From fa93d7c00ced89915a1f6fb235ccec8ca2d1475a Mon Sep 17 00:00:00 2001 From: Gabriel Souza Date: Tue, 4 Jun 2024 10:27:02 -0300 Subject: [PATCH 01/10] Dispose Screens only when transition ends --- .../navigator/internal/NavigatorDisposable.kt | 4 +- .../voyager/transitions/ScreenTransition.kt | 41 +++++++++++++++++++ .../transitions/internal/rememberPrevious.kt | 38 +++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt diff --git a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt index 81408884..d0959a41 100644 --- a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt +++ b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt @@ -2,12 +2,14 @@ package cafe.adriel.voyager.navigator.internal import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import cafe.adriel.voyager.core.annotation.InternalVoyagerApi import cafe.adriel.voyager.core.lifecycle.DisposableEffectIgnoringConfiguration import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.lifecycle.NavigatorLifecycleStore -private val disposableEvents: Set = +@InternalVoyagerApi +public val disposableEvents: Set = setOf(StackEvent.Pop, StackEvent.Replace) @Composable diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt index bdf169e9..fa57aaa9 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt @@ -6,13 +6,17 @@ import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ContentTransform import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator +import cafe.adriel.voyager.navigator.internal.disposableEvents +import cafe.adriel.voyager.transitions.internal.rememberPrevious @ExperimentalVoyagerApi public interface ScreenTransition { @@ -84,6 +88,28 @@ public fun ScreenTransition( modifier: Modifier = Modifier, content: ScreenTransitionContent = { it.Content() } ) { + ScreenTransition( + navigator = navigator, + transition = transition, + modifier = modifier, + disposeScreenAfterTransitionEnd = false, + content = content, + ) +} + +@ExperimentalVoyagerApi +@OptIn(ExperimentalAnimationApi::class) +@Composable +public fun ScreenTransition( + navigator: Navigator, + transition: AnimatedContentTransitionScope.() -> ContentTransform, + modifier: Modifier = Modifier, + disposeScreenAfterTransitionEnd: Boolean = false, + content: ScreenTransitionContent = { it.Content() } +) { + // This can be costly because is checking every single item in the list + // we should re evaluate how validation works, maybe validating screen keys or === + val previousItems = rememberPrevious(navigator.items) AnimatedContent( targetState = navigator.lastItem, transitionSpec = { @@ -104,6 +130,21 @@ public fun ScreenTransition( }, modifier = modifier ) { screen -> + if (this.transition.targetState == this.transition.currentState) { + LaunchedEffect(Unit) { + if(disposeScreenAfterTransitionEnd) { + // if disposeSteps = true, lastEvent will be always idle + // else it will keep the event and we can dispose our self. + if (navigator.lastEvent in disposableEvents) { + val newScreenKeys = navigator.items.map { it.key } + previousItems?.filter { it.key !in newScreenKeys }?.forEach { + navigator.dispose(it) + } + navigator.clearEvent() + } + } + } + } navigator.saveableState("transition", screen) { content(screen) } diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt new file mode 100644 index 00000000..433c38e5 --- /dev/null +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt @@ -0,0 +1,38 @@ +package cafe.adriel.voyager.transitions.internal + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.remember + +@Composable +internal fun rememberRef(): MutableState { + // for some reason it always recreated the value with vararg keys, + // leaving out the keys as a parameter for remember for now + return remember() { + object: MutableState { + override var value: T? = null + + override fun component1(): T? = value + + override fun component2(): (T?) -> Unit = { value = it } + } + } +} + +@Composable +internal fun rememberPrevious( + current: T, + shouldUpdate: (prev: T?, curr: T) -> Boolean = { a: T?, b: T -> a != b }, +): T? { + val ref = rememberRef() + + // launched after render, so the current render will have the old value anyway + SideEffect { + if (shouldUpdate(ref.value, current)) { + ref.value = current + } + } + + return ref.value +} From c68e78e521e51481be0b5200835ee8510a49a222 Mon Sep 17 00:00:00 2001 From: Gabriel Souza Date: Tue, 4 Jun 2024 10:59:46 -0300 Subject: [PATCH 02/10] Adding the new parameter to all transitions apis --- .../voyager/transitions/FadeTransition.kt | 20 +++++++++++++++ .../voyager/transitions/ScaleTransition.kt | 20 +++++++++++++++ .../voyager/transitions/ScreenTransition.kt | 25 ++++++++++++++++++- .../voyager/transitions/SlideTransition.kt | 25 +++++++++++++++++++ .../transitions/internal/rememberPrevious.kt | 5 +--- 5 files changed, 90 insertions(+), 5 deletions(-) diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/FadeTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/FadeTransition.kt index 428880c3..61af4445 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/FadeTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/FadeTransition.kt @@ -8,6 +8,7 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi import cafe.adriel.voyager.navigator.Navigator @Composable @@ -16,10 +17,29 @@ public fun FadeTransition( modifier: Modifier = Modifier, animationSpec: FiniteAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow), content: ScreenTransitionContent = { it.Content() } +) { + FadeTransition( + navigator = navigator, + modifier = modifier, + disposeScreenAfterTransitionEnd = false, + animationSpec = animationSpec, + content = content, + ) +} + +@ExperimentalVoyagerApi +@Composable +public fun FadeTransition( + navigator: Navigator, + modifier: Modifier = Modifier, + animationSpec: FiniteAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow), + disposeScreenAfterTransitionEnd: Boolean = false, + content: ScreenTransitionContent = { it.Content() } ) { ScreenTransition( navigator = navigator, modifier = modifier, + disposeScreenAfterTransitionEnd = disposeScreenAfterTransitionEnd, content = content, transition = { fadeIn(animationSpec = animationSpec) togetherWith fadeOut(animationSpec = animationSpec) } ) diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScaleTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScaleTransition.kt index ee957af1..22317211 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScaleTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScaleTransition.kt @@ -8,6 +8,7 @@ import androidx.compose.animation.scaleOut import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator @@ -20,10 +21,29 @@ public fun ScaleTransition( modifier: Modifier = Modifier, animationSpec: FiniteAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow), content: ScreenTransitionContent = { it.Content() } +) { + ScaleTransition( + navigator = navigator, + modifier = modifier, + animationSpec = animationSpec, + disposeScreenAfterTransitionEnd = false, + content = content, + ) +} + +@ExperimentalVoyagerApi +@Composable +public fun ScaleTransition( + navigator: Navigator, + modifier: Modifier = Modifier, + animationSpec: FiniteAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow), + disposeScreenAfterTransitionEnd: Boolean = false, + content: ScreenTransitionContent = { it.Content() } ) { ScreenTransition( navigator = navigator, modifier = modifier, + disposeScreenAfterTransitionEnd = disposeScreenAfterTransitionEnd, content = content, transition = { val (initialScale, targetScale) = when (navigator.lastEvent) { diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt index fa57aaa9..c2230758 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt @@ -40,17 +40,20 @@ public interface ScreenTransition { public typealias ScreenTransitionContent = @Composable AnimatedVisibilityScope.(Screen) -> Unit +@ExperimentalVoyagerApi @Composable public fun ScreenTransition( navigator: Navigator, enterTransition: AnimatedContentTransitionScope.() -> ContentTransform, exitTransition: AnimatedContentTransitionScope.() -> ContentTransform, modifier: Modifier = Modifier, + disposeScreenAfterTransitionEnd: Boolean = false, content: ScreenTransitionContent = { it.Content() } ) { ScreenTransition( navigator = navigator, modifier = modifier, + disposeScreenAfterTransitionEnd = disposeScreenAfterTransitionEnd, content = content, transition = { when (navigator.lastEvent) { @@ -61,12 +64,30 @@ public fun ScreenTransition( ) } +@Composable +public fun ScreenTransition( + navigator: Navigator, + enterTransition: AnimatedContentTransitionScope.() -> ContentTransform, + exitTransition: AnimatedContentTransitionScope.() -> ContentTransform, + modifier: Modifier = Modifier, + content: ScreenTransitionContent = { it.Content() } +) { + ScreenTransition( + navigator = navigator, + enterTransition = enterTransition, + exitTransition = exitTransition, + modifier = modifier, + content = content, + ) +} + @ExperimentalVoyagerApi @Composable public fun ScreenTransition( navigator: Navigator, defaultTransition: ScreenTransition, modifier: Modifier = Modifier, + disposeScreenAfterTransitionEnd: Boolean = false, content: ScreenTransitionContent = { it.Content() } ) { ScreenTransition( @@ -77,7 +98,8 @@ public fun ScreenTransition( enter togetherWith exit }, modifier = modifier, - content = content + disposeScreenAfterTransitionEnd = disposeScreenAfterTransitionEnd, + content = content, ) } @@ -145,6 +167,7 @@ public fun ScreenTransition( } } } + navigator.saveableState("transition", screen) { content(screen) } diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/SlideTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/SlideTransition.kt index efb5311b..c336dbb0 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/SlideTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/SlideTransition.kt @@ -12,6 +12,7 @@ import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.IntOffset +import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator @@ -25,10 +26,34 @@ public fun SlideTransition( visibilityThreshold = IntOffset.VisibilityThreshold ), content: ScreenTransitionContent = { it.Content() } +) { + SlideTransition( + navigator = navigator, + modifier = modifier, + orientation = orientation, + animationSpec = animationSpec, + disposeScreenAfterTransitionEnd = false, + content = content, + ) +} + +@ExperimentalVoyagerApi +@Composable +public fun SlideTransition( + navigator: Navigator, + modifier: Modifier = Modifier, + orientation: SlideOrientation = SlideOrientation.Horizontal, + animationSpec: FiniteAnimationSpec = spring( + stiffness = Spring.StiffnessMediumLow, + visibilityThreshold = IntOffset.VisibilityThreshold + ), + disposeScreenAfterTransitionEnd: Boolean = false, + content: ScreenTransitionContent = { it.Content() } ) { ScreenTransition( navigator = navigator, modifier = modifier, + disposeScreenAfterTransitionEnd = disposeScreenAfterTransitionEnd, content = content, transition = { val (initialOffset, targetOffset) = when (navigator.lastEvent) { diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt index 433c38e5..93c3a323 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt @@ -7,9 +7,7 @@ import androidx.compose.runtime.remember @Composable internal fun rememberRef(): MutableState { - // for some reason it always recreated the value with vararg keys, - // leaving out the keys as a parameter for remember for now - return remember() { + return remember { object: MutableState { override var value: T? = null @@ -27,7 +25,6 @@ internal fun rememberPrevious( ): T? { val ref = rememberRef() - // launched after render, so the current render will have the old value anyway SideEffect { if (shouldUpdate(ref.value, current)) { ref.value = current From 1a6b6ceee06d25f9873daf7215ef4ebfe99d9278 Mon Sep 17 00:00:00 2001 From: Gabriel Souza Date: Tue, 4 Jun 2024 11:03:53 -0300 Subject: [PATCH 03/10] fix lint --- .../cafe/adriel/voyager/transitions/FadeTransition.kt | 2 +- .../cafe/adriel/voyager/transitions/ScaleTransition.kt | 2 +- .../cafe/adriel/voyager/transitions/ScreenTransition.kt | 8 ++++---- .../cafe/adriel/voyager/transitions/SlideTransition.kt | 2 +- .../voyager/transitions/internal/rememberPrevious.kt | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/FadeTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/FadeTransition.kt index 61af4445..2e61c0b7 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/FadeTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/FadeTransition.kt @@ -23,7 +23,7 @@ public fun FadeTransition( modifier = modifier, disposeScreenAfterTransitionEnd = false, animationSpec = animationSpec, - content = content, + content = content ) } diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScaleTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScaleTransition.kt index 22317211..81f8a9cb 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScaleTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScaleTransition.kt @@ -27,7 +27,7 @@ public fun ScaleTransition( modifier = modifier, animationSpec = animationSpec, disposeScreenAfterTransitionEnd = false, - content = content, + content = content ) } diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt index c2230758..09304a35 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt @@ -77,7 +77,7 @@ public fun ScreenTransition( enterTransition = enterTransition, exitTransition = exitTransition, modifier = modifier, - content = content, + content = content ) } @@ -99,7 +99,7 @@ public fun ScreenTransition( }, modifier = modifier, disposeScreenAfterTransitionEnd = disposeScreenAfterTransitionEnd, - content = content, + content = content ) } @@ -115,7 +115,7 @@ public fun ScreenTransition( transition = transition, modifier = modifier, disposeScreenAfterTransitionEnd = false, - content = content, + content = content ) } @@ -154,7 +154,7 @@ public fun ScreenTransition( ) { screen -> if (this.transition.targetState == this.transition.currentState) { LaunchedEffect(Unit) { - if(disposeScreenAfterTransitionEnd) { + if (disposeScreenAfterTransitionEnd) { // if disposeSteps = true, lastEvent will be always idle // else it will keep the event and we can dispose our self. if (navigator.lastEvent in disposableEvents) { diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/SlideTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/SlideTransition.kt index c336dbb0..13e1d7c8 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/SlideTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/SlideTransition.kt @@ -33,7 +33,7 @@ public fun SlideTransition( orientation = orientation, animationSpec = animationSpec, disposeScreenAfterTransitionEnd = false, - content = content, + content = content ) } diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt index 93c3a323..e51a465c 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt @@ -8,7 +8,7 @@ import androidx.compose.runtime.remember @Composable internal fun rememberRef(): MutableState { return remember { - object: MutableState { + object : MutableState { override var value: T? = null override fun component1(): T? = value @@ -21,7 +21,7 @@ internal fun rememberRef(): MutableState { @Composable internal fun rememberPrevious( current: T, - shouldUpdate: (prev: T?, curr: T) -> Boolean = { a: T?, b: T -> a != b }, + shouldUpdate: (prev: T?, curr: T) -> Boolean = { a: T?, b: T -> a != b } ): T? { val ref = rememberRef() From 9510523bf2ede9350030664219c4600c1ccb442d Mon Sep 17 00:00:00 2001 From: Gabriel Souza Date: Tue, 4 Jun 2024 13:32:39 -0300 Subject: [PATCH 04/10] Disposing solution from #427 to fix animation not ended dispose bug --- .../sample/screenTransition/SampleScreens.kt | 23 +++++- .../ScreenTransitionActivity.kt | 70 +++++++++++++------ .../voyager/transitions/ScreenTransition.kt | 49 +++++++++---- .../transitions/internal/rememberPrevious.kt | 35 ---------- 4 files changed, 109 insertions(+), 68 deletions(-) delete mode 100644 voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleScreens.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleScreens.kt index a19b0da0..ed862748 100644 --- a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleScreens.kt +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleScreens.kt @@ -9,12 +9,15 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.ScreenKey import cafe.adriel.voyager.transitions.ScreenTransition @@ -27,6 +30,18 @@ private val colors = listOf( Color.Black ) +class BaseSampleScreenModel( + val index: Int +) : ScreenModel { + + init { + println("Init BaseSampleScreenModel $index") + } + override fun onDispose() { + println("Disposing BaseSampleScreenModel $index") + } +} + abstract class BaseSampleScreen( private val transitionType: String ) : Screen { @@ -37,10 +52,16 @@ abstract class BaseSampleScreen( @Composable override fun Content() { + val model = rememberScreenModel { + BaseSampleScreenModel(index) + } + val color = remember { + colors.getOrNull(index % colors.size) ?: colors.random() + } Column( modifier = Modifier .fillMaxSize() - .background(colors[index % colors.size].copy(alpha = 0.3f)) + .background(color.copy(alpha = 0.3f)) .padding(40.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/ScreenTransitionActivity.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/ScreenTransitionActivity.kt index 18b3439b..a491b37d 100644 --- a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/ScreenTransitionActivity.kt +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/ScreenTransitionActivity.kt @@ -5,6 +5,7 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -16,7 +17,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import cafe.adriel.voyager.navigator.Navigator +import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior import cafe.adriel.voyager.transitions.ScreenTransition +import kotlin.random.Random class ScreenTransitionActivity : ComponentActivity() { @@ -31,40 +34,67 @@ class ScreenTransitionActivity : ComponentActivity() { @Composable fun Content() { Navigator( - screen = NoCustomAnimationSampleScreen(0) + screen = NoCustomAnimationSampleScreen(0), + disposeBehavior = NavigatorDisposeBehavior(disposeSteps = false) ) { navigator -> Box(modifier = Modifier.fillMaxSize()) { ScreenTransition( navigator = navigator, - defaultTransition = SlideTransition() + defaultTransition = SlideTransition(), + disposeScreenAfterTransitionEnd = true, ) - Row( + Column( modifier = Modifier .align(Alignment.BottomCenter) .fillMaxWidth() - .padding(40.dp), - horizontalArrangement = Arrangement.spacedBy(20.dp) ) { - Button( - onClick = { navigator.push(FadeAnimationSampleScreen(navigator.items.size)) }, - modifier = Modifier.weight(1f) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + horizontalArrangement = Arrangement.spacedBy(20.dp) ) { - Text(text = "Fade") - } + Button( + onClick = { navigator.push(FadeAnimationSampleScreen(navigator.items.size)) }, + modifier = Modifier.weight(1f) + ) { + Text(text = "Fade") + } - Button( - onClick = { navigator.push(NoCustomAnimationSampleScreen(navigator.items.size)) }, - modifier = Modifier.weight(1f) - ) { - Text(text = "Default") - } + Button( + onClick = { navigator.push(NoCustomAnimationSampleScreen(navigator.items.size)) }, + modifier = Modifier.weight(1f) + ) { + Text(text = "Default") + } - Button( - onClick = { navigator.pop() }, - modifier = Modifier.weight(1f) + Button( + onClick = { navigator.pop() }, + modifier = Modifier.weight(1f) + ) { + Text(text = "Pop") + } + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + horizontalArrangement = Arrangement.spacedBy(20.dp) ) { - Text(text = "Pop") + Button( + onClick = { navigator.replace(NoCustomAnimationSampleScreen(Random.nextInt())) }, + modifier = Modifier.weight(1f) + ) { + Text(text = "Replace") + } + + Button( + onClick = { navigator.replaceAll(NoCustomAnimationSampleScreen(Random.nextInt())) }, + modifier = Modifier.weight(1f) + ) { + Text(text = "ReplaceAll") + } } } } diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt index 09304a35..c78f72a3 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt @@ -9,14 +9,18 @@ import androidx.compose.animation.ExitTransition import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.core.screen.ScreenKey import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator -import cafe.adriel.voyager.navigator.internal.disposableEvents -import cafe.adriel.voyager.transitions.internal.rememberPrevious @ExperimentalVoyagerApi public interface ScreenTransition { @@ -129,9 +133,20 @@ public fun ScreenTransition( disposeScreenAfterTransitionEnd: Boolean = false, content: ScreenTransitionContent = { it.Content() } ) { - // This can be costly because is checking every single item in the list - // we should re evaluate how validation works, maybe validating screen keys or === - val previousItems = rememberPrevious(navigator.items) + val screenCandidatesToDispose = rememberSaveable(saver = screenCandidatesToDisposeSaver()) { + mutableStateOf(emptySet()) + } + + val currentScreens = navigator.items + + DisposableEffect(currentScreens) { + onDispose { + val newScreenKeys = navigator.items.map { it.key } + screenCandidatesToDispose.value += currentScreens.filter { it.key !in newScreenKeys } + .map { ScreenData(it.key, it) } + } + } + AnimatedContent( targetState = navigator.lastItem, transitionSpec = { @@ -155,15 +170,13 @@ public fun ScreenTransition( if (this.transition.targetState == this.transition.currentState) { LaunchedEffect(Unit) { if (disposeScreenAfterTransitionEnd) { - // if disposeSteps = true, lastEvent will be always idle - // else it will keep the event and we can dispose our self. - if (navigator.lastEvent in disposableEvents) { - val newScreenKeys = navigator.items.map { it.key } - previousItems?.filter { it.key !in newScreenKeys }?.forEach { - navigator.dispose(it) - } + val newScreens = navigator.items.map { it.key } + val screensToDispose = screenCandidatesToDispose.value.filterNot { it.key in newScreens } + if (screensToDispose.isNotEmpty()) { + screensToDispose.forEach { navigator.dispose(it.screen) } navigator.clearEvent() } + screenCandidatesToDispose.value = emptySet() } } } @@ -173,3 +186,15 @@ public fun ScreenTransition( } } } + +private data class ScreenData( + val key: ScreenKey, + val screen: Screen +) + +private fun screenCandidatesToDisposeSaver(): Saver>, List> { + return Saver( + save = { it.value.toList() }, + restore = { mutableStateOf(it.toSet()) } + ) +} diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt deleted file mode 100644 index e51a465c..00000000 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/internal/rememberPrevious.kt +++ /dev/null @@ -1,35 +0,0 @@ -package cafe.adriel.voyager.transitions.internal - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.remember - -@Composable -internal fun rememberRef(): MutableState { - return remember { - object : MutableState { - override var value: T? = null - - override fun component1(): T? = value - - override fun component2(): (T?) -> Unit = { value = it } - } - } -} - -@Composable -internal fun rememberPrevious( - current: T, - shouldUpdate: (prev: T?, curr: T) -> Boolean = { a: T?, b: T -> a != b } -): T? { - val ref = rememberRef() - - SideEffect { - if (shouldUpdate(ref.value, current)) { - ref.value = current - } - } - - return ref.value -} From 1a1cd186795a828454cc07c187423d88c33c52d2 Mon Sep 17 00:00:00 2001 From: Gabriel Souza Date: Tue, 4 Jun 2024 13:52:26 -0300 Subject: [PATCH 05/10] fix doc workflow --- .github/workflows/docs-publish.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/docs-publish.yaml b/.github/workflows/docs-publish.yaml index 16e36c78..8e22d4b6 100644 --- a/.github/workflows/docs-publish.yaml +++ b/.github/workflows/docs-publish.yaml @@ -1,8 +1,6 @@ name: Docs Publish on: push: - branches: [main, mkdocs] - pull_request: branches: [main] jobs: From 933951f700ab91ff66485a1f6a48a92077bdcbcb Mon Sep 17 00:00:00 2001 From: Gabriel Souza Date: Tue, 4 Jun 2024 13:53:05 -0300 Subject: [PATCH 06/10] fix lint --- .../voyager/sample/screenTransition/ScreenTransitionActivity.kt | 2 +- .../kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/ScreenTransitionActivity.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/ScreenTransitionActivity.kt index a491b37d..e1da2ff6 100644 --- a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/ScreenTransitionActivity.kt +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/ScreenTransitionActivity.kt @@ -41,7 +41,7 @@ class ScreenTransitionActivity : ComponentActivity() { ScreenTransition( navigator = navigator, defaultTransition = SlideTransition(), - disposeScreenAfterTransitionEnd = true, + disposeScreenAfterTransitionEnd = true ) Column( diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt index c78f72a3..0c5028c1 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt @@ -12,9 +12,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi import cafe.adriel.voyager.core.screen.Screen From 21357fe17a799d340d878da09418d1fcd6046d74 Mon Sep 17 00:00:00 2001 From: Gabriel Souza Date: Tue, 4 Jun 2024 14:09:59 -0300 Subject: [PATCH 07/10] update docs --- docs/transitions-api.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/transitions-api.md b/docs/transitions-api.md index c8428e9d..9e05530b 100644 --- a/docs/transitions-api.md +++ b/docs/transitions-api.md @@ -13,6 +13,25 @@ setContent { } ``` +!!! error + There is a know bug using any Transition APIs can leaky ScreenModels or ViewModels, this happens because Voyager by default + dispose Screens in the next Composition tick after a `pop` or `replace` is called, but the transition only finish later, so + the ScreenModel or ViewModel is re created or cleared to early. For this purpose since Voyager `1.1.0-beta02` we have introduce + a new API to fix this issue. For more details on the issue see [#106](https://github.com/adrielcafe/voyager/issues/106). + + ```kotlin + Navigator( + screen = ..., + disposeBehavior = NavigatorDisposeBehavior(disposeSteps = false), + ) { navigator -> + SlideTransition( + navigator = navigator, + ... + disposeScreenAfterTransitionEnd = true + ) + } + ``` + !!! warning Have encounter `Screen was used multiple times` crash? Provide a `uniqueScreenKey` for your Screens From 432f2f3418437942f1f3e991b4a86e3d46f02038 Mon Sep 17 00:00:00 2001 From: Gabriel Souza Date: Tue, 4 Jun 2024 14:17:44 -0300 Subject: [PATCH 08/10] update api dump --- .../voyager/navigator/internal/NavigatorDisposable.kt | 3 +-- .../api/android/voyager-transitions.api | 10 ++++++++++ .../api/desktop/voyager-transitions.api | 10 ++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt index d0959a41..f3ed1451 100644 --- a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt +++ b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt @@ -8,8 +8,7 @@ import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.lifecycle.NavigatorLifecycleStore -@InternalVoyagerApi -public val disposableEvents: Set = +private val disposableEvents: Set = setOf(StackEvent.Pop, StackEvent.Replace) @Composable diff --git a/voyager-transitions/api/android/voyager-transitions.api b/voyager-transitions/api/android/voyager-transitions.api index 0fd293e7..9c8fe82d 100644 --- a/voyager-transitions/api/android/voyager-transitions.api +++ b/voyager-transitions/api/android/voyager-transitions.api @@ -8,15 +8,19 @@ public final class cafe/adriel/voyager/transitions/ComposableSingletons$Crossfad public final class cafe/adriel/voyager/transitions/ComposableSingletons$FadeTransitionKt { public static final field INSTANCE Lcafe/adriel/voyager/transitions/ComposableSingletons$FadeTransitionKt; public static field lambda-1 Lkotlin/jvm/functions/Function4; + public static field lambda-2 Lkotlin/jvm/functions/Function4; public fun ()V public final fun getLambda-1$voyager_transitions_release ()Lkotlin/jvm/functions/Function4; + public final fun getLambda-2$voyager_transitions_release ()Lkotlin/jvm/functions/Function4; } public final class cafe/adriel/voyager/transitions/ComposableSingletons$ScaleTransitionKt { public static final field INSTANCE Lcafe/adriel/voyager/transitions/ComposableSingletons$ScaleTransitionKt; public static field lambda-1 Lkotlin/jvm/functions/Function4; + public static field lambda-2 Lkotlin/jvm/functions/Function4; public fun ()V public final fun getLambda-1$voyager_transitions_release ()Lkotlin/jvm/functions/Function4; + public final fun getLambda-2$voyager_transitions_release ()Lkotlin/jvm/functions/Function4; } public final class cafe/adriel/voyager/transitions/ComposableSingletons$ScreenTransitionKt { @@ -24,17 +28,23 @@ public final class cafe/adriel/voyager/transitions/ComposableSingletons$ScreenTr public static field lambda-1 Lkotlin/jvm/functions/Function4; public static field lambda-2 Lkotlin/jvm/functions/Function4; public static field lambda-3 Lkotlin/jvm/functions/Function4; + public static field lambda-4 Lkotlin/jvm/functions/Function4; + public static field lambda-5 Lkotlin/jvm/functions/Function4; public fun ()V public final fun getLambda-1$voyager_transitions_release ()Lkotlin/jvm/functions/Function4; public final fun getLambda-2$voyager_transitions_release ()Lkotlin/jvm/functions/Function4; public final fun getLambda-3$voyager_transitions_release ()Lkotlin/jvm/functions/Function4; + public final fun getLambda-4$voyager_transitions_release ()Lkotlin/jvm/functions/Function4; + public final fun getLambda-5$voyager_transitions_release ()Lkotlin/jvm/functions/Function4; } public final class cafe/adriel/voyager/transitions/ComposableSingletons$SlideTransitionKt { public static final field INSTANCE Lcafe/adriel/voyager/transitions/ComposableSingletons$SlideTransitionKt; public static field lambda-1 Lkotlin/jvm/functions/Function4; + public static field lambda-2 Lkotlin/jvm/functions/Function4; public fun ()V public final fun getLambda-1$voyager_transitions_release ()Lkotlin/jvm/functions/Function4; + public final fun getLambda-2$voyager_transitions_release ()Lkotlin/jvm/functions/Function4; } public final class cafe/adriel/voyager/transitions/CrossfadeTransitionKt { diff --git a/voyager-transitions/api/desktop/voyager-transitions.api b/voyager-transitions/api/desktop/voyager-transitions.api index 1aacfd2e..f4ecf2b1 100644 --- a/voyager-transitions/api/desktop/voyager-transitions.api +++ b/voyager-transitions/api/desktop/voyager-transitions.api @@ -8,15 +8,19 @@ public final class cafe/adriel/voyager/transitions/ComposableSingletons$Crossfad public final class cafe/adriel/voyager/transitions/ComposableSingletons$FadeTransitionKt { public static final field INSTANCE Lcafe/adriel/voyager/transitions/ComposableSingletons$FadeTransitionKt; public static field lambda-1 Lkotlin/jvm/functions/Function4; + public static field lambda-2 Lkotlin/jvm/functions/Function4; public fun ()V public final fun getLambda-1$voyager_transitions ()Lkotlin/jvm/functions/Function4; + public final fun getLambda-2$voyager_transitions ()Lkotlin/jvm/functions/Function4; } public final class cafe/adriel/voyager/transitions/ComposableSingletons$ScaleTransitionKt { public static final field INSTANCE Lcafe/adriel/voyager/transitions/ComposableSingletons$ScaleTransitionKt; public static field lambda-1 Lkotlin/jvm/functions/Function4; + public static field lambda-2 Lkotlin/jvm/functions/Function4; public fun ()V public final fun getLambda-1$voyager_transitions ()Lkotlin/jvm/functions/Function4; + public final fun getLambda-2$voyager_transitions ()Lkotlin/jvm/functions/Function4; } public final class cafe/adriel/voyager/transitions/ComposableSingletons$ScreenTransitionKt { @@ -24,17 +28,23 @@ public final class cafe/adriel/voyager/transitions/ComposableSingletons$ScreenTr public static field lambda-1 Lkotlin/jvm/functions/Function4; public static field lambda-2 Lkotlin/jvm/functions/Function4; public static field lambda-3 Lkotlin/jvm/functions/Function4; + public static field lambda-4 Lkotlin/jvm/functions/Function4; + public static field lambda-5 Lkotlin/jvm/functions/Function4; public fun ()V public final fun getLambda-1$voyager_transitions ()Lkotlin/jvm/functions/Function4; public final fun getLambda-2$voyager_transitions ()Lkotlin/jvm/functions/Function4; public final fun getLambda-3$voyager_transitions ()Lkotlin/jvm/functions/Function4; + public final fun getLambda-4$voyager_transitions ()Lkotlin/jvm/functions/Function4; + public final fun getLambda-5$voyager_transitions ()Lkotlin/jvm/functions/Function4; } public final class cafe/adriel/voyager/transitions/ComposableSingletons$SlideTransitionKt { public static final field INSTANCE Lcafe/adriel/voyager/transitions/ComposableSingletons$SlideTransitionKt; public static field lambda-1 Lkotlin/jvm/functions/Function4; + public static field lambda-2 Lkotlin/jvm/functions/Function4; public fun ()V public final fun getLambda-1$voyager_transitions ()Lkotlin/jvm/functions/Function4; + public final fun getLambda-2$voyager_transitions ()Lkotlin/jvm/functions/Function4; } public final class cafe/adriel/voyager/transitions/CrossfadeTransitionKt { From 3a64a078c41f18d1aa52e4412d99142daef19a85 Mon Sep 17 00:00:00 2001 From: Gabriel Souza Date: Tue, 4 Jun 2024 14:25:25 -0300 Subject: [PATCH 09/10] fix lint --- .../adriel/voyager/navigator/internal/NavigatorDisposable.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt index f3ed1451..81408884 100644 --- a/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt +++ b/voyager-navigator/src/commonMain/kotlin/cafe/adriel/voyager/navigator/internal/NavigatorDisposable.kt @@ -2,7 +2,6 @@ package cafe.adriel.voyager.navigator.internal import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import cafe.adriel.voyager.core.annotation.InternalVoyagerApi import cafe.adriel.voyager.core.lifecycle.DisposableEffectIgnoringConfiguration import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator From 7eb62ca133ca63dc0f81777442187e2d92336520 Mon Sep 17 00:00:00 2001 From: Gabriel Souza Date: Tue, 4 Jun 2024 19:59:23 -0300 Subject: [PATCH 10/10] addressing reviews --- .../voyager/transitions/ScreenTransition.kt | 35 ++++++++----------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt index 0c5028c1..62af9d53 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt @@ -18,7 +18,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi import cafe.adriel.voyager.core.screen.Screen -import cafe.adriel.voyager.core.screen.ScreenKey import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator @@ -139,11 +138,12 @@ public fun ScreenTransition( val currentScreens = navigator.items - DisposableEffect(currentScreens) { - onDispose { - val newScreenKeys = navigator.items.map { it.key } - screenCandidatesToDispose.value += currentScreens.filter { it.key !in newScreenKeys } - .map { ScreenData(it.key, it) } + if (disposeScreenAfterTransitionEnd) { + DisposableEffect(currentScreens) { + onDispose { + val newScreenKeys = navigator.items.map { it.key } + screenCandidatesToDispose.value += currentScreens.filter { it.key !in newScreenKeys } + } } } @@ -167,17 +167,15 @@ public fun ScreenTransition( }, modifier = modifier ) { screen -> - if (this.transition.targetState == this.transition.currentState) { + if (this.transition.targetState == this.transition.currentState && disposeScreenAfterTransitionEnd) { LaunchedEffect(Unit) { - if (disposeScreenAfterTransitionEnd) { - val newScreens = navigator.items.map { it.key } - val screensToDispose = screenCandidatesToDispose.value.filterNot { it.key in newScreens } - if (screensToDispose.isNotEmpty()) { - screensToDispose.forEach { navigator.dispose(it.screen) } - navigator.clearEvent() - } - screenCandidatesToDispose.value = emptySet() + val newScreens = navigator.items.map { it.key } + val screensToDispose = screenCandidatesToDispose.value.filterNot { it.key in newScreens } + if (screensToDispose.isNotEmpty()) { + screensToDispose.forEach { navigator.dispose(it) } + navigator.clearEvent() } + screenCandidatesToDispose.value = emptySet() } } @@ -187,12 +185,7 @@ public fun ScreenTransition( } } -private data class ScreenData( - val key: ScreenKey, - val screen: Screen -) - -private fun screenCandidatesToDisposeSaver(): Saver>, List> { +private fun screenCandidatesToDisposeSaver(): Saver>, List> { return Saver( save = { it.value.toList() }, restore = { mutableStateOf(it.toSet()) }