diff --git a/examples/todoapp/common/root/src/commonMain/kotlin/example/todo/common/root/integration/TodoRootImpl.kt b/examples/todoapp/common/root/src/commonMain/kotlin/example/todo/common/root/integration/TodoRootImpl.kt index e8e1ccad61..570ddf7d9a 100644 --- a/examples/todoapp/common/root/src/commonMain/kotlin/example/todo/common/root/integration/TodoRootImpl.kt +++ b/examples/todoapp/common/root/src/commonMain/kotlin/example/todo/common/root/integration/TodoRootImpl.kt @@ -1,8 +1,8 @@ package example.todo.common.root.integration import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.extensions.compose.children import com.arkivanov.decompose.router import com.arkivanov.decompose.statekeeper.Parcelable import com.arkivanov.decompose.statekeeper.Parcelize @@ -14,7 +14,6 @@ import example.todo.common.root.TodoRoot.Dependencies import example.todo.common.utils.Component import example.todo.common.utils.Consumer import example.todo.common.utils.Crossfade -import example.todo.common.utils.asState internal class TodoRootImpl( componentContext: ComponentContext, @@ -63,11 +62,10 @@ internal class TodoRootImpl( @Composable override fun invoke() { - val routerState by router.state.asState() - val activeChild = routerState.activeChild - - Crossfade(currentChild = activeChild.component, currentKey = activeChild.configuration) { child -> - child.invoke() + router.state.children { child, configuration -> + Crossfade(currentChild = child, currentKey = configuration) { currentChild -> + currentChild() + } } } diff --git a/examples/todoapp/common/utils/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/RouterStateComposable.kt b/examples/todoapp/common/utils/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/RouterStateComposable.kt new file mode 100644 index 0000000000..f47b70f663 --- /dev/null +++ b/examples/todoapp/common/utils/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/RouterStateComposable.kt @@ -0,0 +1,100 @@ +/* + * Copied from Decompose + */ + +package com.arkivanov.decompose.extensions.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers +import androidx.compose.runtime.onDispose +import androidx.compose.runtime.remember +import androidx.compose.runtime.savedinstancestate.UiSavedStateRegistry +import androidx.compose.runtime.savedinstancestate.UiSavedStateRegistryAmbient +import com.arkivanov.decompose.RouterState +import com.arkivanov.decompose.statekeeper.Parcelable +import com.arkivanov.decompose.value.Value +import example.todo.common.utils.invoke + +private typealias SavedState = Map> + +@Composable +fun Value>.children(render: @Composable() (child: T, configuration: C) -> Unit) { + val parentRegistry: UiSavedStateRegistry? = UiSavedStateRegistryAmbient.current + val children = remember { Children() } + + if (parentRegistry != null) { + onDispose { + children.inactive.entries.forEach { (key, value) -> + parentRegistry.unregisterProvider(key, value.provider) + } + children.active?.also { + parentRegistry.unregisterProvider(it.key, it.provider) + } + } + } + + invoke { state -> + val activeChildConfiguration = state.activeChild.configuration + + val currentChild: ActiveChild? = children.active + if ((currentChild != null) && state.backStack.any { it.configuration === currentChild.configuration }) { + parentRegistry?.unregisterProvider(currentChild.key, currentChild.provider) + val inactiveChild = InactiveChild(configuration = currentChild.configuration, savedState = currentChild.provider()) + children.inactive[currentChild.key] = inactiveChild + parentRegistry?.registerProvider(currentChild.key, inactiveChild.provider) + } + + val activeChildRegistry: UiSavedStateRegistry + + if (currentChild?.configuration === activeChildConfiguration) { + activeChildRegistry = currentChild.registry + } else { + val key = activeChildConfiguration.toString() + + val savedChild: InactiveChild? = children.inactive.remove(key) + if (savedChild != null) { + parentRegistry?.unregisterProvider(key, savedChild.provider) + } + @Suppress("UNCHECKED_CAST") + val savedState: SavedState? = savedChild?.savedState ?: parentRegistry?.consumeRestored(key) as SavedState? + + activeChildRegistry = UiSavedStateRegistry(savedState) { true } + + val newActiveChild = ActiveChild(configuration = activeChildConfiguration, key = key, registry = activeChildRegistry) + children.active = newActiveChild + parentRegistry?.registerProvider(key, newActiveChild.provider) + } + + children.inactive.entries.removeAll { (key, value) -> + val remove = state.backStack.none { it.configuration === value.configuration } + if (remove) { + parentRegistry?.unregisterProvider(key, value.provider) + } + remove + } + + Providers(UiSavedStateRegistryAmbient provides activeChildRegistry) { + render(state.activeChild.component, activeChildConfiguration) + } + } +} + +private class Children { + val inactive: MutableMap> = HashMap() + var active: ActiveChild? = null +} + +private class ActiveChild( + val configuration: C, + val key: String, + val registry: UiSavedStateRegistry +) { + val provider: () -> SavedState = registry::performSave +} + +private class InactiveChild( + val configuration: C, + val savedState: SavedState +) { + val provider: () -> SavedState = ::savedState +}