diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ca40a135..5a3b45af 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -47,10 +47,11 @@ koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin-com hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" } appCompat = { module = "androidx.appcompat:appcompat", version.ref = "appCompat" } -lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } -lifecycle-savedState = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycle" } lifecycle-viewModelKtx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" } lifecycle-viewModelCompose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" } +jetbrains-lifecycle-runtime-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" } +jetbrains-lifecycle-savedState = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycle" } +jetbrains-core-bundle = { module = "org.jetbrains.androidx.core:core-bundle", version = "1.0.0" } compose-rxjava = { module = "androidx.compose.runtime:runtime-rxjava3", version.ref = "composeRuntime" } compose-compiler = { module = "androidx.compose.compiler:compiler", version.ref = "composeCompiler" } compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "composeRuntime" } diff --git a/samples/multi-module/feature-home/src/main/java/cafe/adriel/voyager/sample/multimodule/home/HomeScreen.kt b/samples/multi-module/feature-home/src/main/java/cafe/adriel/voyager/sample/multimodule/home/HomeScreen.kt index 52d5a82b..6915c9a9 100644 --- a/samples/multi-module/feature-home/src/main/java/cafe/adriel/voyager/sample/multimodule/home/HomeScreen.kt +++ b/samples/multi-module/feature-home/src/main/java/cafe/adriel/voyager/sample/multimodule/home/HomeScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.dropUnlessResumed import cafe.adriel.voyager.core.registry.rememberScreen import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator @@ -43,7 +44,7 @@ class HomeScreen : Screen { Spacer(modifier = Modifier.height(16.dp)) Button( - onClick = { navigator.push(postListScreen) } + onClick = dropUnlessResumed { navigator.push(postListScreen) } ) { Text( text = "To Post List", @@ -54,7 +55,7 @@ class HomeScreen : Screen { Spacer(modifier = Modifier.height(16.dp)) Button( - onClick = { navigator.push(postDetailsScreen) } + onClick = dropUnlessResumed { navigator.push(postDetailsScreen) } ) { Text( text = "To Post Details", diff --git a/samples/multi-module/feature-posts/src/main/java/cafe/adriel/voyager/sample/multimodule/posts/DetailsScreen.kt b/samples/multi-module/feature-posts/src/main/java/cafe/adriel/voyager/sample/multimodule/posts/DetailsScreen.kt index 8081006a..2408d752 100644 --- a/samples/multi-module/feature-posts/src/main/java/cafe/adriel/voyager/sample/multimodule/posts/DetailsScreen.kt +++ b/samples/multi-module/feature-posts/src/main/java/cafe/adriel/voyager/sample/multimodule/posts/DetailsScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.dropUnlessResumed import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow @@ -44,7 +45,7 @@ data class DetailsScreen( Spacer(modifier = Modifier.height(16.dp)) Button( - onClick = { navigator.pop() } + onClick = dropUnlessResumed { navigator.pop() } ) { Text( text = "Return", diff --git a/samples/multi-module/feature-posts/src/main/java/cafe/adriel/voyager/sample/multimodule/posts/ListScreen.kt b/samples/multi-module/feature-posts/src/main/java/cafe/adriel/voyager/sample/multimodule/posts/ListScreen.kt index 7f7baa9a..94701b1a 100644 --- a/samples/multi-module/feature-posts/src/main/java/cafe/adriel/voyager/sample/multimodule/posts/ListScreen.kt +++ b/samples/multi-module/feature-posts/src/main/java/cafe/adriel/voyager/sample/multimodule/posts/ListScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.dropUnlessResumed import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow @@ -35,7 +36,7 @@ class ListScreen : Screen { Spacer(modifier = Modifier.height(16.dp)) Button( - onClick = { navigator.pop() } + onClick = dropUnlessResumed { navigator.pop() } ) { Text( text = "Return", diff --git a/samples/multiplatform/src/commonMain/kotlin/cafe/adriel/voyager/sample/multiplatform/BasicNavigationScreen.kt b/samples/multiplatform/src/commonMain/kotlin/cafe/adriel/voyager/sample/multiplatform/BasicNavigationScreen.kt index ef71a4ab..9fe6a161 100644 --- a/samples/multiplatform/src/commonMain/kotlin/cafe/adriel/voyager/sample/multiplatform/BasicNavigationScreen.kt +++ b/samples/multiplatform/src/commonMain/kotlin/cafe/adriel/voyager/sample/multiplatform/BasicNavigationScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.dropUnlessResumed import cafe.adriel.voyager.core.lifecycle.LifecycleEffect import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.ScreenKey @@ -62,7 +63,9 @@ public data class BasicNavigationScreen( ) { Button( enabled = navigator.canPop, - onClick = navigator::pop, + onClick = dropUnlessResumed { + navigator.pop() + }, modifier = Modifier.weight(.5f) ) { Text(text = "Pop") @@ -71,7 +74,9 @@ public data class BasicNavigationScreen( Spacer(modifier = Modifier.weight(.1f)) Button( - onClick = { navigator.push(BasicNavigationScreen(index.inc(), wrapContent)) }, + onClick = dropUnlessResumed { + navigator.push(BasicNavigationScreen(index.inc(), wrapContent)) + }, modifier = Modifier.weight(.5f) ) { Text(text = "Push") @@ -80,7 +85,9 @@ public data class BasicNavigationScreen( Spacer(modifier = Modifier.weight(.1f)) Button( - onClick = { navigator.replace(BasicNavigationScreen(index.inc(), wrapContent)) }, + onClick = dropUnlessResumed { + navigator.replace(BasicNavigationScreen(index.inc(), wrapContent)) + }, modifier = Modifier.weight(.5f) ) { Text(text = "Replace") diff --git a/voyager-core/build.gradle.kts b/voyager-core/build.gradle.kts index 5e80f46b..f5d387eb 100644 --- a/voyager-core/build.gradle.kts +++ b/voyager-core/build.gradle.kts @@ -21,6 +21,9 @@ kotlin { compileOnly(compose.runtime) compileOnly(compose.runtimeSaveable) implementation(libs.coroutines.core) + api(libs.jetbrains.lifecycle.runtime.compose) + api(libs.jetbrains.lifecycle.savedState) + api(libs.jetbrains.core.bundle) } jvmTest.dependencies { implementation(libs.junit.api) @@ -29,8 +32,6 @@ kotlin { androidMain.dependencies { implementation(libs.compose.activity) - implementation(libs.lifecycle.runtime) - implementation(libs.lifecycle.savedState) implementation(libs.lifecycle.viewModelKtx) implementation(libs.lifecycle.viewModelCompose) } diff --git a/voyager-core/src/androidMain/kotlin/cafe/adriel/voyager/androidx/AndroidScreenLifecycleOwner.kt b/voyager-core/src/androidMain/kotlin/cafe/adriel/voyager/androidx/AndroidScreenLifecycleOwner.kt index c3d0a33f..bc5b59de 100644 --- a/voyager-core/src/androidMain/kotlin/cafe/adriel/voyager/androidx/AndroidScreenLifecycleOwner.kt +++ b/voyager-core/src/androidMain/kotlin/cafe/adriel/voyager/androidx/AndroidScreenLifecycleOwner.kt @@ -2,24 +2,15 @@ package cafe.adriel.voyager.androidx import android.app.Application import android.content.Context -import android.os.Bundle +import android.content.ContextWrapper import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.ProvidedValue -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalSavedStateRegistryOwner -import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.HasDefaultViewModelProviderFactory -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.SAVED_STATE_REGISTRY_OWNER_KEY import androidx.lifecycle.SavedStateViewModelFactory import androidx.lifecycle.VIEW_MODEL_STORE_OWNER_KEY @@ -31,34 +22,20 @@ import androidx.lifecycle.enableSavedStateHandles import androidx.lifecycle.viewmodel.CreationExtras import androidx.lifecycle.viewmodel.MutableCreationExtras import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner -import androidx.savedstate.SavedStateRegistry -import androidx.savedstate.SavedStateRegistryController -import androidx.savedstate.SavedStateRegistryOwner +import cafe.adriel.voyager.core.lifecycle.CommonScreenLifecycleOwner import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleOwner import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleStore import cafe.adriel.voyager.core.screen.Screen import java.util.concurrent.atomic.AtomicReference public class AndroidScreenLifecycleOwner private constructor() : - ScreenLifecycleOwner, - LifecycleOwner, + CommonScreenLifecycleOwner(), ViewModelStoreOwner, - SavedStateRegistryOwner, HasDefaultViewModelProviderFactory { - override val lifecycle: LifecycleRegistry = LifecycleRegistry(this) - override val viewModelStore: ViewModelStore = ViewModelStore() private val atomicAppContext = AtomicReference() - internal val atomicParentLifecycleOwner = AtomicReference() - - private val controller = SavedStateRegistryController.create(this) - - private var isCreated: Boolean by mutableStateOf(false) - - override val savedStateRegistry: SavedStateRegistry - get() = controller.savedStateRegistry override val defaultViewModelProviderFactory: ViewModelProvider.Factory get() = SavedStateViewModelFactory( @@ -81,41 +58,16 @@ public class AndroidScreenLifecycleOwner private constructor() : } init { - controller.performAttach() enableSavedStateHandles() } - private fun onCreate(savedState: Bundle?) { - check(!isCreated) { "onCreate already called" } - isCreated = true - controller.performRestore(savedState) - initEvents.forEach { - lifecycle.safeHandleLifecycleEvent(it) - } - } - - private fun emitOnStartEvents() { - startEvents.forEach { - lifecycle.safeHandleLifecycleEvent(it) - } - } - - private fun emitOnStopEvents() { - stopEvents.forEach { - lifecycle.safeHandleLifecycleEvent(it) - } - } - @Composable override fun ProvideBeforeScreenContent( provideSaveableState: @Composable (suffixKey: String, content: @Composable () -> Unit) -> Unit, content: @Composable () -> Unit ) { provideSaveableState("lifecycle") { - LifecycleDisposableEffect() - val hooks = getHooks() - CompositionLocalProvider(*hooks.toTypedArray()) { content() } @@ -123,14 +75,8 @@ public class AndroidScreenLifecycleOwner private constructor() : } override fun onDispose(screen: Screen) { + super.onDispose(screen) viewModelStore.clear() - disposeEvents.forEach { event -> - lifecycle.safeHandleLifecycleEvent(event) - } - } - - private fun performSave(outState: Bundle) { - controller.performSave(outState) } @Composable @@ -140,103 +86,18 @@ public class AndroidScreenLifecycleOwner private constructor() : return remember(this) { listOf( - LocalLifecycleOwner provides this, LocalViewModelStoreOwner provides this, LocalSavedStateRegistryOwner provides this ) } } - /** - * Returns a unregister callback - */ - private fun registerLifecycleListener(outState: Bundle): () -> Unit { - val lifecycleOwner = atomicParentLifecycleOwner.get() - if (lifecycleOwner != null) { - val observer = object : DefaultLifecycleObserver { - override fun onPause(owner: LifecycleOwner) { - lifecycle.safeHandleLifecycleEvent(Lifecycle.Event.ON_PAUSE) - } - - override fun onResume(owner: LifecycleOwner) { - lifecycle.safeHandleLifecycleEvent(Lifecycle.Event.ON_RESUME) - } - - override fun onStart(owner: LifecycleOwner) { - lifecycle.safeHandleLifecycleEvent(Lifecycle.Event.ON_START) - } - - override fun onStop(owner: LifecycleOwner) { - lifecycle.safeHandleLifecycleEvent(Lifecycle.Event.ON_STOP) - - // when the Application goes to background, perform save - performSave(outState) - } - } - val lifecycle = lifecycleOwner.lifecycle - lifecycle.addObserver(observer) - - return { lifecycle.removeObserver(observer) } - } else { - return { } - } - } - - @Composable - private fun LifecycleDisposableEffect() { - val savedState = rememberSaveable { Bundle() } - if (!isCreated) { - onCreate(savedState) // do this in the UI thread to force it to be called before anything else - } - - DisposableEffect(this) { - val unregisterLifecycle = registerLifecycleListener(savedState) - emitOnStartEvents() - - onDispose { - unregisterLifecycle() - - // when the screen goes to stack, perform save - performSave(savedState) - - // notify lifecycle screen listeners - emitOnStopEvents() - } - } - } - - private fun Context.getApplication(): Application? = when (this) { + private tailrec fun Context.getApplication(): Application? = when (this) { is Application -> this else -> null } - private fun LifecycleRegistry.safeHandleLifecycleEvent(event: Lifecycle.Event) { - val currentState = currentState - if (!currentState.isAtLeast(Lifecycle.State.INITIALIZED)) return - - handleLifecycleEvent(event) - } - public companion object { - - private val initEvents = arrayOf( - Lifecycle.Event.ON_CREATE - ) - - private val startEvents = arrayOf( - Lifecycle.Event.ON_START, - Lifecycle.Event.ON_RESUME - ) - - private val stopEvents = arrayOf( - Lifecycle.Event.ON_PAUSE, - Lifecycle.Event.ON_STOP - ) - - private val disposeEvents = arrayOf( - Lifecycle.Event.ON_DESTROY - ) - public fun get(screen: Screen): ScreenLifecycleOwner { return ScreenLifecycleStore.get(screen) { AndroidScreenLifecycleOwner() } } diff --git a/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/lifecycle/ScreenLifecycleOwner.kt b/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/lifecycle/ScreenLifecycleOwner.kt index dcd90a0f..d0aa9eeb 100644 --- a/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/lifecycle/ScreenLifecycleOwner.kt +++ b/voyager-core/src/commonMain/kotlin/cafe/adriel/voyager/core/lifecycle/ScreenLifecycleOwner.kt @@ -1,10 +1,27 @@ package cafe.adriel.voyager.core.lifecycle import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.core.bundle.Bundle +import androidx.lifecycle.AtomicReference +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import androidx.savedstate.SavedStateRegistry +import androidx.savedstate.SavedStateRegistryController +import androidx.savedstate.SavedStateRegistryOwner import cafe.adriel.voyager.core.annotation.InternalVoyagerApi import cafe.adriel.voyager.core.screen.Screen -public interface ScreenLifecycleOwner : ScreenLifecycleContentProvider, ScreenDisposable +public interface ScreenLifecycleOwner : ScreenLifecycleContentProvider, + LifecycleOwner, + SavedStateRegistryOwner, + ScreenDisposable public interface ScreenLifecycleContentProvider { /** @@ -26,5 +43,149 @@ public interface ScreenDisposable { public fun onDispose(screen: Screen) {} } +public abstract class CommonScreenLifecycleOwner : ScreenLifecycleOwner { + + private val lifecycleRegistry by lazy(LazyThreadSafetyMode.NONE) { + LifecycleRegistry(this) + } + + override val lifecycle: Lifecycle + get() = lifecycleRegistry + + private val controller by lazy(LazyThreadSafetyMode.NONE) { + SavedStateRegistryController.create(this) + } + + override val savedStateRegistry: SavedStateRegistry + get() = controller.savedStateRegistry + + private var isCreated: Boolean by mutableStateOf(false) + + internal val atomicParentLifecycleOwner = AtomicReference(null) + + init { + controller.performAttach() + } + + @Composable + public fun LifecycleDisposableEffect() { + val savedState = rememberSaveable { Bundle() } + if (!isCreated) { + onCreate(savedState) // do this in the UI thread to force it to be called before anything else + } + + DisposableEffect(this) { + val unregisterLifecycle = registerLifecycleListener(savedState) + emitOnStartEvents() + + onDispose { + unregisterLifecycle() + + // when the screen goes to stack, perform save + performSave(savedState) + + // notify lifecycle screen listeners + emitOnStopEvents() + } + } + } + + /** + * Returns a unregister callback + */ + private fun registerLifecycleListener(outState: Bundle): () -> Unit { + val lifecycleOwner = atomicParentLifecycleOwner.get() + if (lifecycleOwner != null) { + val observer = object : DefaultLifecycleObserver { + override fun onPause(owner: LifecycleOwner) { + lifecycleRegistry.safeHandleLifecycleEvent(Lifecycle.Event.ON_PAUSE) + } + + override fun onResume(owner: LifecycleOwner) { + lifecycleRegistry.safeHandleLifecycleEvent(Lifecycle.Event.ON_RESUME) + } + + override fun onStart(owner: LifecycleOwner) { + lifecycleRegistry.safeHandleLifecycleEvent(Lifecycle.Event.ON_START) + } + + override fun onStop(owner: LifecycleOwner) { + lifecycleRegistry.safeHandleLifecycleEvent(Lifecycle.Event.ON_STOP) + + // when the Application goes to background, perform save + performSave(outState) + } + } + val lifecycle = lifecycleOwner.lifecycle + lifecycle.addObserver(observer) + + return { lifecycle.removeObserver(observer) } + } else { + return { } + } + } + + private fun performSave(outState: Bundle) { + controller.performSave(outState) + } + + + private fun onCreate(savedState: Bundle?) { + check(!isCreated) { "onCreate already called" } + isCreated = true + controller.performRestore(savedState) + initEvents.forEach { + lifecycleRegistry.safeHandleLifecycleEvent(it) + } + } + + private fun emitOnStartEvents() { + startEvents.forEach { + lifecycleRegistry.safeHandleLifecycleEvent(it) + } + } + + private fun emitOnStopEvents() { + stopEvents.forEach { + lifecycleRegistry.safeHandleLifecycleEvent(it) + } + } + + + private fun LifecycleRegistry.safeHandleLifecycleEvent(event: Lifecycle.Event) { + val currentState = currentState + if (!currentState.isAtLeast(Lifecycle.State.INITIALIZED)) return + + handleLifecycleEvent(event) + } + + override fun onDispose(screen: Screen) { + disposeEvents.forEach { event -> + lifecycleRegistry.safeHandleLifecycleEvent(event) + } + } + + private companion object { + private val initEvents = arrayOf( + Lifecycle.Event.ON_CREATE + ) + + private val startEvents = arrayOf( + Lifecycle.Event.ON_START, + Lifecycle.Event.ON_RESUME + ) + + private val stopEvents = arrayOf( + Lifecycle.Event.ON_PAUSE, + Lifecycle.Event.ON_STOP + ) + + private val disposeEvents = arrayOf( + Lifecycle.Event.ON_DESTROY + ) + } +} + + @InternalVoyagerApi -public object DefaultScreenLifecycleOwner : ScreenLifecycleOwner +public object DefaultScreenLifecycleOwner : CommonScreenLifecycleOwner() diff --git a/voyager-hilt/build.gradle.kts b/voyager-hilt/build.gradle.kts index 41cbdcf4..5aac0d27 100644 --- a/voyager-hilt/build.gradle.kts +++ b/voyager-hilt/build.gradle.kts @@ -24,7 +24,6 @@ dependencies { implementation(libs.compose.runtime) implementation(libs.compose.ui) - implementation(libs.lifecycle.savedState) implementation(libs.lifecycle.viewModelKtx) implementation(libs.hilt.android) implementation(libs.lifecycle.viewModelCompose) diff --git a/voyager-kodein/src/commonMain/kotlin/cafe/adriel/voyager/kodein/ScreenLifecycleScope.kt b/voyager-kodein/src/commonMain/kotlin/cafe/adriel/voyager/kodein/ScreenLifecycleScope.kt index a54a8f19..cc3c8c58 100644 --- a/voyager-kodein/src/commonMain/kotlin/cafe/adriel/voyager/kodein/ScreenLifecycleScope.kt +++ b/voyager-kodein/src/commonMain/kotlin/cafe/adriel/voyager/kodein/ScreenLifecycleScope.kt @@ -3,7 +3,7 @@ package cafe.adriel.voyager.kodein import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi -import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleOwner +import cafe.adriel.voyager.core.lifecycle.CommonScreenLifecycleOwner import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleStore import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator @@ -70,8 +70,9 @@ public open class ScreenLifecycleScope private constructor( private class ScreenScopeLifecycleOwner( val onDispose: () -> Unit -) : ScreenLifecycleOwner { +) : CommonScreenLifecycleOwner() { override fun onDispose(screen: Screen) { + super.onDispose(screen) onDispose() } } diff --git a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/Navigator.kt b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/Navigator.kt index f9239a71..8b1bed16 100644 --- a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/Navigator.kt +++ b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/Navigator.kt @@ -2,6 +2,7 @@ package cafe.adriel.voyager.navigator import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.currentCompositeKeyHash import androidx.compose.runtime.derivedStateOf @@ -10,9 +11,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.SaveableStateHolder import androidx.compose.runtime.saveable.rememberSaveableStateHolder import androidx.compose.runtime.staticCompositionLocalOf +import androidx.lifecycle.compose.LocalLifecycleOwner import cafe.adriel.voyager.core.annotation.InternalVoyagerApi import cafe.adriel.voyager.core.concurrent.ThreadSafeMap import cafe.adriel.voyager.core.concurrent.ThreadSafeSet +import cafe.adriel.voyager.core.lifecycle.CommonScreenLifecycleOwner import cafe.adriel.voyager.core.lifecycle.MultipleProvideBeforeScreenContent import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleStore import cafe.adriel.voyager.core.lifecycle.rememberScreenLifecycleOwner @@ -147,7 +150,16 @@ public class Navigator @InternalVoyagerApi constructor( screenLifecycleContentProviders = composed, provideSaveableState = { suffix, content -> provideSaveableState(suffix, content) }, content = { - stateHolder.SaveableStateProvider(stateKey, content) + CompositionLocalProvider( + LocalLifecycleOwner provides lifecycleOwner, + ) { + stateHolder.SaveableStateProvider(stateKey) { + content() + } + if (lifecycleOwner is CommonScreenLifecycleOwner) { + lifecycleOwner.LifecycleDisposableEffect() + } + } } ) }