Skip to content

Commit

Permalink
feat: added support to have navigator scoped screen model
Browse files Browse the repository at this point in the history
  • Loading branch information
Thiago dos Santos authored and Thiago dos Santos committed Sep 23, 2023
1 parent 5e1cd06 commit 1e3bfb0
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ public inline fun <reified T : ScreenModel> Screen.rememberScreenModel(
ScreenModelStore.getOrPut(this, tag, factory)
}

public inline fun <reified T : ScreenModel> Screen.getScreenModel(
tag: String? = null
): T? = ScreenModelStore.getOrNull(this, tag)

public interface ScreenModel {

public fun onDispose() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ public object ScreenModelStore {
return screenModels.getOrPut(key, factory) as T
}

@PublishedApi
internal inline fun <reified T : ScreenModel> getOrNull(
screen: Screen,
tag: String?
): T? {
val key = getKey<T>(screen, tag)
return screenModels[key] as? T
}

public inline fun <reified T : Any> getOrPutDependency(
screenModel: ScreenModel,
name: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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
import cafe.adriel.voyager.navigator.screen.NavigatorScreen

private val disposableEvents: Set<StackEvent> =
setOf(StackEvent.Pop, StackEvent.Replace)
Expand Down Expand Up @@ -77,6 +78,7 @@ internal fun disposeNavigator(navigator: Navigator) {
for (screen in navigator.items) {
navigator.dispose(screen)
}
navigator.dispose(NavigatorScreen(navigator))
NavigatorLifecycleStore.remove(navigator)
navigator.clearEvent()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package cafe.adriel.voyager.navigator.lifecycle

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisallowComposableCalls
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.getScreenModel
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow
import cafe.adriel.voyager.navigator.screen.NavigatorScreen

/**
* Lookup for a [ScreenModel] that was remembered in the current [LocalNavigator] or in your parents.
* If an instance was not found, an [IllegalStateException] will be thrown
*
* @param tag A custom tag used to "tag" the [ScreenModel]
* @throws IllegalStateException if now [ScreenModel] was found in [LocalNavigator] or in your parents
*
* @return The [ScreenModel] instance found
*/
@Suppress("UnusedReceiverParameter")
@Throws(IllegalStateException::class)
@Composable
public inline fun <reified T : ScreenModel> Screen.navigatorScreenModel(
tag: String? = null
): T {
val navigator = LocalNavigator.currentOrThrow
var screenModel: T? by remember(tag) { mutableStateOf(null) }
if (screenModel == null) {
screenModel = generateSequence(seed = navigator) { it.parent }
.mapNotNull { nav -> emptyScreen(nav).getScreenModel(tag) as? T }
.firstOrNull()
}
return checkNotNull(screenModel) {
"${T::class} was not found in $navigator and it parents"
}
}

/**
* Remember a [ScreenModel] that will be scoped to current [LocalNavigator]
*
* @param tag A custom tag used to "tag" the [ScreenModel]
* @param factory A function to create a new instance if one is not remembered yet
*
* @return The [ScreenModel] instance
*/
@Suppress("UnusedReceiverParameter")
@Composable
public inline fun <reified T : ScreenModel> Screen.rememberNavigatorScreenModel(
tag: String? = null,
crossinline factory: @DisallowComposableCalls () -> T
): T {
val navigator = LocalNavigator.currentOrThrow
val screen = emptyScreen(navigator)
return screen.rememberScreenModel(tag, factory)
}

public fun emptyScreen(navigator: Navigator): Screen = NavigatorScreen(navigator)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package cafe.adriel.voyager.navigator.screen

import androidx.compose.runtime.Composable
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.screen.ScreenKey
import cafe.adriel.voyager.navigator.Navigator

/**
* An internal screen to be used as "key" to persist a [cafe.adriel.voyager.core.model.ScreenModel]
*
* @param navigator The current [Navigator] to be scoped
*/
internal data class NavigatorScreen(
private val navigator: Navigator
) : Screen {

override val key: ScreenKey = "$navigator"

@Composable
override fun Content() {
error("Calling NavigatorScreen Content() fun is an error")
}
}

0 comments on commit 1e3bfb0

Please sign in to comment.