diff --git a/khonshu-navigation-core/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/NavigationExecutor.kt b/khonshu-navigation-core/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/NavigationExecutor.kt index f43485d5..c7bab936 100644 --- a/khonshu-navigation-core/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/NavigationExecutor.kt +++ b/khonshu-navigation-core/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/NavigationExecutor.kt @@ -1,6 +1,7 @@ package com.hoc081098.solivagant.navigation.internal import com.hoc081098.kmp.viewmodel.SavedStateHandle +import com.hoc081098.kmp.viewmodel.SavedStateHandleFactory import com.hoc081098.solivagant.navigation.BaseRoute import com.hoc081098.solivagant.navigation.Navigator import com.hoc081098.solivagant.navigation.Serializable @@ -10,6 +11,7 @@ import kotlin.reflect.KClass public interface NavigationExecutor : Navigator { public fun routeFor(destinationId: DestinationId): T public fun savedStateHandleFor(destinationId: DestinationId): SavedStateHandle + public fun savedStateHandleFactoryFor(destinationId: DestinationId): SavedStateHandleFactory public fun storeFor(destinationId: DestinationId): Store public fun extra(destinationId: DestinationId): Serializable diff --git a/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/NavHost.kt b/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/NavHost.kt index e9fc6afa..2af2df45 100644 --- a/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/NavHost.kt +++ b/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/NavHost.kt @@ -5,10 +5,14 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.SaveableStateHolder import androidx.compose.runtime.saveable.rememberSaveableStateHolder import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.snapshots.Snapshot import androidx.compose.ui.Modifier import com.hoc081098.kmp.viewmodel.Closeable import com.hoc081098.kmp.viewmodel.compose.SavedStateHandleFactoryProvider @@ -16,7 +20,7 @@ import com.hoc081098.kmp.viewmodel.compose.ViewModelStoreOwnerProvider import com.hoc081098.solivagant.navigation.internal.MultiStackNavigationExecutor import com.hoc081098.solivagant.navigation.internal.OnBackPressedCallback import com.hoc081098.solivagant.navigation.internal.StackEntry -import com.hoc081098.solivagant.navigation.internal.ViewModelStoreOwnerCloseable +import com.hoc081098.solivagant.navigation.internal.StackEntryViewModelStoreOwner import com.hoc081098.solivagant.navigation.internal.WeakReference import com.hoc081098.solivagant.navigation.internal.currentBackPressedDispatcher import com.hoc081098.solivagant.navigation.internal.rememberNavigationExecutor @@ -74,23 +78,28 @@ private fun Show( // it is available when the destination is cleared. Which, because of animations, // only happens after this leaves composition. Which means we can't rely on // DisposableEffect to clean up this reference (as it'll be cleaned up too early) - remember(entry, executor, saveableStateHolder) { - executor.storeFor(entry.id).getOrCreate(SaveableCloseable::class) { - SaveableCloseable(entry.id.value, WeakReference(saveableStateHolder)) - } + val saveableCloseable = remember(entry, executor, saveableStateHolder) { + executor + .storeFor(entry.id) + .getOrCreate(SaveableCloseable::class) { + SaveableCloseable( + entry.id.value, + WeakReference(saveableStateHolder), + ) + } } + val viewModelStoreOwner = saveableCloseable + .viewModelStoreOwnerState + .value // <-- This will cause the recomposition when the value is cleared. + ?: return + saveableStateHolder.SaveableStateProvider(entry.id.value) { ViewModelStoreOwnerProvider( - viewModelStoreOwner = executor - .storeFor(entry.id) - .getOrCreate( - ViewModelStoreOwnerCloseable::class, - ::ViewModelStoreOwnerCloseable, - ), + viewModelStoreOwner = viewModelStoreOwner, ) { SavedStateHandleFactoryProvider( - savedStateHandleFactory = { executor.savedStateHandleFor(entry.destinationId) }, + savedStateHandleFactory = executor.savedStateHandleFactoryFor(entry.destinationId), ) { entry.destination.content(entry.route) } @@ -102,7 +111,17 @@ internal class SaveableCloseable( private val id: String, private val saveableStateHolderRef: WeakReference, ) : Closeable { + private val _viewModelStoreOwnerState: MutableState = + mutableStateOf(StackEntryViewModelStoreOwner()) + + inline val viewModelStoreOwnerState: State get() = _viewModelStoreOwnerState + override fun close() { + Snapshot.withMutableSnapshot { + _viewModelStoreOwnerState.value?.clearIfInitialized() + _viewModelStoreOwnerState.value = null + } + saveableStateHolderRef.get()?.removeState(id) saveableStateHolderRef.clear() } diff --git a/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/MultiStackNavigationExecutor.kt b/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/MultiStackNavigationExecutor.kt index 682c9e2a..72299f1a 100644 --- a/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/MultiStackNavigationExecutor.kt +++ b/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/MultiStackNavigationExecutor.kt @@ -2,6 +2,7 @@ package com.hoc081098.solivagant.navigation.internal import androidx.compose.runtime.State import com.hoc081098.kmp.viewmodel.SavedStateHandle +import com.hoc081098.kmp.viewmodel.SavedStateHandleFactory import com.hoc081098.solivagant.navigation.BaseRoute import com.hoc081098.solivagant.navigation.NavRoot import com.hoc081098.solivagant.navigation.NavRoute @@ -67,6 +68,11 @@ internal class MultiStackNavigationExecutor( return viewModel.provideSavedStateHandle(entry.id, entry.route) } + override fun savedStateHandleFactoryFor(destinationId: DestinationId): SavedStateHandleFactory { + val entry = entryFor(destinationId) + return viewModel.provideSavedStateHandleFactory(entry.id, entry.route) + } + override fun storeFor(destinationId: DestinationId): NavigationExecutor.Store { val entry = entryFor(destinationId) return storeFor(entry.id) diff --git a/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/StoreViewModel.kt b/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/StoreViewModel.kt index 88765340..d39aad43 100644 --- a/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/StoreViewModel.kt +++ b/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/StoreViewModel.kt @@ -1,6 +1,7 @@ package com.hoc081098.solivagant.navigation.internal import com.hoc081098.kmp.viewmodel.SavedStateHandle +import com.hoc081098.kmp.viewmodel.SavedStateHandleFactory import com.hoc081098.kmp.viewmodel.ViewModel import com.hoc081098.solivagant.navigation.BaseRoute import com.hoc081098.solivagant.navigation.EXTRA_ROUTE @@ -12,25 +13,33 @@ internal class StoreViewModel( ) : ViewModel() { private val stores = mutableMapOf() private val savedStateHandles = mutableMapOf() + private val savedStateHandleFactories = mutableMapOf() internal val savedNavRoot: NavRoot? get() = globalSavedStateHandle[SAVED_START_ROOT_KEY] - fun provideStore(id: StackEntry.Id): NavigationExecutor.Store { + internal fun provideStore(id: StackEntry.Id): NavigationExecutor.Store { return stores.getOrPut(id) { NavigationExecutorStore() } } - fun provideSavedStateHandle(id: StackEntry.Id, route: BaseRoute): SavedStateHandle { + internal fun provideSavedStateHandle(id: StackEntry.Id, route: BaseRoute): SavedStateHandle { return savedStateHandles.getOrPut(id) { createSavedStateHandleAndSetSavedStateProvider(id.value, globalSavedStateHandle) .apply { this[EXTRA_ROUTE] = route } } } - fun removeEntry(id: StackEntry.Id) { + internal fun provideSavedStateHandleFactory(id: StackEntry.Id, route: BaseRoute): SavedStateHandleFactory { + return savedStateHandleFactories.getOrPut(id) { + SavedStateHandleFactory { provideSavedStateHandle(id, route) } + } + } + + internal fun removeEntry(id: StackEntry.Id) { val store = stores.remove(id) store?.close() savedStateHandles.remove(id) + savedStateHandleFactories.remove(id) globalSavedStateHandle.removeSavedStateProvider(id.value) globalSavedStateHandle.remove(id.value) } @@ -47,6 +56,7 @@ internal class StoreViewModel( globalSavedStateHandle.remove(key.value) } savedStateHandles.clear() + savedStateHandleFactories.clear() } internal fun setInputStartRoot(root: NavRoot) { diff --git a/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/ViewModelStoreOwnerCloseable.kt b/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/ViewModelStoreOwnerCloseable.kt index a13da4f0..e6023efe 100644 --- a/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/ViewModelStoreOwnerCloseable.kt +++ b/navigation/src/commonMain/kotlin/com/hoc081098/solivagant/navigation/internal/ViewModelStoreOwnerCloseable.kt @@ -1,15 +1,15 @@ package com.hoc081098.solivagant.navigation.internal -import com.hoc081098.kmp.viewmodel.Closeable import com.hoc081098.kmp.viewmodel.ViewModelStore import com.hoc081098.kmp.viewmodel.ViewModelStoreOwner +import kotlin.LazyThreadSafetyMode.NONE internal expect fun createViewModelStore(): ViewModelStore -internal class ViewModelStoreOwnerCloseable : Closeable, ViewModelStoreOwner { - private val viewModelStoreLazy = lazy { createViewModelStore() } +internal class StackEntryViewModelStoreOwner : ViewModelStoreOwner { + private val viewModelStoreLazy = lazy(NONE) { createViewModelStore() } - override fun close() { + fun clearIfInitialized() { if (viewModelStoreLazy.isInitialized()) { viewModelStoreLazy.value.clear() } diff --git a/sample/app/build.gradle.kts b/sample/app/build.gradle.kts index e230af82..96318483 100644 --- a/sample/app/build.gradle.kts +++ b/sample/app/build.gradle.kts @@ -43,41 +43,5 @@ android { dependencies { implementation(project(":sample:shared")) -// implementation(platform(libs.androidx.compose.bom)) -// -// implementation(libs.androidx.lifecycle.runtime.compose) -// -// implementation(libs.androidx.compose.ui.ui) -// debugImplementation(libs.androidx.compose.ui.tooling) -// implementation(libs.androidx.compose.ui.tooling.preview) -// implementation(libs.androidx.compose.foundation) -// implementation(libs.androidx.compose.material3) -// implementation(libs.androidx.compose.material) -// implementation(libs.androidx.compose.runtime) implementation(libs.androidx.activity.compose) -// implementation(libs.androidx.navigation.compose) - -// implementation(libs.koin.androidx.compose) -// implementation(libs.coil.compose) - -// implementation(libs.kotlinx.collections.immutable) } - -// tasks.withType { -// kotlinOptions { -// val buildDirAbsolutePath = project.layout.buildDirectory.map { it.asFile.absolutePath }.get() -// -// if (project.findProperty("composeCompilerReports") == "true") { -// freeCompilerArgs = freeCompilerArgs + listOf( -// "-P", -// "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=$buildDirAbsolutePath/compose_compiler", -// ) -// } -// if (project.findProperty("composeCompilerMetrics") == "true") { -// freeCompilerArgs = freeCompilerArgs + listOf( -// "-P", -// "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=$buildDirAbsolutePath/compose_compiler", -// ) -// } -// } -// }