diff --git a/search/search_index.json b/search/search_index.json index a08cdf12..cc571fa8 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Overview","text":""},{"location":"#voyager-compose-on-warp-speed","title":"Voyager: Compose on Warp Speed","text":"

A multiplatform navigation library built for, and seamlessly integrated with, Jetpack Compose.

Create scalable Single-Activity apps powered by a pragmatic API:

class HomeScreenModel : ScreenModel {\n    // ...\n}\n\nclass HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = rememberScreenModel { HomeScreenModel() }\n        // ...\n    }\n}\n\nclass SingleActivity : ComponentActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        setContent {\n            Navigator(HomeScreen())\n        }\n    }\n}\n

Turn on the Warp Drive and enjoy the voyage \ud83d\udd96

"},{"location":"#features","title":"Features","text":""},{"location":"#credits","title":"Credits","text":""},{"location":"back-press/","title":"Back press","text":"

By default, Voyager will handle back presses but you can override its behavior. Use the onBackPressed to manually handle it: return true to pop the current screen, or false otherwise. To disable, just set to null.

setContent {\n    Navigator(\n        initialScreen = HomeScreen,\n        onBackPressed = { currentScreen ->\n            false // won't pop the current screen\n            // true will pop, default behavior\n        }\n        // To disable:\n        // onBackPressed = null\n    )\n}\n
"},{"location":"community-projects/","title":"Community Projects Catalog","text":""},{"location":"community-projects/#open-source-libraries-with-voyager-extensions","title":"Open source libraries with Voyager extensions","text":""},{"location":"community-projects/#open-source-projects-using-voyager","title":"Open source projects using Voyager","text":""},{"location":"community-projects/#talks-and-tutorials","title":"Talks and Tutorials","text":""},{"location":"community-projects/#talks","title":"Talks","text":""},{"location":"community-projects/#tutorials","title":"Tutorials","text":""},{"location":"community-projects/#community-snippetsextensions","title":"Community snippets/extensions","text":""},{"location":"deep-links/","title":"Deep links","text":"

Warning

Currently Voyager does not provided a built in solution to handle Deeplink and URIs. see #149 and #382

You can initialize the Navigator with multiple screens, that way, the first visible screen will be the last one and will be possible to return (pop()) to the previous screens.

val postId = getPostIdFromIntent()\n\nsetContent {\n    Navigator(\n        HomeScreen,\n        PostListScreen(),\n        PostDetailsScreen(postId)\n    )\n}\n
"},{"location":"faq/","title":"Faq","text":"

iOS Swipe Back support { #ios-swipeback }

Voyager does not have a built in support for swipe back yet, we are not 100% conformable with all solutions that have out there and we think we will better of using a community made solution by copying to your code base and be able to change as you want your app to behave.

See this issue each for community build solutions.

Alternatively, we can also discuss in the future a community build solution using NavigationController under the hood like compose-cupertino have implemented for Decompose.

Support for predictive back animations { #predictive-back }

Voyager does not have a built in support for predictive back yet, but as well as iOS Swipe Back, the community have build extensions, and snippets with support, see #223 and 144.

Support for result passing between screens { #result-passing }

Voyager does not have a built in support for swipe back yet, we are not 100% conformable with all solutions that have out there, we encourage to use community made solutions by copying to your code base and being free to extend as your code base needs.

See #128 comments for community solutions.

Deeplink support

"},{"location":"lifecycle/","title":"Lifecycle","text":"

** Experimental API

Inside a Screen, you can call LifecycleEffectOnce to execute a block of code the first time the Screen appears, and optionally define a onDispose callback for when the Screen is leaving/removed from the Stack:

class PostListScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        LifecycleEffectOnce {\n            screenModel.initSomething()\n            onDispose {\n                // Do something when the screen is leaving/removed from the Stack\n            }\n        }\n\n        // ...\n    }\n}\n
"},{"location":"lifecycle/#screenlifecycleowner","title":"ScreenLifecycleOwner","text":"

With ScreenLifecycleProvider interface you can provide a special ScreenLifecycleOwner that can react when the Screen leaves the Stack

class MyScreenLifecycleOwner : ScreenLifecycleOwner {\n    override fun onDispose(screen: Screen) {\n        println(\"My ${screen.key} is being disposed\")\n    }\n}\n\n\ndata object MyScreen : Screen, ScreenLifecycleProvider {\n   @Composable\n   fun Content() {\n      ...\n   }\n\n   public override fun getLifecycleOwner(): ScreenLifecycleOwner =\n       ScreenLifecycleOwner()\n}\n
"},{"location":"lifecycle/#extending-the-lifecycle-lazily","title":"Extending the Lifecycle lazily","text":"

You can access directly the ScreenLifecycleStore to add a ScreenDisposable implementation lazily in a lifecycle aware custom API.

If you for example have a custom dependency that should be notified when the Screen is disposed you can use the ScreenLifecycleStore directly. For example, the Screen Model and ScreenModelStore APIs rely on this API to Store the ScreenModel instance and the Screen owns the ScreenModel instance, when the Screen leaves the Stack, the ScreenModelStore, that implements ScreenDisposable is notified and can dispose the ScreenModel.

Let\u2019s imagine a dependency called MyDependency that holds and provides a State for a Screen while it is on the Navigator stack and we want to notify MyDependency when the Screen leaves the Stack.

class MyCustomAPIWithDisposable(\n   private val myDependency: MyDependency\n) : ScreenDisposable {\n   public override fun onDispose(screen: Screen) {\n      myDependency.disposeStateForScreen(screen)\n   }\n}\n\n@Composable\nfun rememberMyDependency(): MyDependency {\n   val navigator = LocalNavigator.currentOrThrow\n   val myDependency by getMyDependecy() // getting from DI\n   remember(myDependency) {\n      myDependency.provideStateForScreen(navigator.lastItem)\n      ScreenLifecycleStore.get(navigator.lastItem) {\n         MyCustomAPIWithDisposable(myDependency)\n      }\n   }\n\n   return myDependency\n}\n
"},{"location":"lifecycle/#screendisposable-for-all-screens","title":"ScreenDisposable for all Screens","text":"

You can also provide a ScreenLifecycleOwner for all Screen in the stack of a Navigator easily by in the Navigator composable start to listen to lastItem for registering at ScreenLifecycleStore.

Let\u2019s reuse the MyScreenLifecycleOwner from the example above and provide it to all screens in the navigator.

class MyScreenLifecycleOwner : ScreenDisposable {\n    override fun onDispose(screen: Screen) {\n        println(\"My ${screen.key} is being disposed\")\n    }\n}\n\n@Composable\nfun YourAppComposable() {\n  Navigator(...) { navigator ->\n     remember(navigator.lastItem) {\n        ScreenLifecycleStore.get(navigator.lastItem) {\n           MyScreenLifecycleOwner()\n        }\n     }\n     CurrentScreen()\n  }\n}\n
"},{"location":"migration-to-1.0.0/","title":"Migration to 1.0.0","text":""},{"location":"migration-to-1.0.0/#screenmodel","title":"ScreenModel","text":""},{"location":"migration-to-1.0.0/#new-module","title":"New Module","text":"

Now the ScreenModel API has its own module and no longer are available in voyager-core, if you are using ScreenModel you should declare the dependency cafe.adriel.voyager:voyager-screenmodel (see Setup).

"},{"location":"migration-to-1.0.0/#navigation-level-screenmodel","title":"Navigation level ScreenModel","text":"

Since 1.0.0-rc08 we have introduced the ScreenModel scoped at Navigator Lifecycle, now the API is no longer marked as Experimental.

"},{"location":"migration-to-1.0.0/#deprecation-cycle","title":"Deprecation cycle","text":"

Since 1.0.0-rc08 we have renamed the extension coroutineScope to screenModelScope, now it was removed from 1.0.0, if you are still using it, just replace with screenModelScope.

"},{"location":"migration-to-1.0.0/#androidscreen","title":"AndroidScreen","text":"

The module voyager-androidx and AndroidScreen was removed! Since 1.0.0-rc06 we have introduced a new API called NavigatorScreenLifecycleProvider that provides by default the previous behavior of AndroidScreenLifecycleOwner on Android target for all Screen.

Important notice: AndroidScreen, different from Screen, it holds the Screen.key as a uniqueScreenKey, this is a pretty common requirement, to avoid issues and weird behaviors, we recommend continuing to specify a uniqueScreenKey if you are not, we also recommend creating a abstract class UniqueScreen to replace your AndroidScreen implementation.

abstract class UniqueScreen : Screen {\n    override val key: ScreenKey = uniqueScreenKey\n}\n
"},{"location":"migration-to-1.0.0/#apis-promote-to-stable","title":"APIs promote to Stable","text":""},{"location":"migration-to-1.0.0/#deprecation-cycle_1","title":"Deprecation cycle","text":""},{"location":"setup/","title":"Setup","text":"
  1. Add Maven Central to your repositories if needed

    repositories {\n    mavenCentral()\n}\n
    2. Add the desired dependencies to your module\u2019s build.gradle file

DependenciesVersion Catalog
dependencies {\n    val voyagerVersion = \"1.1.0-beta02\"\n\n    // Multiplatform\n\n    // Navigator\n    implementation(\"cafe.adriel.voyager:voyager-navigator:$voyagerVersion\")\n\n    // Screen Model\n    implementation(\"cafe.adriel.voyager:voyager-screenmodel:$voyagerVersion\")\n\n    // BottomSheetNavigator\n    implementation(\"cafe.adriel.voyager:voyager-bottom-sheet-navigator:$voyagerVersion\")\n\n    // TabNavigator\n    implementation(\"cafe.adriel.voyager:voyager-tab-navigator:$voyagerVersion\")\n\n    // Transitions\n    implementation(\"cafe.adriel.voyager:voyager-transitions:$voyagerVersion\")\n\n    // Android\n\n    // Koin integration\n    implementation(\"cafe.adriel.voyager:voyager-koin:$voyagerVersion\")\n\n    // Hilt integration\n    implementation(\"cafe.adriel.voyager:voyager-hilt:$voyagerVersion\")\n\n    // LiveData integration\n    implementation(\"cafe.adriel.voyager:voyager-livedata:$voyagerVersion\")\n\n    // Desktop + Android\n\n    // Kodein integration\n    implementation(\"cafe.adriel.voyager:voyager-kodein:$voyagerVersion\")\n\n    // RxJava integration\n    implementation(\"cafe.adriel.voyager:voyager-rxjava:$voyagerVersion\")\n}\n
[versions]\nvoyager = \"1.1.0-beta02\"\n\n[libraries]\nvoyager-navigator = { module = \"cafe.adriel.voyager:voyager-navigator\", version.ref = \"voyager\" }\nvoyager-screenModel = { module = \"cafe.adriel.voyager:voyager-screenmodel\", version.ref = \"voyager\" }\nvoyager-bottomSheetNavigator = { module = \"cafe.adriel.voyager:voyager-bottom-sheet-navigator\", version.ref = \"voyager\" }\nvoyager-tabNavigator = { module = \"cafe.adriel.voyager:voyager-tab-navigator\", version.ref = \"voyager\" }\nvoyager-transitions = { module = \"cafe.adriel.voyager:voyager-transitions\", version.ref = \"voyager\" }\nvoyager-koin = { module = \"cafe.adriel.voyager:voyager-koin\", version.ref = \"voyager\" }\nvoyager-hilt = { module = \"cafe.adriel.voyager:voyager-hilt\", version.ref = \"voyager\" }\nvoyager-kodein = { module = \"cafe.adriel.voyager:voyager-kodein\", version.ref = \"voyager\" }\nvoyager-rxjava = { module = \"cafe.adriel.voyager:voyager-rxjava\", version.ref = \"voyager\" }\n

Current version here.

"},{"location":"setup/#platform-compatibility","title":"Platform compatibility","text":"

Multiplatform targets: Android, iOS, Desktop, Mac Native, Web.

Android Desktop Multiplatform voyager-navigator voyager-screenModel voyager-bottom-sheet-navigator voyager-tab-navigator voyager-transitions voyager-koin voyager-kodein voyager-lifecycle-kmp voyager-hilt voyager-rxjava voyager-livedata"},{"location":"stack-api/","title":"Stack API","text":""},{"location":"stack-api/#snapshotstatestack","title":"SnapshotStateStack","text":"

Voyager is backed by a SnapshotStateStack:

You will use it to navigate forward (push, replace, replaceAll) and backwards (pop, popAll, popUntil), but the SnapshotStateStack can also be used as a regular collection outside the Navigator.

val stack = mutableStateStackOf(\"\ud83c\udf47\", \"\ud83c\udf49\", \"\ud83c\udf4c\", \"\ud83c\udf50\", \"\ud83e\udd5d\", \"\ud83c\udf4b\")\n// \ud83c\udf47, \ud83c\udf49, \ud83c\udf4c, \ud83c\udf50, \ud83e\udd5d, \ud83c\udf4b\n\nstack.lastItemOrNull\n// \ud83c\udf4b\n\nstack.push(\"\ud83c\udf4d\")\n// \ud83c\udf47, \ud83c\udf49, \ud83c\udf4c, \ud83c\udf50, \ud83e\udd5d, \ud83c\udf4b, \ud83c\udf4d\n\nstack.pop()\n// \ud83c\udf47, \ud83c\udf49, \ud83c\udf4c, \ud83c\udf50, \ud83e\udd5d, \ud83c\udf4b\n\nstack.popUntil { it == \"\ud83c\udf50\" }\n// \ud83c\udf47, \ud83c\udf49, \ud83c\udf4c, \ud83c\udf50\n\nstack.replace(\"\ud83c\udf53\")\n// \ud83c\udf47, \ud83c\udf49, \ud83c\udf4c, \ud83c\udf53\n\nstack.replaceAll(\"\ud83c\udf52\")\n// \ud83c\udf52\n

You can also create a SnapshotStateStack through rememberStateStack(), it will restore the values after Activity recreation.

"},{"location":"stack-api/#events","title":"Events","text":"

Whenever the content changes, the SnapshotStateStack will emit a StackEvent. Use the stack.lastEvent to get the most recent one.

The available events are:

This is very useful for deciding which transition to make.

"},{"location":"stack-api/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"state-restoration/","title":"State restoration","text":"

Voyager by default expect that it screens can be stored inside a Bundle. This means both Java Serializable and Parcelable are supported. By default all Voyager Screen is Java Serializable this means that Screen can be restored if all parameters are Java Serializable.

"},{"location":"state-restoration/#java-serializable","title":"Java Serializable","text":"
// \u2714\ufe0f DO\ndata class Post(/*...*/) : Serializable\n\ndata class ValidScreen(\n    val userId: UUID, // Built-in serializable types\n    val post: Post // Your own serializable types\n) : Screen {\n    // ...\n}\n\n// \ud83d\udeab DON'T\nclass Post(/*...*/)\n\ndata class InvalidScreen(\n    val context: Context, // Built-in non-serializable types\n    val post: Post, // Your own non-serializable types\n    val parcelable: SomeParcelable // Android Parcelable is not Java Serializable by default\n) : Screen {\n    // ...\n}\n

Not only the params, but the properties will also be restored, so the same rule applies.

// \u2714\ufe0f DO\nclass ValidScreen : Screen {\n\n    // Serializable properties\n    val tag = \"ValidScreen\"\n\n    // Lazily initialized serializable types\n    val randomId by lazy { UUID.randomUUID() }\n}\n\n// \ud83d\udeab DON'T\nclass InvalidScreen : Screen {\n\n    // Non-serializable properties\n    val postService = PostService()\n}\n
"},{"location":"state-restoration/#android-parcelables","title":"Android Parcelables","text":"
// \u2714\ufe0f DO\n@Parcelize\ndata class Post(/*...*/) : Parcelable\n\n@Parcelize\ndata class ValidScreen(\n    val post: Post // Your own parcelable types\n) : Screen, Parcelable {\n    // ...\n}\n\n// \ud83d\udeab DON'T\nclass Post(/*...*/)\n\n@Parcelize\ndata class InvalidScreen(\n    val context: Context, // Built-in non-parcelable types\n    val post: Post, // Your own non-parcelable types\n    val serializable: SomeSerializable // Java Serializable are not Android Parcelable by default\n) : Screen, Parcelable {\n    // ...\n}\n
"},{"location":"state-restoration/#enforcing-android-parcelable-on-your-screens","title":"Enforcing Android Parcelable on your screens","text":"

You can build your own Screen type for enforcing in at compile time that all yours screens should be Parcelable by creating an interface that implement Parcelable.

interface ParcelableScreen : Screen, Parcelable\n\n// Compile\n@Parcelize\ndata class Post(/*...*/) : Parcelable\n\n@Parcelize\ndata class ValidScreen(\n    val post: Post\n) : ParcelableScreen {\n    // ...\n}\n\n// Not compile\ndata class Post(/*...*/)\n\n@Parcelize\ndata class ValidScreen(\n    val post: Post\n) : ParcelableScreen {\n    // ...\n}\n

Starting from version 1.0.0-rc05 you can specify a custom NavigatorSaver to enforce that all Screen is Parcelable by using parcelableNavigatorSaver.

CompositionLocalProvider(\n    LocalNavigatorSaver provides parcelableNavigatorSaver()\n) {\n    Navigator(...) {\n       ...\n    }\n}\n
"},{"location":"state-restoration/#multiplatform-state-restoration","title":"Multiplatform state restoration","text":"

When working in a Multiplatform project and sharing the Parameters models with other platforms, your types required to be serializable in a Bundle if you are targeting Android, the easiest way is defining in common code a JavaSerializable interface that on Android only would implement java.io.Serialiable, see example below.

// commonMain - module core\nexpect interface JavaSerializable\n\ndata class Post(/*...*/) : JavaSerializable\n\n// androidMain - module core\nactual typealias JavaSerializable = java.io.Serializable\n\n// non AndroidMain (ios, web, etc) - module core\nactual interface JavaSerializable\n\n// android ui module or compose multiplatform module\ndata class ValidScreen(\n    val post: Post\n) : Screen\n
"},{"location":"state-restoration/#dependency-injection","title":"Dependency Injection","text":"

If you want to inject dependencies through a DI framework, make sure it supports Compose, like Koin and Kodein.

// \u2714\ufe0f DO\nclass ValidScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        // Inject your dependencies inside composables\n        val postService = get<PostService>()\n    }\n}\n\n// \ud83d\udeab DON'T\nclass InvalidScreen : Screen {\n\n    // Using DI to inject non-serializable types as properties\n    val postService by inject<PostService>()\n}\n
"},{"location":"state-restoration/#identifying-screens","title":"Identifying screens","text":"

The Screen interface has a key property used for saving and restoring the states for the subtree. You can override the default value to set your own key.

class HomeScreen : Screen {\n\n    override val key = \"CUSTOM_KEY\"\n\n    @Composable\n    override fun Content() {\n        // ...\n    }\n}\n

Voyager provides a uniqueScreenKey property, useful if you don\u2019t want to manage the keys yourself.

override val key = uniqueScreenKey\n

Warning

You should always set your own key if the screen:

"},{"location":"transitions-api/","title":"Transitions","text":"

To use the transitions you should first import cafe.adriel.voyager:voyager-transitions (see Setup).

Voyager has built-in transitions! When initializing the Navigator you can override the default content and use, for example, the SlideTransition.

setContent {\n    Navigator(HomeScreen) { navigator ->\n        SlideTransition(navigator)\n    }\n}\n

Known bug

There is a known 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.

Navigator(\n    screen = ...,\n    disposeBehavior = NavigatorDisposeBehavior(disposeSteps = false),\n) { navigator ->\n    SlideTransition(\n        navigator = navigator,\n        ...\n        disposeScreenAfterTransitionEnd = true\n    )\n}\n

Warning

Have encounter Screen was used multiple times crash? Provide a uniqueScreenKey for your Screens

class ScreenFoo : Screen {\n\n    override val key: ScreenKey = uniqueScreenKey\n\n    @Composable\n    override fun Content() {\n        ...\n    }\n
"},{"location":"transitions-api/#available-transitions","title":"Available transitions","text":"FadeTransition() SlideTransition() ScaleTransition()"},{"location":"transitions-api/#custom-transitions","title":"Custom transitions","text":"

It\u2019s simple to add your own transitions: call ScreenTransition with a custom transitionModifier. Use the available params (screen, transition and event) to animate as needed.

@Composable\nfun MyCustomTransition(\n    navigator: Navigator,\n    modifier: Modifier = Modifier,\n    content: ScreenTransitionContent\n) {\n    ScreenTransition(\n        navigator = navigator,\n        modifier = modifier,\n        content = content,\n        transition = {\n            val (initialScale, targetScale) = when (navigator.lastEvent) {\n                StackEvent.Pop -> ExitScales\n                else -> EnterScales\n            }\n\n            scaleIn(initialScale) with scaleOut(targetScale)\n        }\n    )\n}\n\nsetContent {\n    Navigator(HomeScreen) { navigator ->\n        MyCustomTransition(navigator) { screen ->\n            screen.Content()\n        }\n    }\n}\n

Take a look at the source of the available transitions for working examples.

"},{"location":"transitions-api/#per-screen-transitions-experimental","title":"Per Screen transitions [Experimental]","text":"

If you want to define a Enter and Exit transition for a specific Screen, you have a lot of options to do starting from 1.1.0-beta01 Voyager have a new experimental API for this purpose. To animate the content, we use transitions of the target screen in the case of push navigation, otherwise we use transitions of the initial screen

class ExampleSlideScreen : Screen, ScreenTransition {\n    override val key: ScreenKey\n        get() = uniqueScreenKey\n\n    @Composable\n    override fun Content() {\n        ...\n    }\n\n    override fun enter(lastEvent: StackEvent): EnterTransition {\n        return slideIn { size ->\n            val x = if (lastEvent == StackEvent.Pop) -size.width else size.width\n            IntOffset(x = x, y = 0)\n        }\n    }\n\n    override fun exit(lastEvent: StackEvent): ExitTransition {\n        return slideOut { size ->\n            val x = if (lastEvent == StackEvent.Pop) size.width else -size.width\n            IntOffset(x = x, y = 0)\n        }\n    }\n}\n

It\u2019s convenient to use Kotlin delegates for per-Screen transitions. For example, you can create a SlideTransition and FadeTransition classes:

class FadeTransition : ScreenTransition {\n\n    override fun enter(lastEvent: StackEvent): EnterTransition {\n        return fadeIn(tween(500, delayMillis = 500))\n    }\n\n    override fun exit(lastEvent: StackEvent): ExitTransition {\n        return fadeOut(tween(500))\n    }\n}\n\nclass SlideTransition : ScreenTransition {\n\n    override fun enter(lastEvent: StackEvent): EnterTransition {\n        return slideIn { size ->\n            val x = if (lastEvent == StackEvent.Pop) -size.width else size.width\n            IntOffset(x = x, y = 0)\n        }\n    }\n\n    override fun exit(lastEvent: StackEvent): ExitTransition {\n        return slideOut { size ->\n            val x = if (lastEvent == StackEvent.Pop) size.width else -size.width\n            IntOffset(x = x, y = 0)\n        }\n    }\n}\n

Then you can use them as delegates in your Screens:

class SlideScreen : Screen, ScreenTransition by SlideTransition() {\n\n    @Composable\n    override fun Content() {\n        ...\n    }\n}\n\nclass FadeScreen : Screen, ScreenTransition by FadeTransition() {\n\n    @Composable\n    override fun Content() {\n        ...\n    }\n}\n

Also you can use can pass your custom ScreenTransition instance in ScreenTransition function, it will be used for default animation.

setContent {\n    Navigator(FadeScreen) { navigator ->\n        ScreenTransition(\n            navigator = navigator,\n            defaultTransition = SlideTransition()\n        )\n    }\n}\n

The API works with any ScreenTransition API, you just need to provide one and the Per Screen transition should.

setContent {\n    Navigator(HomeScreen) { navigator ->\n        SlideTransition(navigator)\n    }\n}\n

CrossfadeTransition is not supported yet.

"},{"location":"android-viewmodel/","title":"Android ViewModel","text":"
class PostListScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val viewModel = viewModel<PostListViewModel>()\n        // ...\n    }\n}\n

By default Voyager provides its own LocalViewModelStoreOwner and LocalSavedStateRegistryOwner, that way you can safely create ViewModels without depending on Activity or Fragment.

Info

Voyager provides a similar implementation, the ScreenModel, which does the same as ViewModel but also works with Compose Multiplatform.

"},{"location":"android-viewmodel/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"android-viewmodel/hilt-integration/","title":"Hilt integration","text":"

Success

To use the getViewModel you should first import cafe.adriel.voyager:voyager-hilt (see Setup).

"},{"location":"android-viewmodel/hilt-integration/#inject","title":"@Inject","text":"

Add @HiltViewModel and @Inject annotations to your ViewModel.

@HiltViewModel\nclass HomeViewModel @Inject constructor() : ViewModel() {\n    // ...\n}\n

Call getViewModel() to get a new instance.

class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = getViewModel<HomeScreenModel>()\n        // ...\n    }\n}\n
"},{"location":"android-viewmodel/hilt-integration/#assistedinject","title":"@AssistedInject","text":"

Currently there\u2019s no Assisted Injection support for Hilt ViewModels (issue).

"},{"location":"android-viewmodel/hilt-integration/#sample","title":"Sample","text":"

Info

Sample code here.

"},{"location":"android-viewmodel/viewmodel-kmp/","title":"ViewModel KMP","text":"

Since 1.1.0-beta01 we have introduce a experimental API for ViewModel KMP. It is under the package cafe.adriel.voyager:voyager-lifecycle-kmp (see Setup).

You will need to call ProvideNavigatorLifecycleKMPSupport before all Navigator calls and it will be working out of the box.

@Composable\nfun MainView() {\n    ProvideNavigatorLifecycleKMPSupport {\n        Navigator(...)\n    }\n}\n\nclass MyScreen : Screen {\n    @Composable\n    fun Content() {\n        val myViewModel = viewModel { MyScreenViewModel() }\n    }\n}\n
"},{"location":"android-viewmodel/viewmodel-kmp/#navigator-scoped-viewmodel","title":"Navigator scoped ViewModel","text":"

Voyager 1.1.0-beta01 also have introduced the support for Navigator scoped ViewModel and Lifecycle. This will make easy to share a ViewModel cross screen of the same navigator.

class MyScreen : Screen {\n    @Composable\n    fun Content() {\n        val myViewModel = navigatorViewModel { MyScreenViewModel() }\n    }\n}\n
"},{"location":"android-viewmodel/viewmodel-kmp/#lifecycle-kmp","title":"Lifecycle KMP","text":"

This version also brings the Lifecycle events for Screen lifecycle in KMP, now is possible to a generic third party API that listen to Lifecycle of a Screen in KMP.

"},{"location":"navigation/","title":"Navigation","text":"

Success

To use the Navigator you should first import cafe.adriel.voyager:voyager-navigator (see Setup).

"},{"location":"navigation/#screen","title":"Screen","text":"

On Voyager, screens are just classes with a composable function as the entrypoint. To create one, you should implement the Screen interface and override the Content() composable function.

You can use data class (if you need to send params), class (if no param is required).

class PostListScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        // ...\n    }\n}\n\ndata class PostDetailsScreen(val postId: Long) : Screen {\n\n    @Composable\n    override fun Content() {\n        // ...\n    }\n}\n
"},{"location":"navigation/#navigator","title":"Navigator","text":"

Navigator is a composable function deeply integrated with Compose internals. It\u2019ll manage the lifecyle, back press, state restoration and even nested navigation for you.

To start using it, just set the initial Screen.

class SingleActivity : ComponentActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        setContent {\n            Navigator(HomeScreen)\n        }\n    }\n}\n

Use the LocalNavigator to navigate to other screens. Take a look at the Stack API for the available operations.

class PostListScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        // ...\n    }\n\n    @Composable\n    private fun PostCard(post: Post) {\n        val navigator = LocalNavigator.currentOrThrow\n\n        Card(\n            modifier = Modifier.clickable { \n                navigator.push(PostDetailsScreen(post.id))\n                // Also works:\n                // navigator push PostDetailsScreen(post.id)\n                // navigator += PostDetailsScreen(post.id)\n            }\n        ) {\n            // ...\n        }\n    }\n}\n

If part of your UI is shared between screens, like the TopAppBar or BottomNavigation, you can easily reuse them with Voyager.

@Composable\noverride fun Content() {\n    Navigator(HomeScreen) { navigator ->\n        Scaffold(\n            topBar = { /* ... */ },\n            content = { CurrentScreen() },\n            bottomBar = { /* ... */ }\n        )\n    }\n}\n

{% hint style=\u201dwarning\u201d %} You should use CurrentScreen() instead of navigator.lastItem.Content(), because it will save the Screen\u2019s subtree for you (see SaveableStateHolder). {% endhint %}

"},{"location":"navigation/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"navigation/bottomsheet-navigation/","title":"BottomSheet navigation","text":"

Success

To use the BottomSheetNavigator you should first import cafe.adriel.voyager:voyager-bottom-sheet-navigator (see Setup).

Voyager provides a specialized navigator for ModalBottomSheetLayout: the BottomSheetNavigator.

Call it and set the back layer content. The BottomSheet content (or the front layer) will be set on demand.

setContent {\n    BottomSheetNavigator {\n        BackContent()\n    }\n}\n

You can also use the default Navigator to navigate on your back layer content.

setContent {\n    BottomSheetNavigator {\n        Navigator(BackScreen())\n    }\n}\n

The BottomSheetNavigator accepts the same params as ModalBottomSheetLayout.

@Composable\npublic fun BottomSheetNavigator(\n    modifier: Modifier = Modifier,\n    hideOnBackPress: Boolean = true,\n    scrimColor: Color = ModalBottomSheetDefaults.scrimColor,\n    sheetShape: Shape = MaterialTheme.shapes.large,\n    sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,\n    sheetBackgroundColor: Color = MaterialTheme.colors.surface,\n    sheetContentColor: Color = contentColorFor(sheetBackgroundColor),\n    // ...\n)\n

Use the LocalBottomSheetNavigator to show and hide the BottomSheet.

class BackScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val bottomSheetNavigator = LocalBottomSheetNavigator.current\n\n        Button(\n            onClick = { \n                bottomSheetNavigator.show(FrontScreen())\n            }\n        ) {\n            Text(text = \"Show BottomSheet\")\n        }\n    }\n}\n
"},{"location":"navigation/bottomsheet-navigation/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"navigation/multi-module-navigation/","title":"Multi-module navigation","text":"

Voyager has built-in support for multi-module navigation. Its API is based on great DI frameworks like Koin and Kodein, so should be familiar to use.

Suppose we have the following modules:

To navigate from HomeScreen to the screens on feature-posts module (ListScreen and DetailsScreen), we first need to provide them. The navigation module should declare all shared screens in the app, use the ScreenProvider interface for that.

navigation/../SharedScreen.kt
sealed class SharedScreen : ScreenProvider {\n    object PostList : SharedScreen()\n    data class PostDetails(val id: String) : SharedScreen()\n}\n

Now use the ScreenRegistry to register these providers. This should be done in yourApplication class.

app/../MyApp.kt
class MyApp : Application() {\n\n    override fun onCreate() {\n        super.onCreate()\n\n        ScreenRegistry {\n            register<SharedScreen.PostList> {\n                ListScreen()\n            }\n            register<SharedScreen.PostDetails> { provider ->\n                DetailsScreen(id = provider.id)\n            }\n        }\n    }\n}\n

You can also create a screenModule into your feature module, that way your Application class won\u2019t be bloated by too many declarations.

ScreenModule feature-posts/../ScreenModule.kt
val featurePostsScreenModule = screenModule {\n    register<SharedScreen.PostList> {\n        ListScreen()\n    }\n    register<SharedScreen.PostDetails> { provider ->\n        DetailsScreen(id = provider.id)\n    }\n}\n
app/../MyApp.kt
override fun onCreate() {\n    super.onCreate()\n\n    ScreenRegistry {\n        featurePostsScreenModule()\n    }\n}\n

Finally, call rememberScreen() (inside a composable function) or ScreenRegistry.get() to access your screens.

feature-home/../HomeScreen.kt
class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val navigator = LocalNavigator.currentOrThrow\n        val postListScreen = rememberScreen(SharedScreen.PostList)\n        val postDetailsScreen = rememberScreen(SharedScreen.PostDetails(id = postId))\n\n        // navigator.push(postListScreen)\n        // navigator.push(postDetailsScreen)\n    }\n}\n
"},{"location":"navigation/multi-module-navigation/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"navigation/nested-navigation/","title":"Nested navigation","text":""},{"location":"navigation/nested-navigation/#nested-navigators","title":"Nested Navigators","text":"

Going a little further, it\u2019s possible to have nested navigators. The Navigator has a level property (so you can check how deeper your are) and can have a parent navigator (if you need to interact with it).

setContent {\n    Navigator(ScreenA) { navigator0 ->\n        println(navigator.level)\n        // 0\n        println(navigator.parent == null)\n        // true\n        Navigator(ScreenB) { navigator1 ->\n            println(navigator.level)\n            // 1\n            println(navigator.parent == navigator0)\n            // true\n            Navigator(ScreenC) { navigator2 ->\n                println(navigator.level)\n                // 2\n                println(navigator.parent == navigator1)\n                // true\n            }\n        }\n    }\n}\n

Another operation is the popUntilRoot(), it will recursively pop all screens starting from the leaf navigator until the root one.

"},{"location":"navigation/nested-navigation/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"navigation/tab-navigation/","title":"Tab navigation","text":"

Success

To use the TabNavigator you should first import cafe.adriel.voyager:voyager-tab-navigator (see Setup).

Voyager provides a specialized navigator for tabs : the TabNavigator.

The Tab interface, like the Screen, has a Content() composable function, but also requires a TabOptions.

object HomeTab : Tab {\n\n    override val options: TabOptions\n        @Composable\n        get() {\n            val title = stringResource(R.string.home_tab)\n            val icon = rememberVectorPainter(Icons.Default.Home)\n\n            return remember {\n                TabOptions(\n                    index = 0u,\n                    title = title,\n                    icon = icon\n                )\n            }\n        }\n\n    @Composable\n    override fun Content() {\n        // ...\n    }\n}\n

Info

Since tabs aren\u2019t usually reused, its OK to create them as object.

The TabNavigator unlike the Navigator:

You can use it with a Scaffold to easily create the UI for your tabs.

setContent {\n    TabNavigator(HomeTab) {\n        Scaffold(\n            content = { \n                CurrentTab() \n            },\n            bottomBar = {\n                BottomNavigation {\n                    TabNavigationItem(HomeTab)\n                    TabNavigationItem(FavoritesTab)\n                    TabNavigationItem(ProfileTab)\n                }\n            }\n        )\n    }\n}\n

Warning

Like theCurrentScreen(), you should use CurrentTab instead of tabNavigator.current.Content(), because it will save the Tab\u2019s subtree for you (see SaveableStateHolder).

Use the LocalTabNavigator to get the current TabNavigator, and current to get and set the current tab.

@Composable\nprivate fun RowScope.TabNavigationItem(tab: Tab) {\n    val tabNavigator = LocalTabNavigator.current\n\n    BottomNavigationItem(\n        selected = tabNavigator.current == tab,\n        onClick = { tabNavigator.current = tab },\n        icon = { Icon(painter = tab.icon, contentDescription = tab.title) }\n    )\n}\n
"},{"location":"navigation/tab-navigation/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"navigation/tab-navigation/#tabnavigator-nested-navigator","title":"TabNavigator + Nested Navigator","text":"

For more complex use cases, when each tab should have its own independent navigation, like the Youtube app, you can combine the TabNavigator with multiple Navigators.

Let\u2019s go back to the Tab navigation example.

setContent {\n    TabNavigator(HomeTab) {\n        // ...\n    }\n}\n

But now, the HomeTab will have it\u2019s own Navigator.

object HomeTab : Screen {\n\n    @Composable\n    override fun Content() {\n        Navigator(PostListScreen())\n    }\n}\n

That way, we can use the LocalNavigator to navigate deeper into HomeTab, or the LocalTabNavigator to switch between tabs.

class PostListScreen : Screen {\n\n    @Composable\n    private fun GoToPostDetailsScreenButton(post: Post) {\n        val navigator = LocalNavigator.currentOrThrow\n\n        Button(\n            onClick = { navigator.push(PostDetailsScreen(post.id)) }\n        )\n    }\n\n    @Composable\n    private fun GoToProfileTabButton() {\n        val tabNavigator = LocalTabNavigator.current\n\n        Button(\n            onClick = { tabNavigator.current = ProfileTab }\n        )\n    }\n}\n
"},{"location":"screenmodel/","title":"ScreenModel","text":"

Success

The ScreenModel API is a part of the modulecafe.adriel.voyager:voyager-screenmodel (see Setup).

ScreenModel is just like a ViewModel: designed to store and manage UI-related data in a lifecycle conscious way. It also allows data to survive configuration changes such as screen rotations.

Unlike ViewModel, ScreenModel is just an interface. It\u2019s also Android independent and doesn\u2019t requires an Activity or Fragment to work.

class HomeScreenModel : ScreenModel {\n\n   // Optional\n    override fun onDispose() {\n        // ...\n    }\n}\n

Info

ScreenModel is integrated with Coroutines, RxJava, LiveData, Koin, Kodein and Hit!

By design, it\u2019s only possible to create a ScreenModel instance inside a Screen. Call rememberScreenModel and provide a factory lambda.

class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = rememberScreenModel { HomeScreenModel() }\n        // ...\n    }\n}\n

If you need to have multiple instances of the same ScreenModel for the same Screen, add a tag to differentiate them.

val screenModel = rememberScreenModel(tag = \"CUSTOM_TAG\") { HomeScreenModel() }\n
"},{"location":"screenmodel/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"screenmodel/#navigator-scoped-screenmodel","title":"Navigator scoped ScreenModel","text":"

Success

TherememberNavigatorScreenModel are part of the navigator library.

Starting from 1.0.0rc08 by using the new Navigator extension called rememberNavigatorScreenModel is possible to have a ScreenModel that is shared cross all Screens from a Navigator and when the Navigator leaves the Composition the ScreenModel is disposed.

class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val navigator = LocalNavigator.currentOrThrow\n        val screenModel = navigator.rememberNavigatorScreenModel { HomeScreenModel() }\n        // ...\n    }\n}\n

Info

Each DI library we provide a extension out of the box it is also provided support for Navigator scoped ScreenModel. See Koin, Kodein and Hit!

"},{"location":"screenmodel/coroutines-integration/","title":"Coroutines integration","text":"

Success

The screenModelScope and StateScreenModelare part of the core library.

"},{"location":"screenmodel/coroutines-integration/#coroutinescope","title":"CoroutineScope","text":"

The ScreenModel provides a screenModelScope property. It\u2019s canceled automatically when the ScreenModel is disposed.

class PostDetailsScreenModel(\n    private val repository: PostRepository\n) : ScreenModel {\n\n    fun getPost(id: String) {\n        screenModelScope.launch {\n            val post = repository.getPost(id)\n            // ...\n        }\n    }\n}\n
"},{"location":"screenmodel/coroutines-integration/#state-aware-screenmodel","title":"State-aware ScreenModel","text":"

If your ScreenModel needs to provide a state, use the StateScreenModel. Set the initial state on the constructor and use mutableState to change the current state.

class PostDetailsScreenModel(\n    private val repository: PostRepository\n) : StateScreenModel<PostDetailsScreenModel.State>(State.Init) {\n\n    sealed class State {\n        object Init : State()\n        object Loading : State()\n        data class Result(val post: Post) : State()\n    }\n\n    fun getPost(id: String) {\n        coroutineScope.launch {\n            mutableState.value = State.Loading\n            mutableState.value = State.Result(post = repository.getPost(id))\n        }\n    }\n}\n

In your screen use state.collectAsState() and handle the current state.

class PostDetailsScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = rememberScreenModel<PostDetailsScreenModel>()\n        val state by screenModel.state.collectAsState()\n\n        when (state) {\n            is State.Loading -> LoadingContent()\n            is State.Result -> PostContent(state.post)\n        }\n    }\n}\n
"},{"location":"screenmodel/coroutines-integration/#sample","title":"Sample","text":"

Info

Sample code here.

"},{"location":"screenmodel/coroutines-integration/#desktop-note","title":"Desktop Note","text":"

Info

If you are targeting Desktop, you should provide the dependency org.jetbrains.kotlinx:kotlinx-coroutines-swing, the screenModelScope depends on Dispatchers.Main provided by this library on Desktop. We don\u2019t include it because this library is incompatible with IntelliJ Plugin, see. If you are targeting Desktop for IntelliJ plugins, this library does not require to be provided.

"},{"location":"screenmodel/hilt-integration/","title":"Hilt integration","text":"

Success

To use the getScreenModel you should first import cafe.adriel.voyager:voyager-hilt (see Setup).

"},{"location":"screenmodel/hilt-integration/#inject","title":"@Inject","text":"

Add @Inject annotation to your ScreenModel.

class HomeScreenModel @Inject constructor() : ScreenModel {\n    // ...\n}\n

Call getScreenModel() to get a new instance.

class HomeScreen : AndroidScreen() {\n\n    @Composable\n    override fun Content() {\n        val screenModel = getScreenModel<HomeScreenModel>()\n        // ...\n    }\n}\n
"},{"location":"screenmodel/hilt-integration/#assistedinject","title":"@AssistedInject","text":"

Add @AssistedInject annotation to your ScreenModel and provide a ScreenModelFactory annotated with @AssistedFactory.

class PostDetailsScreenModel @AssistedInject constructor(\n    @Assisted val postId: Long\n) : ScreenModel {\n\n    @AssistedFactory\n    interface Factory : ScreenModelFactory {\n        fun create(postId: Long): PostDetailsScreenModel\n    }\n}\n

Call getScreenModel() and use your factory to create a new instance.

data class PostDetailsScreen(val postId: Long): AndroidScreen() {\n\n    @Composable\n    override fun Content() {\n        val screenModel = getScreenModel<PostDetailsScreenModel, PostDetailsScreenModel.Factory> { factory ->\n            factory.create(postId)\n        }\n        // ...\n    }\n}\n
"},{"location":"screenmodel/hilt-integration/#sample","title":"Sample","text":"

Info

Sample code here.

"},{"location":"screenmodel/hilt-integration/#navigator-scoped-screenmodel","title":"Navigator scoped ScreenModel","text":"
class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val navigator = LocalNavigator.currentOrThrow\n        val screenModel = navigator.getNavigatorScreenModel<HomeScreenModel>()\n        // ...\n    }\n}\n
"},{"location":"screenmodel/kodein-integration/","title":"Kodein integration","text":"

Success

To use the rememberScreenModel you should first import cafe.adriel.voyager:voyager-kodein (see Setup).

Declare your ScreenModels using the bindProvider bind.

val homeModule = DI.Module(name = \"home\") {\n    bindProvider { HomeScreenModel() } \n}\n

Call rememberScreenModel() to get a new instance.

class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = rememberScreenModel<HomeScreenModel>()\n        // ...\n    }\n}\n
"},{"location":"screenmodel/kodein-integration/#sample","title":"Sample","text":"

Info

Sample code here.

"},{"location":"screenmodel/kodein-integration/#navigator-scoped-screenmodel","title":"Navigator scoped ScreenModel","text":"
class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val navigator = LocalNavigator.currentOrThrow\n        val screenModel = navigator.rememberNavigatorScreenModel<HomeScreenModel>()\n        // ...\n    }\n}\n
"},{"location":"screenmodel/koin-integration/","title":"Koin integration","text":"

Success

To use the getScreenModel you should first import cafe.adriel.voyager:voyager-koin (see Setup).

Warning

Since 1.1.0-alpha04 we have rename the getScreenModel to koinScreenModel, this is a change to follow Koin Compose naming schema. The previous getScreenModel is deprecated and will be removed on 1.1.0

Declare your ScreenModels using the factory component.

val homeModule = module {\n    factory { HomeScreenModel() } \n}\n

Call getScreenModel() to get a new instance.

class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = getScreenModel<HomeScreenModel>()\n        // ...\n    }\n}\n
"},{"location":"screenmodel/koin-integration/#sample","title":"Sample","text":"

Info

Sample code here.

"},{"location":"screenmodel/koin-integration/#navigator-scoped-screenmodel","title":"Navigator scoped ScreenModel","text":"
class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val navigator = LocalNavigator.currentOrThrow\n        val screenModel = navigator.getNavigatorScreenModel<HomeScreenModel>()\n        // ...\n    }\n}\n
"},{"location":"screenmodel/livedata-integration/","title":"LiveData integration","text":"

Success

To use the LiveScreenModel you should first import cafe.adriel.voyager:voyager-livedata (see Setup).

"},{"location":"screenmodel/livedata-integration/#state-aware-screenmodel","title":"State-aware ScreenModel","text":"

If your ScreenModel needs to provide a state, use the LiveScreenModel. Set the initial state on the constructor and use mutableState to change the current state.

class PostDetailsScreenModel(\n    private val repository: PostRepository\n) : LiveScreenModel<PostDetailsScreenModel.State>(State.Init) {\n\n    sealed class State {\n        object Init : State()\n        object Loading : State()\n        data class Result(val post: Post) : State()\n    }\n\n    fun getPost(id: String) {\n        coroutineScope.launch {\n            val result = State.Result(post = repository.getPost(id))\n            mutableState.postValue(result)\n        }\n    }\n}\n

In your screen use state.observeAsState() and handle the current state.

class PostDetailsScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = rememberScreenModel<PostDetailsScreenModel>()\n        val state by screenModel.state.observeAsState()\n\n        when (state) {\n            is State.Loading -> LoadingContent()\n            is State.Result -> PostContent(state.post)\n        }\n    }\n}\n
"},{"location":"screenmodel/livedata-integration/#sample","title":"Sample","text":"

Info

Sample code here.

"},{"location":"screenmodel/rxjava-integration/","title":"RxJava integration","text":"

Success

To use the disposables and RxScreenModel you should first import cafe.adriel.voyager:voyager-rxjava (see Setup).

"},{"location":"screenmodel/rxjava-integration/#compositedisposable","title":"CompositeDisposable","text":"

The ScreenModel provides a disposables property. It\u2019s cleared automatically when the ScreenModel is disposed.

class PostDetailsScreenModel(\n    private val repository: PostRepository\n) : ScreenModel {\n\n    fun getPost(id: String) {\n        repository.getPost(id)\n            .subscribe { post -> /* ... */ }\n            .let(disposables::add)\n    }\n}\n
"},{"location":"screenmodel/rxjava-integration/#state-aware-screenmodel","title":"State-aware ScreenModel","text":"

If your ScreenModel needs to provide a state, use the RxScreenModel. Use mutableState to change the current state.

class PostDetailsScreenModel(\n    private val repository: PostRepository\n) : RxScreenModel<PostDetailsScreenModel.State>() {\n\n    sealed class State {\n        object Init : State()\n        object Loading : State()\n        data class Result(val post: Post) : State()\n    }\n\n    fun getPost(id: String) {\n        repository.getPost(id)\n            .doOnSubscribe { mutableState.onNext(State.Loading) }\n            .subscribe { post -> mutableState.onNext(State.Result(post)) }\n            .let(disposables::add)\n    }\n}\n

In your screen use state.subscribeAsState() and handle the current state.

class PostDetailsScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = rememberScreenModel<PostDetailsScreenModel>()\n        val state by screenModel.state.subscribeAsState(initial = State.Init)\n\n        when (state) {\n            is State.Loading -> LoadingContent()\n            is State.Result -> PostContent(state.post)\n        }\n    }\n}\n
"},{"location":"screenmodel/rxjava-integration/#sample","title":"Sample","text":"

Info

Sample code here.

"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Overview","text":""},{"location":"#voyager-compose-on-warp-speed","title":"Voyager: Compose on Warp Speed","text":"

A multiplatform navigation library built for, and seamlessly integrated with, Jetpack Compose.

Create scalable Single-Activity apps powered by a pragmatic API:

class HomeScreenModel : ScreenModel {\n    // ...\n}\n\nclass HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = rememberScreenModel { HomeScreenModel() }\n        // ...\n    }\n}\n\nclass SingleActivity : ComponentActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        setContent {\n            Navigator(HomeScreen())\n        }\n    }\n}\n

Turn on the Warp Drive and enjoy the voyage \ud83d\udd96

"},{"location":"#features","title":"Features","text":""},{"location":"#credits","title":"Credits","text":""},{"location":"back-press/","title":"Back press","text":"

By default, Voyager will handle back presses but you can override its behavior. Use the onBackPressed to manually handle it: return true to pop the current screen, or false otherwise. To disable, just set to null.

setContent {\n    Navigator(\n        initialScreen = HomeScreen,\n        onBackPressed = { currentScreen ->\n            false // won't pop the current screen\n            // true will pop, default behavior\n        }\n        // To disable:\n        // onBackPressed = null\n    )\n}\n
"},{"location":"community-projects/","title":"Community Projects Catalog","text":""},{"location":"community-projects/#open-source-libraries-with-voyager-extensions","title":"Open source libraries with Voyager extensions","text":""},{"location":"community-projects/#open-source-projects-using-voyager","title":"Open source projects using Voyager","text":""},{"location":"community-projects/#talks-and-tutorials","title":"Talks and Tutorials","text":""},{"location":"community-projects/#talks","title":"Talks","text":""},{"location":"community-projects/#tutorials","title":"Tutorials","text":""},{"location":"community-projects/#community-snippetsextensions","title":"Community snippets/extensions","text":""},{"location":"deep-links/","title":"Deep links","text":"

Warning

Currently Voyager does not provided a built in solution to handle Deeplink and URIs. see #149 and #382

You can initialize the Navigator with multiple screens, that way, the first visible screen will be the last one and will be possible to return (pop()) to the previous screens.

val postId = getPostIdFromIntent()\n\nsetContent {\n    Navigator(\n        HomeScreen,\n        PostListScreen(),\n        PostDetailsScreen(postId)\n    )\n}\n
"},{"location":"faq/","title":"Faq","text":"

iOS Swipe Back support { #ios-swipeback }

Voyager does not have a built in support for swipe back yet, we are not 100% conformable with all solutions that have out there and we think we will better of using a community made solution by copying to your code base and be able to change as you want your app to behave.

See this issue each for community build solutions.

Alternatively, we can also discuss in the future a community build solution using NavigationController under the hood like compose-cupertino have implemented for Decompose.

Support for predictive back animations { #predictive-back }

Voyager does not have a built in support for predictive back yet, but as well as iOS Swipe Back, the community have build extensions, and snippets with support, see #223 and 144.

Support for result passing between screens { #result-passing }

Voyager does not have a built in support for swipe back yet, we are not 100% conformable with all solutions that have out there, we encourage to use community made solutions by copying to your code base and being free to extend as your code base needs.

See #128 comments for community solutions.

Deeplink support

"},{"location":"lifecycle/","title":"Lifecycle","text":"

** Experimental API

Inside a Screen, you can call LifecycleEffectOnce to execute a block of code the first time the Screen appears, and optionally define a onDispose callback for when the Screen is leaving/removed from the Stack:

class PostListScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        LifecycleEffectOnce {\n            screenModel.initSomething()\n            onDispose {\n                // Do something when the screen is leaving/removed from the Stack\n            }\n        }\n\n        // ...\n    }\n}\n
"},{"location":"lifecycle/#screenlifecycleowner","title":"ScreenLifecycleOwner","text":"

With ScreenLifecycleProvider interface you can provide a special ScreenLifecycleOwner that can react when the Screen leaves the Stack

class MyScreenLifecycleOwner : ScreenLifecycleOwner {\n    override fun onDispose(screen: Screen) {\n        println(\"My ${screen.key} is being disposed\")\n    }\n}\n\n\ndata object MyScreen : Screen, ScreenLifecycleProvider {\n   @Composable\n   fun Content() {\n      ...\n   }\n\n   public override fun getLifecycleOwner(): ScreenLifecycleOwner =\n       ScreenLifecycleOwner()\n}\n
"},{"location":"lifecycle/#extending-the-lifecycle-lazily","title":"Extending the Lifecycle lazily","text":"

You can access directly the ScreenLifecycleStore to add a ScreenDisposable implementation lazily in a lifecycle aware custom API.

If you for example have a custom dependency that should be notified when the Screen is disposed you can use the ScreenLifecycleStore directly. For example, the Screen Model and ScreenModelStore APIs rely on this API to Store the ScreenModel instance and the Screen owns the ScreenModel instance, when the Screen leaves the Stack, the ScreenModelStore, that implements ScreenDisposable is notified and can dispose the ScreenModel.

Let\u2019s imagine a dependency called MyDependency that holds and provides a State for a Screen while it is on the Navigator stack and we want to notify MyDependency when the Screen leaves the Stack.

class MyCustomAPIWithDisposable(\n   private val myDependency: MyDependency\n) : ScreenDisposable {\n   public override fun onDispose(screen: Screen) {\n      myDependency.disposeStateForScreen(screen)\n   }\n}\n\n@Composable\nfun rememberMyDependency(): MyDependency {\n   val navigator = LocalNavigator.currentOrThrow\n   val myDependency by getMyDependecy() // getting from DI\n   remember(myDependency) {\n      myDependency.provideStateForScreen(navigator.lastItem)\n      ScreenLifecycleStore.get(navigator.lastItem) {\n         MyCustomAPIWithDisposable(myDependency)\n      }\n   }\n\n   return myDependency\n}\n
"},{"location":"lifecycle/#screendisposable-for-all-screens","title":"ScreenDisposable for all Screens","text":"

You can also provide a ScreenLifecycleOwner for all Screen in the stack of a Navigator easily by in the Navigator composable start to listen to lastItem for registering at ScreenLifecycleStore.

Let\u2019s reuse the MyScreenLifecycleOwner from the example above and provide it to all screens in the navigator.

class MyScreenLifecycleOwner : ScreenDisposable {\n    override fun onDispose(screen: Screen) {\n        println(\"My ${screen.key} is being disposed\")\n    }\n}\n\n@Composable\nfun YourAppComposable() {\n  Navigator(...) { navigator ->\n     remember(navigator.lastItem) {\n        ScreenLifecycleStore.get(navigator.lastItem) {\n           MyScreenLifecycleOwner()\n        }\n     }\n     CurrentScreen()\n  }\n}\n
"},{"location":"migration-to-1.0.0/","title":"Migration to 1.0.0","text":""},{"location":"migration-to-1.0.0/#screenmodel","title":"ScreenModel","text":""},{"location":"migration-to-1.0.0/#new-module","title":"New Module","text":"

Now the ScreenModel API has its own module and no longer are available in voyager-core, if you are using ScreenModel you should declare the dependency cafe.adriel.voyager:voyager-screenmodel (see Setup).

"},{"location":"migration-to-1.0.0/#navigation-level-screenmodel","title":"Navigation level ScreenModel","text":"

Since 1.0.0-rc08 we have introduced the ScreenModel scoped at Navigator Lifecycle, now the API is no longer marked as Experimental.

"},{"location":"migration-to-1.0.0/#deprecation-cycle","title":"Deprecation cycle","text":"

Since 1.0.0-rc08 we have renamed the extension coroutineScope to screenModelScope, now it was removed from 1.0.0, if you are still using it, just replace with screenModelScope.

"},{"location":"migration-to-1.0.0/#androidscreen","title":"AndroidScreen","text":"

The module voyager-androidx and AndroidScreen was removed! Since 1.0.0-rc06 we have introduced a new API called NavigatorScreenLifecycleProvider that provides by default the previous behavior of AndroidScreenLifecycleOwner on Android target for all Screen.

Important notice: AndroidScreen, different from Screen, it holds the Screen.key as a uniqueScreenKey, this is a pretty common requirement, to avoid issues and weird behaviors, we recommend continuing to specify a uniqueScreenKey if you are not, we also recommend creating a abstract class UniqueScreen to replace your AndroidScreen implementation.

abstract class UniqueScreen : Screen {\n    override val key: ScreenKey = uniqueScreenKey\n}\n
"},{"location":"migration-to-1.0.0/#apis-promote-to-stable","title":"APIs promote to Stable","text":""},{"location":"migration-to-1.0.0/#deprecation-cycle_1","title":"Deprecation cycle","text":""},{"location":"setup/","title":"Setup","text":"
  1. Add Maven Central to your repositories if needed

    repositories {\n    mavenCentral()\n}\n
    2. Add the desired dependencies to your module\u2019s build.gradle file

DependenciesVersion Catalog
dependencies {\n    val voyagerVersion = \"1.1.0-beta02\"\n\n    // Multiplatform\n\n    // Navigator\n    implementation(\"cafe.adriel.voyager:voyager-navigator:$voyagerVersion\")\n\n    // Screen Model\n    implementation(\"cafe.adriel.voyager:voyager-screenmodel:$voyagerVersion\")\n\n    // BottomSheetNavigator\n    implementation(\"cafe.adriel.voyager:voyager-bottom-sheet-navigator:$voyagerVersion\")\n\n    // TabNavigator\n    implementation(\"cafe.adriel.voyager:voyager-tab-navigator:$voyagerVersion\")\n\n    // Transitions\n    implementation(\"cafe.adriel.voyager:voyager-transitions:$voyagerVersion\")\n\n    // Koin integration\n    implementation(\"cafe.adriel.voyager:voyager-koin:$voyagerVersion\")\n\n    // Android\n\n    // Hilt integration\n    implementation(\"cafe.adriel.voyager:voyager-hilt:$voyagerVersion\")\n\n    // LiveData integration\n    implementation(\"cafe.adriel.voyager:voyager-livedata:$voyagerVersion\")\n\n    // Desktop + Android\n\n    // Kodein integration\n    implementation(\"cafe.adriel.voyager:voyager-kodein:$voyagerVersion\")\n\n    // RxJava integration\n    implementation(\"cafe.adriel.voyager:voyager-rxjava:$voyagerVersion\")\n}\n
[versions]\nvoyager = \"1.1.0-beta02\"\n\n[libraries]\nvoyager-navigator = { module = \"cafe.adriel.voyager:voyager-navigator\", version.ref = \"voyager\" }\nvoyager-screenModel = { module = \"cafe.adriel.voyager:voyager-screenmodel\", version.ref = \"voyager\" }\nvoyager-bottomSheetNavigator = { module = \"cafe.adriel.voyager:voyager-bottom-sheet-navigator\", version.ref = \"voyager\" }\nvoyager-tabNavigator = { module = \"cafe.adriel.voyager:voyager-tab-navigator\", version.ref = \"voyager\" }\nvoyager-transitions = { module = \"cafe.adriel.voyager:voyager-transitions\", version.ref = \"voyager\" }\nvoyager-koin = { module = \"cafe.adriel.voyager:voyager-koin\", version.ref = \"voyager\" }\nvoyager-hilt = { module = \"cafe.adriel.voyager:voyager-hilt\", version.ref = \"voyager\" }\nvoyager-kodein = { module = \"cafe.adriel.voyager:voyager-kodein\", version.ref = \"voyager\" }\nvoyager-rxjava = { module = \"cafe.adriel.voyager:voyager-rxjava\", version.ref = \"voyager\" }\n

Current version here.

"},{"location":"setup/#platform-compatibility","title":"Platform compatibility","text":"

Multiplatform targets: Android, iOS, Desktop, Mac Native, Web.

Android Desktop Multiplatform voyager-navigator voyager-screenModel voyager-bottom-sheet-navigator voyager-tab-navigator voyager-transitions voyager-koin voyager-kodein voyager-lifecycle-kmp voyager-hilt voyager-rxjava voyager-livedata"},{"location":"stack-api/","title":"Stack API","text":""},{"location":"stack-api/#snapshotstatestack","title":"SnapshotStateStack","text":"

Voyager is backed by a SnapshotStateStack:

You will use it to navigate forward (push, replace, replaceAll) and backwards (pop, popAll, popUntil), but the SnapshotStateStack can also be used as a regular collection outside the Navigator.

val stack = mutableStateStackOf(\"\ud83c\udf47\", \"\ud83c\udf49\", \"\ud83c\udf4c\", \"\ud83c\udf50\", \"\ud83e\udd5d\", \"\ud83c\udf4b\")\n// \ud83c\udf47, \ud83c\udf49, \ud83c\udf4c, \ud83c\udf50, \ud83e\udd5d, \ud83c\udf4b\n\nstack.lastItemOrNull\n// \ud83c\udf4b\n\nstack.push(\"\ud83c\udf4d\")\n// \ud83c\udf47, \ud83c\udf49, \ud83c\udf4c, \ud83c\udf50, \ud83e\udd5d, \ud83c\udf4b, \ud83c\udf4d\n\nstack.pop()\n// \ud83c\udf47, \ud83c\udf49, \ud83c\udf4c, \ud83c\udf50, \ud83e\udd5d, \ud83c\udf4b\n\nstack.popUntil { it == \"\ud83c\udf50\" }\n// \ud83c\udf47, \ud83c\udf49, \ud83c\udf4c, \ud83c\udf50\n\nstack.replace(\"\ud83c\udf53\")\n// \ud83c\udf47, \ud83c\udf49, \ud83c\udf4c, \ud83c\udf53\n\nstack.replaceAll(\"\ud83c\udf52\")\n// \ud83c\udf52\n

You can also create a SnapshotStateStack through rememberStateStack(), it will restore the values after Activity recreation.

"},{"location":"stack-api/#events","title":"Events","text":"

Whenever the content changes, the SnapshotStateStack will emit a StackEvent. Use the stack.lastEvent to get the most recent one.

The available events are:

This is very useful for deciding which transition to make.

"},{"location":"stack-api/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"state-restoration/","title":"State restoration","text":"

Voyager by default expect that it screens can be stored inside a Bundle. This means both Java Serializable and Parcelable are supported. By default all Voyager Screen is Java Serializable this means that Screen can be restored if all parameters are Java Serializable.

"},{"location":"state-restoration/#java-serializable","title":"Java Serializable","text":"
// \u2714\ufe0f DO\ndata class Post(/*...*/) : Serializable\n\ndata class ValidScreen(\n    val userId: UUID, // Built-in serializable types\n    val post: Post // Your own serializable types\n) : Screen {\n    // ...\n}\n\n// \ud83d\udeab DON'T\nclass Post(/*...*/)\n\ndata class InvalidScreen(\n    val context: Context, // Built-in non-serializable types\n    val post: Post, // Your own non-serializable types\n    val parcelable: SomeParcelable // Android Parcelable is not Java Serializable by default\n) : Screen {\n    // ...\n}\n

Not only the params, but the properties will also be restored, so the same rule applies.

// \u2714\ufe0f DO\nclass ValidScreen : Screen {\n\n    // Serializable properties\n    val tag = \"ValidScreen\"\n\n    // Lazily initialized serializable types\n    val randomId by lazy { UUID.randomUUID() }\n}\n\n// \ud83d\udeab DON'T\nclass InvalidScreen : Screen {\n\n    // Non-serializable properties\n    val postService = PostService()\n}\n
"},{"location":"state-restoration/#android-parcelables","title":"Android Parcelables","text":"
// \u2714\ufe0f DO\n@Parcelize\ndata class Post(/*...*/) : Parcelable\n\n@Parcelize\ndata class ValidScreen(\n    val post: Post // Your own parcelable types\n) : Screen, Parcelable {\n    // ...\n}\n\n// \ud83d\udeab DON'T\nclass Post(/*...*/)\n\n@Parcelize\ndata class InvalidScreen(\n    val context: Context, // Built-in non-parcelable types\n    val post: Post, // Your own non-parcelable types\n    val serializable: SomeSerializable // Java Serializable are not Android Parcelable by default\n) : Screen, Parcelable {\n    // ...\n}\n
"},{"location":"state-restoration/#enforcing-android-parcelable-on-your-screens","title":"Enforcing Android Parcelable on your screens","text":"

You can build your own Screen type for enforcing in at compile time that all yours screens should be Parcelable by creating an interface that implement Parcelable.

interface ParcelableScreen : Screen, Parcelable\n\n// Compile\n@Parcelize\ndata class Post(/*...*/) : Parcelable\n\n@Parcelize\ndata class ValidScreen(\n    val post: Post\n) : ParcelableScreen {\n    // ...\n}\n\n// Not compile\ndata class Post(/*...*/)\n\n@Parcelize\ndata class ValidScreen(\n    val post: Post\n) : ParcelableScreen {\n    // ...\n}\n

Starting from version 1.0.0-rc05 you can specify a custom NavigatorSaver to enforce that all Screen is Parcelable by using parcelableNavigatorSaver.

CompositionLocalProvider(\n    LocalNavigatorSaver provides parcelableNavigatorSaver()\n) {\n    Navigator(...) {\n       ...\n    }\n}\n
"},{"location":"state-restoration/#multiplatform-state-restoration","title":"Multiplatform state restoration","text":"

When working in a Multiplatform project and sharing the Parameters models with other platforms, your types required to be serializable in a Bundle if you are targeting Android, the easiest way is defining in common code a JavaSerializable interface that on Android only would implement java.io.Serialiable, see example below.

// commonMain - module core\nexpect interface JavaSerializable\n\ndata class Post(/*...*/) : JavaSerializable\n\n// androidMain - module core\nactual typealias JavaSerializable = java.io.Serializable\n\n// non AndroidMain (ios, web, etc) - module core\nactual interface JavaSerializable\n\n// android ui module or compose multiplatform module\ndata class ValidScreen(\n    val post: Post\n) : Screen\n
"},{"location":"state-restoration/#dependency-injection","title":"Dependency Injection","text":"

If you want to inject dependencies through a DI framework, make sure it supports Compose, like Koin and Kodein.

// \u2714\ufe0f DO\nclass ValidScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        // Inject your dependencies inside composables\n        val postService = get<PostService>()\n    }\n}\n\n// \ud83d\udeab DON'T\nclass InvalidScreen : Screen {\n\n    // Using DI to inject non-serializable types as properties\n    val postService by inject<PostService>()\n}\n
"},{"location":"state-restoration/#identifying-screens","title":"Identifying screens","text":"

The Screen interface has a key property used for saving and restoring the states for the subtree. You can override the default value to set your own key.

class HomeScreen : Screen {\n\n    override val key = \"CUSTOM_KEY\"\n\n    @Composable\n    override fun Content() {\n        // ...\n    }\n}\n

Voyager provides a uniqueScreenKey property, useful if you don\u2019t want to manage the keys yourself.

override val key = uniqueScreenKey\n

Warning

You should always set your own key if the screen:

"},{"location":"transitions-api/","title":"Transitions","text":"

To use the transitions you should first import cafe.adriel.voyager:voyager-transitions (see Setup).

Voyager has built-in transitions! When initializing the Navigator you can override the default content and use, for example, the SlideTransition.

setContent {\n    Navigator(HomeScreen) { navigator ->\n        SlideTransition(navigator)\n    }\n}\n

Known bug

There is a known 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.

Navigator(\n    screen = ...,\n    disposeBehavior = NavigatorDisposeBehavior(disposeSteps = false),\n) { navigator ->\n    SlideTransition(\n        navigator = navigator,\n        ...\n        disposeScreenAfterTransitionEnd = true\n    )\n}\n

Warning

Have encounter Screen was used multiple times crash? Provide a uniqueScreenKey for your Screens

class ScreenFoo : Screen {\n\n    override val key: ScreenKey = uniqueScreenKey\n\n    @Composable\n    override fun Content() {\n        ...\n    }\n
"},{"location":"transitions-api/#available-transitions","title":"Available transitions","text":"FadeTransition() SlideTransition() ScaleTransition()"},{"location":"transitions-api/#custom-transitions","title":"Custom transitions","text":"

It\u2019s simple to add your own transitions: call ScreenTransition with a custom transitionModifier. Use the available params (screen, transition and event) to animate as needed.

@Composable\nfun MyCustomTransition(\n    navigator: Navigator,\n    modifier: Modifier = Modifier,\n    content: ScreenTransitionContent\n) {\n    ScreenTransition(\n        navigator = navigator,\n        modifier = modifier,\n        content = content,\n        transition = {\n            val (initialScale, targetScale) = when (navigator.lastEvent) {\n                StackEvent.Pop -> ExitScales\n                else -> EnterScales\n            }\n\n            scaleIn(initialScale) with scaleOut(targetScale)\n        }\n    )\n}\n\nsetContent {\n    Navigator(HomeScreen) { navigator ->\n        MyCustomTransition(navigator) { screen ->\n            screen.Content()\n        }\n    }\n}\n

Take a look at the source of the available transitions for working examples.

"},{"location":"transitions-api/#per-screen-transitions-experimental","title":"Per Screen transitions [Experimental]","text":"

If you want to define a Enter and Exit transition for a specific Screen, you have a lot of options to do starting from 1.1.0-beta01 Voyager have a new experimental API for this purpose. To animate the content, we use transitions of the target screen in the case of push navigation, otherwise we use transitions of the initial screen

class ExampleSlideScreen : Screen, ScreenTransition {\n    override val key: ScreenKey\n        get() = uniqueScreenKey\n\n    @Composable\n    override fun Content() {\n        ...\n    }\n\n    override fun enter(lastEvent: StackEvent): EnterTransition {\n        return slideIn { size ->\n            val x = if (lastEvent == StackEvent.Pop) -size.width else size.width\n            IntOffset(x = x, y = 0)\n        }\n    }\n\n    override fun exit(lastEvent: StackEvent): ExitTransition {\n        return slideOut { size ->\n            val x = if (lastEvent == StackEvent.Pop) size.width else -size.width\n            IntOffset(x = x, y = 0)\n        }\n    }\n}\n

It\u2019s convenient to use Kotlin delegates for per-Screen transitions. For example, you can create a SlideTransition and FadeTransition classes:

class FadeTransition : ScreenTransition {\n\n    override fun enter(lastEvent: StackEvent): EnterTransition {\n        return fadeIn(tween(500, delayMillis = 500))\n    }\n\n    override fun exit(lastEvent: StackEvent): ExitTransition {\n        return fadeOut(tween(500))\n    }\n}\n\nclass SlideTransition : ScreenTransition {\n\n    override fun enter(lastEvent: StackEvent): EnterTransition {\n        return slideIn { size ->\n            val x = if (lastEvent == StackEvent.Pop) -size.width else size.width\n            IntOffset(x = x, y = 0)\n        }\n    }\n\n    override fun exit(lastEvent: StackEvent): ExitTransition {\n        return slideOut { size ->\n            val x = if (lastEvent == StackEvent.Pop) size.width else -size.width\n            IntOffset(x = x, y = 0)\n        }\n    }\n}\n

Then you can use them as delegates in your Screens:

class SlideScreen : Screen, ScreenTransition by SlideTransition() {\n\n    @Composable\n    override fun Content() {\n        ...\n    }\n}\n\nclass FadeScreen : Screen, ScreenTransition by FadeTransition() {\n\n    @Composable\n    override fun Content() {\n        ...\n    }\n}\n

Also you can use can pass your custom ScreenTransition instance in ScreenTransition function, it will be used for default animation.

setContent {\n    Navigator(FadeScreen) { navigator ->\n        ScreenTransition(\n            navigator = navigator,\n            defaultTransition = SlideTransition()\n        )\n    }\n}\n

The API works with any ScreenTransition API, you just need to provide one and the Per Screen transition should.

setContent {\n    Navigator(HomeScreen) { navigator ->\n        SlideTransition(navigator)\n    }\n}\n

CrossfadeTransition is not supported yet.

"},{"location":"android-viewmodel/","title":"Android ViewModel","text":"
class PostListScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val viewModel = viewModel<PostListViewModel>()\n        // ...\n    }\n}\n

By default Voyager provides its own LocalViewModelStoreOwner and LocalSavedStateRegistryOwner, that way you can safely create ViewModels without depending on Activity or Fragment.

Info

Voyager provides a similar implementation, the ScreenModel, which does the same as ViewModel but also works with Compose Multiplatform.

"},{"location":"android-viewmodel/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"android-viewmodel/hilt-integration/","title":"Hilt integration","text":"

Success

To use the getViewModel you should first import cafe.adriel.voyager:voyager-hilt (see Setup).

"},{"location":"android-viewmodel/hilt-integration/#inject","title":"@Inject","text":"

Add @HiltViewModel and @Inject annotations to your ViewModel.

@HiltViewModel\nclass HomeViewModel @Inject constructor() : ViewModel() {\n    // ...\n}\n

Call getViewModel() to get a new instance.

class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = getViewModel<HomeScreenModel>()\n        // ...\n    }\n}\n
"},{"location":"android-viewmodel/hilt-integration/#assistedinject","title":"@AssistedInject","text":"

Currently there\u2019s no Assisted Injection support for Hilt ViewModels (issue).

"},{"location":"android-viewmodel/hilt-integration/#sample","title":"Sample","text":"

Info

Sample code here.

"},{"location":"android-viewmodel/viewmodel-kmp/","title":"ViewModel KMP","text":"

Since 1.1.0-beta01 we have introduce a experimental API for ViewModel KMP. It is under the package cafe.adriel.voyager:voyager-lifecycle-kmp (see Setup).

You will need to call ProvideNavigatorLifecycleKMPSupport before all Navigator calls and it will be working out of the box.

@Composable\nfun MainView() {\n    ProvideNavigatorLifecycleKMPSupport {\n        Navigator(...)\n    }\n}\n\nclass MyScreen : Screen {\n    @Composable\n    fun Content() {\n        val myViewModel = viewModel { MyScreenViewModel() }\n    }\n}\n
"},{"location":"android-viewmodel/viewmodel-kmp/#navigator-scoped-viewmodel","title":"Navigator scoped ViewModel","text":"

Voyager 1.1.0-beta01 also have introduced the support for Navigator scoped ViewModel and Lifecycle. This will make easy to share a ViewModel cross screen of the same navigator.

class MyScreen : Screen {\n    @Composable\n    fun Content() {\n        val myViewModel = navigatorViewModel { MyScreenViewModel() }\n    }\n}\n
"},{"location":"android-viewmodel/viewmodel-kmp/#lifecycle-kmp","title":"Lifecycle KMP","text":"

This version also brings the Lifecycle events for Screen lifecycle in KMP, now is possible to a generic third party API that listen to Lifecycle of a Screen in KMP.

"},{"location":"navigation/","title":"Navigation","text":"

Success

To use the Navigator you should first import cafe.adriel.voyager:voyager-navigator (see Setup).

"},{"location":"navigation/#screen","title":"Screen","text":"

On Voyager, screens are just classes with a composable function as the entrypoint. To create one, you should implement the Screen interface and override the Content() composable function.

You can use data class (if you need to send params), class (if no param is required).

class PostListScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        // ...\n    }\n}\n\ndata class PostDetailsScreen(val postId: Long) : Screen {\n\n    @Composable\n    override fun Content() {\n        // ...\n    }\n}\n
"},{"location":"navigation/#navigator","title":"Navigator","text":"

Navigator is a composable function deeply integrated with Compose internals. It\u2019ll manage the lifecyle, back press, state restoration and even nested navigation for you.

To start using it, just set the initial Screen.

class SingleActivity : ComponentActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        setContent {\n            Navigator(HomeScreen)\n        }\n    }\n}\n

Use the LocalNavigator to navigate to other screens. Take a look at the Stack API for the available operations.

class PostListScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        // ...\n    }\n\n    @Composable\n    private fun PostCard(post: Post) {\n        val navigator = LocalNavigator.currentOrThrow\n\n        Card(\n            modifier = Modifier.clickable { \n                navigator.push(PostDetailsScreen(post.id))\n                // Also works:\n                // navigator push PostDetailsScreen(post.id)\n                // navigator += PostDetailsScreen(post.id)\n            }\n        ) {\n            // ...\n        }\n    }\n}\n

If part of your UI is shared between screens, like the TopAppBar or BottomNavigation, you can easily reuse them with Voyager.

@Composable\noverride fun Content() {\n    Navigator(HomeScreen) { navigator ->\n        Scaffold(\n            topBar = { /* ... */ },\n            content = { CurrentScreen() },\n            bottomBar = { /* ... */ }\n        )\n    }\n}\n

{% hint style=\u201dwarning\u201d %} You should use CurrentScreen() instead of navigator.lastItem.Content(), because it will save the Screen\u2019s subtree for you (see SaveableStateHolder). {% endhint %}

"},{"location":"navigation/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"navigation/bottomsheet-navigation/","title":"BottomSheet navigation","text":"

Success

To use the BottomSheetNavigator you should first import cafe.adriel.voyager:voyager-bottom-sheet-navigator (see Setup).

Voyager provides a specialized navigator for ModalBottomSheetLayout: the BottomSheetNavigator.

Call it and set the back layer content. The BottomSheet content (or the front layer) will be set on demand.

setContent {\n    BottomSheetNavigator {\n        BackContent()\n    }\n}\n

You can also use the default Navigator to navigate on your back layer content.

setContent {\n    BottomSheetNavigator {\n        Navigator(BackScreen())\n    }\n}\n

The BottomSheetNavigator accepts the same params as ModalBottomSheetLayout.

@Composable\npublic fun BottomSheetNavigator(\n    modifier: Modifier = Modifier,\n    hideOnBackPress: Boolean = true,\n    scrimColor: Color = ModalBottomSheetDefaults.scrimColor,\n    sheetShape: Shape = MaterialTheme.shapes.large,\n    sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,\n    sheetBackgroundColor: Color = MaterialTheme.colors.surface,\n    sheetContentColor: Color = contentColorFor(sheetBackgroundColor),\n    // ...\n)\n

Use the LocalBottomSheetNavigator to show and hide the BottomSheet.

class BackScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val bottomSheetNavigator = LocalBottomSheetNavigator.current\n\n        Button(\n            onClick = { \n                bottomSheetNavigator.show(FrontScreen())\n            }\n        ) {\n            Text(text = \"Show BottomSheet\")\n        }\n    }\n}\n
"},{"location":"navigation/bottomsheet-navigation/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"navigation/multi-module-navigation/","title":"Multi-module navigation","text":"

Voyager has built-in support for multi-module navigation. Its API is based on great DI frameworks like Koin and Kodein, so should be familiar to use.

Suppose we have the following modules:

To navigate from HomeScreen to the screens on feature-posts module (ListScreen and DetailsScreen), we first need to provide them. The navigation module should declare all shared screens in the app, use the ScreenProvider interface for that.

navigation/../SharedScreen.kt
sealed class SharedScreen : ScreenProvider {\n    object PostList : SharedScreen()\n    data class PostDetails(val id: String) : SharedScreen()\n}\n

Now use the ScreenRegistry to register these providers. This should be done in yourApplication class.

app/../MyApp.kt
class MyApp : Application() {\n\n    override fun onCreate() {\n        super.onCreate()\n\n        ScreenRegistry {\n            register<SharedScreen.PostList> {\n                ListScreen()\n            }\n            register<SharedScreen.PostDetails> { provider ->\n                DetailsScreen(id = provider.id)\n            }\n        }\n    }\n}\n

You can also create a screenModule into your feature module, that way your Application class won\u2019t be bloated by too many declarations.

ScreenModule feature-posts/../ScreenModule.kt
val featurePostsScreenModule = screenModule {\n    register<SharedScreen.PostList> {\n        ListScreen()\n    }\n    register<SharedScreen.PostDetails> { provider ->\n        DetailsScreen(id = provider.id)\n    }\n}\n
app/../MyApp.kt
override fun onCreate() {\n    super.onCreate()\n\n    ScreenRegistry {\n        featurePostsScreenModule()\n    }\n}\n

Finally, call rememberScreen() (inside a composable function) or ScreenRegistry.get() to access your screens.

feature-home/../HomeScreen.kt
class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val navigator = LocalNavigator.currentOrThrow\n        val postListScreen = rememberScreen(SharedScreen.PostList)\n        val postDetailsScreen = rememberScreen(SharedScreen.PostDetails(id = postId))\n\n        // navigator.push(postListScreen)\n        // navigator.push(postDetailsScreen)\n    }\n}\n
"},{"location":"navigation/multi-module-navigation/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"navigation/nested-navigation/","title":"Nested navigation","text":""},{"location":"navigation/nested-navigation/#nested-navigators","title":"Nested Navigators","text":"

Going a little further, it\u2019s possible to have nested navigators. The Navigator has a level property (so you can check how deeper your are) and can have a parent navigator (if you need to interact with it).

setContent {\n    Navigator(ScreenA) { navigator0 ->\n        println(navigator.level)\n        // 0\n        println(navigator.parent == null)\n        // true\n        Navigator(ScreenB) { navigator1 ->\n            println(navigator.level)\n            // 1\n            println(navigator.parent == navigator0)\n            // true\n            Navigator(ScreenC) { navigator2 ->\n                println(navigator.level)\n                // 2\n                println(navigator.parent == navigator1)\n                // true\n            }\n        }\n    }\n}\n

Another operation is the popUntilRoot(), it will recursively pop all screens starting from the leaf navigator until the root one.

"},{"location":"navigation/nested-navigation/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"navigation/tab-navigation/","title":"Tab navigation","text":"

Success

To use the TabNavigator you should first import cafe.adriel.voyager:voyager-tab-navigator (see Setup).

Voyager provides a specialized navigator for tabs : the TabNavigator.

The Tab interface, like the Screen, has a Content() composable function, but also requires a TabOptions.

object HomeTab : Tab {\n\n    override val options: TabOptions\n        @Composable\n        get() {\n            val title = stringResource(R.string.home_tab)\n            val icon = rememberVectorPainter(Icons.Default.Home)\n\n            return remember {\n                TabOptions(\n                    index = 0u,\n                    title = title,\n                    icon = icon\n                )\n            }\n        }\n\n    @Composable\n    override fun Content() {\n        // ...\n    }\n}\n

Info

Since tabs aren\u2019t usually reused, its OK to create them as object.

The TabNavigator unlike the Navigator:

You can use it with a Scaffold to easily create the UI for your tabs.

setContent {\n    TabNavigator(HomeTab) {\n        Scaffold(\n            content = { \n                CurrentTab() \n            },\n            bottomBar = {\n                BottomNavigation {\n                    TabNavigationItem(HomeTab)\n                    TabNavigationItem(FavoritesTab)\n                    TabNavigationItem(ProfileTab)\n                }\n            }\n        )\n    }\n}\n

Warning

Like theCurrentScreen(), you should use CurrentTab instead of tabNavigator.current.Content(), because it will save the Tab\u2019s subtree for you (see SaveableStateHolder).

Use the LocalTabNavigator to get the current TabNavigator, and current to get and set the current tab.

@Composable\nprivate fun RowScope.TabNavigationItem(tab: Tab) {\n    val tabNavigator = LocalTabNavigator.current\n\n    BottomNavigationItem(\n        selected = tabNavigator.current == tab,\n        onClick = { tabNavigator.current = tab },\n        icon = { Icon(painter = tab.icon, contentDescription = tab.title) }\n    )\n}\n
"},{"location":"navigation/tab-navigation/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"navigation/tab-navigation/#tabnavigator-nested-navigator","title":"TabNavigator + Nested Navigator","text":"

For more complex use cases, when each tab should have its own independent navigation, like the Youtube app, you can combine the TabNavigator with multiple Navigators.

Let\u2019s go back to the Tab navigation example.

setContent {\n    TabNavigator(HomeTab) {\n        // ...\n    }\n}\n

But now, the HomeTab will have it\u2019s own Navigator.

object HomeTab : Screen {\n\n    @Composable\n    override fun Content() {\n        Navigator(PostListScreen())\n    }\n}\n

That way, we can use the LocalNavigator to navigate deeper into HomeTab, or the LocalTabNavigator to switch between tabs.

class PostListScreen : Screen {\n\n    @Composable\n    private fun GoToPostDetailsScreenButton(post: Post) {\n        val navigator = LocalNavigator.currentOrThrow\n\n        Button(\n            onClick = { navigator.push(PostDetailsScreen(post.id)) }\n        )\n    }\n\n    @Composable\n    private fun GoToProfileTabButton() {\n        val tabNavigator = LocalTabNavigator.current\n\n        Button(\n            onClick = { tabNavigator.current = ProfileTab }\n        )\n    }\n}\n
"},{"location":"screenmodel/","title":"ScreenModel","text":"

Success

The ScreenModel API is a part of the modulecafe.adriel.voyager:voyager-screenmodel (see Setup).

ScreenModel is just like a ViewModel: designed to store and manage UI-related data in a lifecycle conscious way. It also allows data to survive configuration changes such as screen rotations.

Unlike ViewModel, ScreenModel is just an interface. It\u2019s also Android independent and doesn\u2019t requires an Activity or Fragment to work.

class HomeScreenModel : ScreenModel {\n\n   // Optional\n    override fun onDispose() {\n        // ...\n    }\n}\n

Info

ScreenModel is integrated with Coroutines, RxJava, LiveData, Koin, Kodein and Hit!

By design, it\u2019s only possible to create a ScreenModel instance inside a Screen. Call rememberScreenModel and provide a factory lambda.

class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = rememberScreenModel { HomeScreenModel() }\n        // ...\n    }\n}\n

If you need to have multiple instances of the same ScreenModel for the same Screen, add a tag to differentiate them.

val screenModel = rememberScreenModel(tag = \"CUSTOM_TAG\") { HomeScreenModel() }\n
"},{"location":"screenmodel/#sample","title":"Sample","text":"

Info

Source code here.

"},{"location":"screenmodel/#navigator-scoped-screenmodel","title":"Navigator scoped ScreenModel","text":"

Success

TherememberNavigatorScreenModel are part of the navigator library.

Starting from 1.0.0rc08 by using the new Navigator extension called rememberNavigatorScreenModel is possible to have a ScreenModel that is shared cross all Screens from a Navigator and when the Navigator leaves the Composition the ScreenModel is disposed.

class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val navigator = LocalNavigator.currentOrThrow\n        val screenModel = navigator.rememberNavigatorScreenModel { HomeScreenModel() }\n        // ...\n    }\n}\n

Info

Each DI library we provide a extension out of the box it is also provided support for Navigator scoped ScreenModel. See Koin, Kodein and Hit!

"},{"location":"screenmodel/coroutines-integration/","title":"Coroutines integration","text":"

Success

The screenModelScope and StateScreenModelare part of the core library.

"},{"location":"screenmodel/coroutines-integration/#coroutinescope","title":"CoroutineScope","text":"

The ScreenModel provides a screenModelScope property. It\u2019s canceled automatically when the ScreenModel is disposed.

class PostDetailsScreenModel(\n    private val repository: PostRepository\n) : ScreenModel {\n\n    fun getPost(id: String) {\n        screenModelScope.launch {\n            val post = repository.getPost(id)\n            // ...\n        }\n    }\n}\n
"},{"location":"screenmodel/coroutines-integration/#state-aware-screenmodel","title":"State-aware ScreenModel","text":"

If your ScreenModel needs to provide a state, use the StateScreenModel. Set the initial state on the constructor and use mutableState to change the current state.

class PostDetailsScreenModel(\n    private val repository: PostRepository\n) : StateScreenModel<PostDetailsScreenModel.State>(State.Init) {\n\n    sealed class State {\n        object Init : State()\n        object Loading : State()\n        data class Result(val post: Post) : State()\n    }\n\n    fun getPost(id: String) {\n        coroutineScope.launch {\n            mutableState.value = State.Loading\n            mutableState.value = State.Result(post = repository.getPost(id))\n        }\n    }\n}\n

In your screen use state.collectAsState() and handle the current state.

class PostDetailsScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = rememberScreenModel<PostDetailsScreenModel>()\n        val state by screenModel.state.collectAsState()\n\n        when (state) {\n            is State.Loading -> LoadingContent()\n            is State.Result -> PostContent(state.post)\n        }\n    }\n}\n
"},{"location":"screenmodel/coroutines-integration/#sample","title":"Sample","text":"

Info

Sample code here.

"},{"location":"screenmodel/coroutines-integration/#desktop-note","title":"Desktop Note","text":"

Info

If you are targeting Desktop, you should provide the dependency org.jetbrains.kotlinx:kotlinx-coroutines-swing, the screenModelScope depends on Dispatchers.Main provided by this library on Desktop. We don\u2019t include it because this library is incompatible with IntelliJ Plugin, see. If you are targeting Desktop for IntelliJ plugins, this library does not require to be provided.

"},{"location":"screenmodel/hilt-integration/","title":"Hilt integration","text":"

Success

To use the getScreenModel you should first import cafe.adriel.voyager:voyager-hilt (see Setup).

"},{"location":"screenmodel/hilt-integration/#inject","title":"@Inject","text":"

Add @Inject annotation to your ScreenModel.

class HomeScreenModel @Inject constructor() : ScreenModel {\n    // ...\n}\n

Call getScreenModel() to get a new instance.

class HomeScreen : AndroidScreen() {\n\n    @Composable\n    override fun Content() {\n        val screenModel = getScreenModel<HomeScreenModel>()\n        // ...\n    }\n}\n
"},{"location":"screenmodel/hilt-integration/#assistedinject","title":"@AssistedInject","text":"

Add @AssistedInject annotation to your ScreenModel and provide a ScreenModelFactory annotated with @AssistedFactory.

class PostDetailsScreenModel @AssistedInject constructor(\n    @Assisted val postId: Long\n) : ScreenModel {\n\n    @AssistedFactory\n    interface Factory : ScreenModelFactory {\n        fun create(postId: Long): PostDetailsScreenModel\n    }\n}\n

Call getScreenModel() and use your factory to create a new instance.

data class PostDetailsScreen(val postId: Long): AndroidScreen() {\n\n    @Composable\n    override fun Content() {\n        val screenModel = getScreenModel<PostDetailsScreenModel, PostDetailsScreenModel.Factory> { factory ->\n            factory.create(postId)\n        }\n        // ...\n    }\n}\n
"},{"location":"screenmodel/hilt-integration/#sample","title":"Sample","text":"

Info

Sample code here.

"},{"location":"screenmodel/hilt-integration/#navigator-scoped-screenmodel","title":"Navigator scoped ScreenModel","text":"
class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val navigator = LocalNavigator.currentOrThrow\n        val screenModel = navigator.getNavigatorScreenModel<HomeScreenModel>()\n        // ...\n    }\n}\n
"},{"location":"screenmodel/kodein-integration/","title":"Kodein integration","text":"

Success

To use the rememberScreenModel you should first import cafe.adriel.voyager:voyager-kodein (see Setup).

Declare your ScreenModels using the bindProvider bind.

val homeModule = DI.Module(name = \"home\") {\n    bindProvider { HomeScreenModel() } \n}\n

Call rememberScreenModel() to get a new instance.

class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = rememberScreenModel<HomeScreenModel>()\n        // ...\n    }\n}\n
"},{"location":"screenmodel/kodein-integration/#sample","title":"Sample","text":"

Info

Sample code here.

"},{"location":"screenmodel/kodein-integration/#navigator-scoped-screenmodel","title":"Navigator scoped ScreenModel","text":"
class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val navigator = LocalNavigator.currentOrThrow\n        val screenModel = navigator.rememberNavigatorScreenModel<HomeScreenModel>()\n        // ...\n    }\n}\n
"},{"location":"screenmodel/koin-integration/","title":"Koin integration","text":"

Success

To use the getScreenModel you should first import cafe.adriel.voyager:voyager-koin (see Setup).

Warning

Since 1.1.0-alpha04 we have rename the getScreenModel to koinScreenModel, this is a change to follow Koin Compose naming schema. The previous getScreenModel is deprecated and will be removed on 1.1.0

Declare your ScreenModels using the factory component.

val homeModule = module {\n    factory { HomeScreenModel() } \n}\n

Call getScreenModel() to get a new instance.

class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = getScreenModel<HomeScreenModel>()\n        // ...\n    }\n}\n
"},{"location":"screenmodel/koin-integration/#sample","title":"Sample","text":"

Info

Sample code here.

"},{"location":"screenmodel/koin-integration/#navigator-scoped-screenmodel","title":"Navigator scoped ScreenModel","text":"
class HomeScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val navigator = LocalNavigator.currentOrThrow\n        val screenModel = navigator.getNavigatorScreenModel<HomeScreenModel>()\n        // ...\n    }\n}\n
"},{"location":"screenmodel/livedata-integration/","title":"LiveData integration","text":"

Success

To use the LiveScreenModel you should first import cafe.adriel.voyager:voyager-livedata (see Setup).

"},{"location":"screenmodel/livedata-integration/#state-aware-screenmodel","title":"State-aware ScreenModel","text":"

If your ScreenModel needs to provide a state, use the LiveScreenModel. Set the initial state on the constructor and use mutableState to change the current state.

class PostDetailsScreenModel(\n    private val repository: PostRepository\n) : LiveScreenModel<PostDetailsScreenModel.State>(State.Init) {\n\n    sealed class State {\n        object Init : State()\n        object Loading : State()\n        data class Result(val post: Post) : State()\n    }\n\n    fun getPost(id: String) {\n        coroutineScope.launch {\n            val result = State.Result(post = repository.getPost(id))\n            mutableState.postValue(result)\n        }\n    }\n}\n

In your screen use state.observeAsState() and handle the current state.

class PostDetailsScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = rememberScreenModel<PostDetailsScreenModel>()\n        val state by screenModel.state.observeAsState()\n\n        when (state) {\n            is State.Loading -> LoadingContent()\n            is State.Result -> PostContent(state.post)\n        }\n    }\n}\n
"},{"location":"screenmodel/livedata-integration/#sample","title":"Sample","text":"

Info

Sample code here.

"},{"location":"screenmodel/rxjava-integration/","title":"RxJava integration","text":"

Success

To use the disposables and RxScreenModel you should first import cafe.adriel.voyager:voyager-rxjava (see Setup).

"},{"location":"screenmodel/rxjava-integration/#compositedisposable","title":"CompositeDisposable","text":"

The ScreenModel provides a disposables property. It\u2019s cleared automatically when the ScreenModel is disposed.

class PostDetailsScreenModel(\n    private val repository: PostRepository\n) : ScreenModel {\n\n    fun getPost(id: String) {\n        repository.getPost(id)\n            .subscribe { post -> /* ... */ }\n            .let(disposables::add)\n    }\n}\n
"},{"location":"screenmodel/rxjava-integration/#state-aware-screenmodel","title":"State-aware ScreenModel","text":"

If your ScreenModel needs to provide a state, use the RxScreenModel. Use mutableState to change the current state.

class PostDetailsScreenModel(\n    private val repository: PostRepository\n) : RxScreenModel<PostDetailsScreenModel.State>() {\n\n    sealed class State {\n        object Init : State()\n        object Loading : State()\n        data class Result(val post: Post) : State()\n    }\n\n    fun getPost(id: String) {\n        repository.getPost(id)\n            .doOnSubscribe { mutableState.onNext(State.Loading) }\n            .subscribe { post -> mutableState.onNext(State.Result(post)) }\n            .let(disposables::add)\n    }\n}\n

In your screen use state.subscribeAsState() and handle the current state.

class PostDetailsScreen : Screen {\n\n    @Composable\n    override fun Content() {\n        val screenModel = rememberScreenModel<PostDetailsScreenModel>()\n        val state by screenModel.state.subscribeAsState(initial = State.Init)\n\n        when (state) {\n            is State.Loading -> LoadingContent()\n            is State.Result -> PostContent(state.post)\n        }\n    }\n}\n
"},{"location":"screenmodel/rxjava-integration/#sample","title":"Sample","text":"

Info

Sample code here.

"}]} \ No newline at end of file diff --git a/setup/index.html b/setup/index.html index 37cfb9b6..9ec06f54 100644 --- a/setup/index.html +++ b/setup/index.html @@ -973,11 +973,11 @@

Setup& // Transitions implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion") - // Android - // Koin integration implementation("cafe.adriel.voyager:voyager-koin:$voyagerVersion") + // Android + // Hilt integration implementation("cafe.adriel.voyager:voyager-hilt:$voyagerVersion") diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 612b9fad..bd9ac325 100644 Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ