Skip to content

Commit

Permalink
feat: new experimental NavigatorScreenLifecycle API
Browse files Browse the repository at this point in the history
Now, AndroidScreen behavior of AndroidScreenLifecycleOwner is available by default in any Screen for Android targets, this means that no longer will required AndroidScreen to proper Android lifecycle support.
  • Loading branch information
DevSrSouza committed May 1, 2023
1 parent ef603b2 commit 33e28d2
Show file tree
Hide file tree
Showing 14 changed files with 168 additions and 3 deletions.
2 changes: 2 additions & 0 deletions buildSrc/src/main/kotlin/Setup.kt
Expand Up @@ -79,6 +79,7 @@ fun Project.setupModuleForComposeMultiplatform(
sourceSets {
all {
languageSettings.optIn("cafe.adriel.voyager.core.annotation.InternalVoyagerApi")
languageSettings.optIn("cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi")
}
}

Expand Down Expand Up @@ -166,6 +167,7 @@ private fun KotlinJvmOptions.configureKotlinJvmOptions(

if (enableExplicitMode) freeCompilerArgs += "-Xexplicit-api=strict"
freeCompilerArgs += "-opt-in=cafe.adriel.voyager.core.annotation.InternalVoyagerApi"
freeCompilerArgs += "-opt-in=cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi"
}

private fun Project.findAndroidExtension(): BaseExtension = extensions.findByType<LibraryExtension>()
Expand Down
@@ -1,5 +1,6 @@
package cafe.adriel.voyager.androidx

import cafe.adriel.voyager.core.lifecycle.DefaultScreenLifecycleOwner
import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleOwner
import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleProvider
import cafe.adriel.voyager.core.screen.Screen
Expand All @@ -10,5 +11,5 @@ public abstract class AndroidScreen : Screen, ScreenLifecycleProvider {

override val key: ScreenKey = uniqueScreenKey

override fun getLifecycleOwner(): ScreenLifecycleOwner = AndroidScreenLifecycleOwner.get(this)
override fun getLifecycleOwner(): ScreenLifecycleOwner = DefaultScreenLifecycleOwner
}
5 changes: 5 additions & 0 deletions voyager-core/build.gradle.kts
Expand Up @@ -29,6 +29,11 @@ kotlin {
val androidMain by getting {
dependencies {
implementation(libs.compose.activity)

implementation(libs.lifecycle.runtime)
implementation(libs.lifecycle.savedState)
implementation(libs.lifecycle.viewModelKtx)
implementation(libs.lifecycle.viewModelCompose)
}
}
val jsMain by getting {
Expand Down
@@ -0,0 +1,9 @@
package cafe.adriel.voyager.core.lifecycle

import cafe.adriel.voyager.androidx.AndroidScreenLifecycleOwner
import cafe.adriel.voyager.core.screen.Screen

internal actual class DefaultNavigatorScreenLifecycleProvider : NavigatorScreenLifecycleProvider {
actual override fun provide(screen: Screen): List<ScreenLifecycleOwner> =
listOf(AndroidScreenLifecycleOwner.get(screen))
}
Expand Up @@ -14,3 +14,18 @@ package cafe.adriel.voyager.core.annotation
message = "This API is Voyager Internal. It may be changed in the future without notice."
)
public annotation class InternalVoyagerApi

@Target(
allowedTargets = [
AnnotationTarget.CLASS,
AnnotationTarget.PROPERTY,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.CONSTRUCTOR,
]
)
@RequiresOptIn(
level = RequiresOptIn.Level.ERROR,
message = "This API is experimental. It may be changed in the future without notice."
)
public annotation class ExperimentalVoyagerApi
@@ -0,0 +1,25 @@
package cafe.adriel.voyager.core.lifecycle

import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.staticCompositionLocalOf
import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi
import cafe.adriel.voyager.core.screen.Screen

@ExperimentalVoyagerApi
public val LocalNavigatorScreenLifecycleProvider: ProvidableCompositionLocal<NavigatorScreenLifecycleProvider> =
staticCompositionLocalOf { DefaultNavigatorScreenLifecycleProvider() }

/**
* Can provides a list of ScreenLifecycleOwner for each Screen in the Navigator stack.
*/
@ExperimentalVoyagerApi
public interface NavigatorScreenLifecycleProvider {

@ExperimentalVoyagerApi
public fun provide(screen: Screen): List<ScreenLifecycleOwner>
}

internal expect class DefaultNavigatorScreenLifecycleProvider() : NavigatorScreenLifecycleProvider{
override fun provide(screen: Screen): List<ScreenLifecycleOwner>
}

Expand Up @@ -3,6 +3,8 @@ package cafe.adriel.voyager.core.lifecycle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi
import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
import cafe.adriel.voyager.core.screen.Screen

@Composable
Expand All @@ -27,6 +29,16 @@ public fun rememberScreenLifecycleOwner(
}
}

@Composable
@ExperimentalVoyagerApi
@InternalVoyagerApi
public fun getNavigatorScreenLifecycleOwner(screen: Screen): List<ScreenLifecycleOwner> {
val navigatorScreenLifecycleProvider = LocalNavigatorScreenLifecycleProvider.current
return remember(screen.key) {
navigatorScreenLifecycleProvider.provide(screen)
}
}

public interface ScreenLifecycleProvider {

public fun getLifecycleOwner(): ScreenLifecycleOwner
Expand Down
@@ -1,6 +1,9 @@
package cafe.adriel.voyager.core.lifecycle

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi
import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
import cafe.adriel.voyager.core.screen.Screen

public interface ScreenLifecycleOwner {
Expand All @@ -13,4 +16,72 @@ public interface ScreenLifecycleOwner {
public fun onDispose(screen: Screen) {}
}

internal object DefaultScreenLifecycleOwner : ScreenLifecycleOwner
@InternalVoyagerApi
public object DefaultScreenLifecycleOwner : ScreenLifecycleOwner

@ExperimentalVoyagerApi
@InternalVoyagerApi
public class ComposedScreenLifecycleOwner(
private val screenLifecycleOwners: List<ScreenLifecycleOwner>
) : ScreenLifecycleOwner {

@Composable
public fun RecursiveProvideBeforeScreenContent(
screenLifecycleOwner: ScreenLifecycleOwner,
provideSaveableState: @Composable (suffixKey: String, content: @Composable () -> Unit) -> Unit,
content: @Composable () -> Unit,
nextOrNull: () -> ScreenLifecycleOwner?,
) {
val next = remember(screenLifecycleOwner, provideSaveableState, content, nextOrNull) { nextOrNull() }
if(next != null) {
val recursiveContent = @Composable {
RecursiveProvideBeforeScreenContent(
screenLifecycleOwner = next,
provideSaveableState = provideSaveableState,
content = content,
nextOrNull = nextOrNull,
)
}
screenLifecycleOwner.ProvideBeforeScreenContent(
provideSaveableState = { suffixKey, _ ->
provideSaveableState(suffixKey, recursiveContent)
}
) {
recursiveContent()
}

} else {
screenLifecycleOwner.ProvideBeforeScreenContent(
provideSaveableState = { suffixKey, content ->
provideSaveableState(suffixKey, content)
}
) {
content()
}
}
}

@Composable
override fun ProvideBeforeScreenContent(
provideSaveableState: @Composable (suffixKey: String, content: @Composable () -> Unit) -> Unit,
content: @Composable () -> Unit
) {
if(screenLifecycleOwners.isNotEmpty()) {
val copy = screenLifecycleOwners.toMutableList()
RecursiveProvideBeforeScreenContent(
screenLifecycleOwner = copy.removeFirst(),
provideSaveableState = provideSaveableState,
content = content,
nextOrNull = { copy.removeFirstOrNull() }
)
} else {
content()
}
}

override fun onDispose(screen: Screen) {
for (screenLifecycleOwner in screenLifecycleOwners) {
screenLifecycleOwner.onDispose(screen)
}
}
}
@@ -0,0 +1,6 @@
package cafe.adriel.voyager.core.lifecycle

import cafe.adriel.voyager.core.screen.Screen
internal actual class DefaultNavigatorScreenLifecycleProvider : NavigatorScreenLifecycleProvider {
actual override fun provide(screen: Screen): List<ScreenLifecycleOwner> = emptyList()
}
@@ -0,0 +1,6 @@
package cafe.adriel.voyager.core.lifecycle

import cafe.adriel.voyager.core.screen.Screen
internal actual class DefaultNavigatorScreenLifecycleProvider : NavigatorScreenLifecycleProvider {
actual override fun provide(screen: Screen): List<ScreenLifecycleOwner> = emptyList()
}
@@ -0,0 +1,6 @@
package cafe.adriel.voyager.core.lifecycle

import cafe.adriel.voyager.core.screen.Screen
internal actual class DefaultNavigatorScreenLifecycleProvider : NavigatorScreenLifecycleProvider {
actual override fun provide(screen: Screen): List<ScreenLifecycleOwner> = emptyList()
}
Expand Up @@ -10,7 +10,9 @@ import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.compose.runtime.staticCompositionLocalOf
import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
import cafe.adriel.voyager.core.concurrent.ThreadSafeSet
import cafe.adriel.voyager.core.lifecycle.ComposedScreenLifecycleOwner
import cafe.adriel.voyager.core.lifecycle.ScreenLifecycleStore
import cafe.adriel.voyager.core.lifecycle.getNavigatorScreenLifecycleOwner
import cafe.adriel.voyager.core.lifecycle.rememberScreenLifecycleOwner
import cafe.adriel.voyager.core.model.ScreenModelStore
import cafe.adriel.voyager.core.screen.Screen
Expand Down Expand Up @@ -131,7 +133,10 @@ public class Navigator @InternalVoyagerApi constructor(
}

val lifecycleOwner = rememberScreenLifecycleOwner(screen)
lifecycleOwner.ProvideBeforeScreenContent(
val navigatorScreenLifecycleOwners = getNavigatorScreenLifecycleOwner(screen)

val composed = ComposedScreenLifecycleOwner(listOf(lifecycleOwner) + navigatorScreenLifecycleOwners)
composed.ProvideBeforeScreenContent(
provideSaveableState = { suffix, content -> provideSaveableState(suffix, content) },
content = {
stateHolder.SaveableStateProvider(stateKey, content)
Expand Down
Expand Up @@ -5,11 +5,13 @@ import androidx.compose.runtime.saveable.SaveableStateHolder
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.staticCompositionLocalOf
import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi
import cafe.adriel.voyager.core.screen.Screen

public val LocalNavigatorSaver: ProvidableCompositionLocal<NavigatorSaver<*>> =
staticCompositionLocalOf { defaultNavigatorSaver() }

@ExperimentalVoyagerApi
public fun interface NavigatorSaver<Saveable : Any> {
public fun saver(
initialScreens: List<Screen>,
Expand Down

0 comments on commit 33e28d2

Please sign in to comment.