Skip to content

Commit

Permalink
Dispose Screens only when transition ends
Browse files Browse the repository at this point in the history
  • Loading branch information
DevSrSouza committed Jun 4, 2024
1 parent 486497c commit fa93d7c
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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<StackEvent> =
@InternalVoyagerApi
public val disposableEvents: Set<StackEvent> =
setOf(StackEvent.Pop, StackEvent.Replace)

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Screen>.() -> 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 = {
Expand All @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <T> rememberRef(): MutableState<T?> {
// 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<T?> {
override var value: T? = null

override fun component1(): T? = value

override fun component2(): (T?) -> Unit = { value = it }
}
}
}

@Composable
internal fun <T> rememberPrevious(
current: T,
shouldUpdate: (prev: T?, curr: T) -> Boolean = { a: T?, b: T -> a != b },
): T? {
val ref = rememberRef<T>()

// 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
}

0 comments on commit fa93d7c

Please sign in to comment.