Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suspend children #122

Merged
merged 10 commits into from
Sep 8, 2022
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Pending changes

- [#122](https://github.com/bumble-tech/appyx/pull/122) - **Breaking change**: `ChildEntry.ChildMode` is removed, now nodes are always created when a nav model changes (previously default behaviour)
- [#122](https://github.com/bumble-tech/appyx/pull/122) - **Added**: New `ChildEntry.KeepMode` that allows to destroy nodes that are currently not visible on the screen
- [#119](https://github.com/bumble-tech/appyx/pull/119) - **Fixed**: Lifecycle observers are invoked in incorrect order (child before parent)
- [#62](https://github.com/bumble-tech/appyx/pull/62) - **Fixed**: Node is marked with stable annotation making some of the composable functions skippable
- [#129](https://github.com/bumble-tech/appyx/pull/129) - **Updated**: Removed sealed interface from operations to allow client to define their own
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fun Page(

Column(
modifier = Modifier
.weight(0.35f)
.weight(0.5f)
) {
Text(
text = title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ class StatefulNode2(
body = "– even when they're not visible. " +
"\n\nTry going back to the previous screen! " +
"You should see that the counters kept on working in the background, " +
"and changes you made to colours are persisted."
"and changes you made to colours are persisted." +
"\n\nIf this behaviour is not desired you can destroy them by using different KeepMode."
) {
val shape = RoundedCornerShape(16.dp)

Expand All @@ -72,10 +73,10 @@ class StatefulNode2(
) {
var counter by remember { mutableStateOf(105) }
LaunchedEffect(Unit) {
while (true) {
delay(1000)
counter++
}
while (true) {
delay(1000)
counter++
}
}

Text(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.test.espresso.Espresso
import androidx.test.platform.app.InstrumentationRegistry
import com.bumble.appyx.Appyx
import com.bumble.appyx.core.children.nodeOrNull
import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.ParentNode
import com.bumble.appyx.core.node.node
import com.bumble.appyx.debug.Appyx
import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.activeRouting
import com.bumble.appyx.navmodel.backstack.operation.push
Expand Down
19 changes: 19 additions & 0 deletions core/src/main/kotlin/com/bumble/appyx/Appyx.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.bumble.appyx

import com.bumble.appyx.core.children.ChildEntry

object Appyx {

var exceptionHandler: ((Exception) -> Unit)? = null
var defaultChildKeepMode: ChildEntry.KeepMode = ChildEntry.KeepMode.KEEP

fun reportException(exception: Exception) {
val handler = exceptionHandler
if (handler != null) {
handler(exception)
} else {
throw exception
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.coroutineScope
import com.bumble.appyx.core.lifecycle.isDestroyed
import com.bumble.appyx.core.navigation.RoutingKey
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode
import com.bumble.appyx.core.navigation.RoutingKey
import com.bumble.appyx.core.withPrevious
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlin.reflect.KClass

class ChildAwareImpl<N: Node> : ChildAware<N> {
class ChildAwareImpl<N : Node> : ChildAware<N> {

private val callbacks: MutableList<ChildAwareCallbackInfo> = ArrayList()

Expand Down Expand Up @@ -74,7 +74,7 @@ class ChildAwareImpl<N: Node> : ChildAware<N> {
val visitedSet = HashSet<Node>()
commonKeys.forEach { key ->
val current = value.current[key]
if (current != value.previous[key] && current is ChildEntry.Eager) {
if (current != value.previous[key] && current is ChildEntry.Initialized) {
notifyWhenChanged(current.node, nodes, visitedSet)
visitedSet.add(current.node)
}
Expand Down Expand Up @@ -131,11 +131,6 @@ class ChildAwareImpl<N: Node> : ChildAware<N> {
}

private fun getCreatedNodes(childEntryMap: Map<out RoutingKey<*>, ChildEntry<*>>) =
childEntryMap.values.mapNotNull { entry ->
when (entry) {
is ChildEntry.Eager -> entry.node
is ChildEntry.Lazy -> null
}
}
childEntryMap.values.mapNotNull { entry -> entry.nodeOrNull }

}
47 changes: 10 additions & 37 deletions core/src/main/kotlin/com/bumble/appyx/core/children/ChildEntry.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package com.bumble.appyx.core.children

import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.build
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.navigation.Resolver
import com.bumble.appyx.core.navigation.RoutingKey
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.state.SavedStateMap

// custom equals/hashCode for MutableStateFlow and equality checks
sealed class ChildEntry<T> {
Expand All @@ -20,45 +18,20 @@ sealed class ChildEntry<T> {
"$key@${javaClass.simpleName}"

/** All public APIs should return this type of child which is ready to work with. */
class Eager<T>(
class Initialized<T>(
override val key: RoutingKey<T>,
val node: Node,
) : ChildEntry<T>()

/** Child representation for Lazy mode. */
class Lazy<T>(
class Suspended<T>(
CherryPerry marked this conversation as resolved.
Show resolved Hide resolved
override val key: RoutingKey<T>,
private val resolver: Resolver<T>,
val buildContext: BuildContext,
) : ChildEntry<T>() {
internal fun initialize(): Eager<T> =
Eager(key, resolver.resolve(key.routing, buildContext).build())
}

/** When to create child nodes? */
enum class ChildMode {

/** When routing state was changed. */
EAGER,

/** When node rendering was requested. */
LAZY,

}
val savedState: SavedStateMap?,
) : ChildEntry<T>()

companion object {
fun <T> create(
key: RoutingKey<T>,
resolver: Resolver<T>,
buildContext: BuildContext,
childMode: ChildMode,
): ChildEntry<T> =
when (childMode) {
ChildMode.EAGER ->
Eager(key, resolver.resolve(key.routing, buildContext).build())
ChildMode.LAZY ->
Lazy(key, resolver, buildContext)
}
/** Keep off-screen nodes in the memory? */
CherryPerry marked this conversation as resolved.
Show resolved Hide resolved
enum class KeepMode {
KEEP,
SUSPEND,
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import com.bumble.appyx.core.node.Node
val <T> ChildEntry<T>.nodeOrNull: Node?
get() =
when (this) {
is ChildEntry.Eager -> node
is ChildEntry.Lazy -> null
is ChildEntry.Initialized -> node
is ChildEntry.Suspended -> null
}
Loading