### Basic Navigator Setup Source: https://context7.com/adrielcafe/voyager/llms.txt Use Navigator as the root composable, passing the initial screen. Voyager automatically manages lifecycle, back-press, and state restoration. ```kotlin Navigator(PostListScreen()) ``` -------------------------------- ### Basic Screen and Navigation Setup Source: https://github.com/adrielcafe/voyager/blob/main/docs/index.md Defines a basic screen model and screen, and sets up the initial navigation in a single activity. Ensure you have the necessary Voyager dependencies added to your project. ```kotlin class HomeScreenModel : ScreenModel { // ... } class HomeScreen : Screen { @Composable override fun Content() { val screenModel = rememberScreenModel { HomeScreenModel() } // ... } } class SingleActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Navigator(HomeScreen()) } } } ``` -------------------------------- ### Nested Navigator Example Source: https://context7.com/adrielcafe/voyager/llms.txt Demonstrates nesting Navigator instances to create hierarchical navigation. Use `popUntilRoot()` to return to the top level of the outermost navigator. ```kotlin setContent { Navigator(RootScreen()) { rootNavigator -> // level == 0, parent == null CurrentScreen() // A screen further down the tree may itself embed a Navigator: // Navigator(ChildScreen()) { childNavigator -> // // level == 1, parent == rootNavigator // CurrentScreen() // } } } // From any screen: pop all the way back to the root of the outermost navigator val navigator = LocalNavigator.currentOrThrow navigator.popUntilRoot() ``` -------------------------------- ### Basic Screen and Navigator Setup in Kotlin Source: https://github.com/adrielcafe/voyager/blob/main/README.md Define a custom Screen with a ScreenModel and initialize the Navigator in a ComponentActivity. This sets up the basic navigation structure for a Jetpack Compose application using Voyager. ```kotlin class HomeScreenModel : ScreenModel { // ... } class HomeScreen : Screen { @Composable override fun Content() { val screenModel = rememberScreenModel() // ... } } class SingleActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Navigator(HomeScreen()) } } } ``` -------------------------------- ### Custom Transitions with ScreenTransition Composable Source: https://context7.com/adrielcafe/voyager/llms.txt Build fully custom transitions using the `ScreenTransition` composable and a `transition` lambda. This example demonstrates a flip transition based on the `navigator.lastEvent`. ```kotlin @Composable fun FlipTransition(navigator: Navigator, modifier: Modifier = Modifier) { ScreenTransition( navigator = navigator, modifier = modifier, transition = { when (navigator.lastEvent) { StackEvent.Pop -> scaleIn(initialScale = 1.2f) with scaleOut(targetScale = 0.8f) else -> scaleIn(initialScale = 0.8f) with scaleOut(targetScale = 1.2f) } } ) } setContent { Navigator(HomeScreen()) { navigator -> FlipTransition(navigator) } } ``` -------------------------------- ### Navigate Using RememberScreen in Home Module Source: https://context7.com/adrielcafe/voyager/llms.txt Navigate between screens in different modules without importing concrete screen implementations directly. Use rememberScreen to get screen instances based on their providers. ```kotlin // :feature-home module — navigates without importing :feature-posts class HomeScreen : Screen { @Composable override fun Content() { val navigator = LocalNavigator.currentOrThrow val postListScreen = rememberScreen(SharedScreen.PostList) val postDetails = rememberScreen(SharedScreen.PostDetails(id = 42L)) Button(onClick = { navigator.push(postListScreen) }) { Text("All posts") } Button(onClick = { navigator.push(postDetails) }) { Text("Post #42") } } } ``` -------------------------------- ### Launch Coroutine with screenModelScope Source: https://github.com/adrielcafe/voyager/blob/main/docs/screenmodel/coroutines-integration.md Use `screenModelScope.launch` to start coroutines that are automatically canceled when the `ScreenModel` is disposed. This is useful for background tasks like data fetching. ```kotlin class PostDetailsScreenModel( private val repository: PostRepository ) : ScreenModel { fun getPost(id: String) { screenModelScope.launch { val post = repository.getPost(id) // ... } } } ``` -------------------------------- ### Get ScreenModel Instance Source: https://github.com/adrielcafe/voyager/blob/main/docs/screenmodel/koin-integration.md Call `getScreenModel()` within a `@Composable` function to obtain an instance of your ScreenModel. Ensure `voyager-koin` is imported. ```kotlin class HomeScreen : Screen { @Composable override fun Content() { val screenModel = getScreenModel() // ... } } ``` -------------------------------- ### Create TabNavigationItem Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/tab-navigation.md Create a reusable composable for tab navigation items. It uses LocalTabNavigator to get the current navigator instance and updates the current tab when clicked. ```kotlin @Composable private fun RowScope.TabNavigationItem(tab: Tab) { val tabNavigator = LocalTabNavigator.current BottomNavigationItem( selected = tabNavigator.current == tab, onClick = { tabNavigator.current = tab }, icon = { Icon(painter = tab.options.icon!!, contentDescription = tab.options.title) } ) } ``` -------------------------------- ### One-time Effect Tied to Screen Appearance Source: https://context7.com/adrielcafe/voyager/llms.txt Use LifecycleEffectOnce for one-time setup and teardown logic tied to a screen's appearance. The onDispose block handles cleanup. ```kotlin class PostListScreen : Screen { @Composable override fun Content() { val screenModel = rememberScreenModel { PostListScreenModel() } LifecycleEffectOnce { screenModel.loadData() onDispose { screenModel.cancelPendingRequests() } } // rest of UI … } } ``` -------------------------------- ### Get Hilt ViewModel in Voyager Screen Source: https://github.com/adrielcafe/voyager/blob/main/docs/android-viewmodel/hilt-integration.md Use `getViewModel()` within your `Screen`'s `Content` composable to obtain an instance of your Hilt-managed ViewModel. Ensure `cafe.adriel.voyager:voyager-hilt` is imported. ```kotlin import androidx.compose.runtime.Composable import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screenmodel.getViewModel class HomeScreen : Screen { @Composable override fun Content() { val screenModel = getViewModel() // ... } } ``` -------------------------------- ### Using popUntilRoot() Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/nested-navigation.md Illustrates the usage of the `popUntilRoot()` function to recursively pop all screens from the current navigator up to the root. ```kotlin val navigator = rememberNavigator() navigator.popUntilRoot() ``` -------------------------------- ### Deep Linking with Initial Stack Source: https://context7.com/adrielcafe/voyager/llms.txt Initialize the Navigator with multiple screens to create a pre-defined back-stack, enabling navigation to intermediate destinations when deep linking. ```kotlin val postId: Long? = intent.getLongExtra("postId", -1).takeIf { it >= 0 } setContent { if (postId != null) { Navigator(HomeScreen(), PostListScreen(), PostDetailsScreen(postId)) } else { Navigator(HomeScreen()) } } ``` -------------------------------- ### Initialize BottomSheetNavigator Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/bottomsheet-navigation.md Use BottomSheetNavigator to set up the back layer content for the bottom sheet. ```kotlin setContent { BottomSheetNavigator { BackContent() } } ``` -------------------------------- ### Initialize Navigator in Activity Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/index.md Set up the `Navigator` in your `ComponentActivity` to manage navigation. Provide an initial screen to display. ```kotlin class SingleActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Navigator(HomeScreen) } } } ``` -------------------------------- ### Define a Screen with Parameters Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/index.md Implement the `Screen` interface to create a screen. Use a data class if the screen requires parameters. ```kotlin class PostListScreen : Screen { @Composable override fun Content() { // ... } } data class PostDetailsScreen(val postId: Long) : Screen { @Composable override fun Content() { // ... } } ``` -------------------------------- ### Initialize Navigator with Multiple Screens Source: https://github.com/adrielcafe/voyager/blob/main/docs/deep-links.md Initialize the Navigator with multiple screens to manage navigation flow. The first visible screen will be the last one added, enabling a pop() action to return to previous screens. ```kotlin val postId = getPostIdFromIntent() setContent { Navigator( HomeScreen, PostListScreen(), PostDetailsScreen(postId) ) } ``` -------------------------------- ### Create a Basic ScreenModel Source: https://github.com/adrielcafe/voyager/blob/main/docs/screenmodel/index.md Extend the `ScreenModel` interface and optionally override the `onDispose` method for cleanup logic. ```kotlin class HomeScreenModel : ScreenModel { // Optional override fun onDispose() { // ... } } ``` -------------------------------- ### Collect State in Composable Screen Source: https://github.com/adrielcafe/voyager/blob/main/docs/screenmodel/coroutines-integration.md In your Jetpack Compose screen, use `rememberScreenModel` to get an instance of your `ScreenModel`. Collect the state updates using `collectAsState()` and render the UI based on the current state. ```kotlin class PostDetailsScreen : Screen { @Composable override fun Content() { val screenModel = rememberScreenModel() val state by screenModel.state.collectAsState() when (state) { is State.Loading -> LoadingContent() is State.Result -> PostContent(state.post) } } } ``` -------------------------------- ### Create and Manipulate SnapshotStateStack Source: https://github.com/adrielcafe/voyager/blob/main/docs/stack-api.md Demonstrates the creation of a SnapshotStateStack using mutableStateStackOf and common manipulation operations like push, pop, popUntil, replace, and replaceAll. Use these methods for forward and backward navigation. ```kotlin val stack = mutableStateStackOf("🍇", "🍉", "🍌", "🍐", "🥝", "🍋") // 🍇, 🍉, 🍌, 🍐, 🥝, 🍋 stack.lastItemOrNull // 🍋 stack.push("🍍") // 🍇, 🍉, 🍌, 🍐, 🥝, 🍋, 🍍 stack.pop() // 🍇, 🍉, 🍌, 🍐, 🥝, 🍋 stack.popUntil { it == "🍐" } // 🍇, 🍉, 🍌, 🍐 stack.replace("🍓") // 🍇, 🍉, 🍌, 🍓 stack.replaceAll("🍒") // 🍒 ``` -------------------------------- ### Run JVM Native App Source: https://github.com/adrielcafe/voyager/blob/main/samples/multiplatform/README.md Execute this command to run the JVM native desktop application. ```shell ./gradlew :samples:multiplatform:run ``` -------------------------------- ### Navigate to a New Screen Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/index.md Use `LocalNavigator.currentOrThrow` to access the navigator instance and push a new screen onto the stack. Alternative syntaxes like `navigator push` or `navigator +=` are also supported. ```kotlin class PostListScreen : Screen { @Composable override fun Content() { // ... } @Composable private fun PostCard(post: Post) { val navigator = LocalNavigator.currentOrThrow Card( modifier = Modifier.clickable { navigator.push(PostDetailsScreen(post.id)) // Also works: // navigator push PostDetailsScreen(post.id) // navigator += PostDetailsScreen(post.id) } ) { // ... } } } ``` -------------------------------- ### Initialize Navigator with SlideTransition Source: https://github.com/adrielcafe/voyager/blob/main/docs/transitions-api.md Use SlideTransition when initializing the Navigator to apply slide animations to screen changes. Ensure `cafe.adriel.voyager:voyager-transitions` is imported. ```kotlin setContent { Navigator(HomeScreen) { SlideTransition(navigator) } } ``` -------------------------------- ### Define a No-Parameter Screen in Voyager Source: https://context7.com/adrielcafe/voyager/llms.txt Create a simple screen by implementing the `Screen` interface and defining its composable content. This is suitable for destinations that do not require any parameters. ```kotlin // No-parameter screen class PostListScreen : Screen { @Composable override fun Content() { Text("Post list") } } ``` -------------------------------- ### Build Android App Source: https://github.com/adrielcafe/voyager/blob/main/samples/multiplatform/README.md Execute this command to assemble the debug version of the Android application. ```shell ./gradlew :samples:multiplatform:assembleDebug ``` -------------------------------- ### Instantiate ScreenModel with a Tag Source: https://github.com/adrielcafe/voyager/blob/main/docs/screenmodel/index.md Provide a `tag` to `rememberScreenModel` when you need multiple distinct instances of the same `ScreenModel` within the same `Screen`. ```kotlin val screenModel = rememberScreenModel(tag = "CUSTOM_TAG") { HomeScreenModel() } ``` -------------------------------- ### Run macOS Native App Source: https://github.com/adrielcafe/voyager/blob/main/samples/multiplatform/README.md Execute this command to run the macOS native desktop application using Kotlin Native. ```shell ./gradlew :samples:multiplatform:runNativeDebug ``` -------------------------------- ### Initialize BottomSheetNavigator with Navigator Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/bottomsheet-navigation.md Initialize BottomSheetNavigator with a default Navigator for the back layer content. ```kotlin setContent { BottomSheetNavigator { Navigator(BackScreen()) } } ``` -------------------------------- ### Implementing Nested Navigators Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/nested-navigation.md Demonstrates how to create nested navigators using the `Navigator` composable. Access the navigator's `level` and `parent` properties within nested scopes. ```kotlin setContent { Navigator(ScreenA) { navigator0 -> println(navigator.level) // 0 println(navigator.parent == null) // true Navigator(ScreenB) { navigator1 -> println(navigator.level) // 1 println(navigator.parent == navigator0) // true Navigator(ScreenC) { navigator2 -> println(navigator.level) // 2 println(navigator.parent == navigator1) // true } } } } ``` -------------------------------- ### Initialize ScreenRegistry in Application Source: https://context7.com/adrielcafe/voyager/llms.txt Initialize the ScreenRegistry in the Application's onCreate method, combining screen modules from different feature modules. ```kotlin // :app module — Application.onCreate() class MyApp : Application() { override fun onCreate() { super.onCreate() ScreenRegistry { featurePostsScreenModule() } } } ``` -------------------------------- ### Per-Screen Transitions with ScreenTransition Source: https://context7.com/adrielcafe/voyager/llms.txt Implement `ScreenTransition` on a `Screen` for custom enter/exit animations, or delegate to a reusable transition class. Use `ScreenTransition` composable with a default fallback transition. ```kotlin // Reusable transition delegates class SlideScreenTransition : ScreenTransition { override fun enter(lastEvent: StackEvent): EnterTransition = slideIn { size -> IntOffset(if (lastEvent == StackEvent.Pop) -size.width else size.width, 0) } override fun exit(lastEvent: StackEvent): ExitTransition = slideOut { size -> IntOffset(if (lastEvent == StackEvent.Pop) size.width else -size.width, 0) } } class FadeScreenTransition : ScreenTransition { override fun enter(lastEvent: StackEvent): EnterTransition = fadeIn(tween(400, delayMillis = 400)) override fun exit(lastEvent: StackEvent): ExitTransition = fadeOut(tween(400)) } // Screens delegate to the transition classes class DetailScreen : Screen, ScreenTransition by SlideScreenTransition() { override val key: ScreenKey = uniqueScreenKey @Composable override fun Content() { /* … */ } } class SummaryScreen : Screen, ScreenTransition by FadeScreenTransition() { override val key: ScreenKey = uniqueScreenKey @Composable override fun Content() { /* … */ } } // Wire up with a default fallback transition setContent { Navigator(HomeScreen()) { navigator -> ScreenTransition(navigator = navigator, defaultTransition = SlideScreenTransition()) } } ``` -------------------------------- ### Provide Custom ScreenLifecycleOwner Source: https://github.com/adrielcafe/voyager/blob/main/docs/lifecycle.md Implement `ScreenLifecycleProvider` to supply a custom `ScreenLifecycleOwner` that can react to screen disposal events. This allows for specific cleanup or logging when a screen leaves the navigation stack. ```kotlin class MyScreenLifecycleOwner : ScreenLifecycleOwner { override fun onDispose(screen: Screen) { println("My ${screen.key} is being disposed") } } data object MyScreen : Screen, ScreenLifecycleProvider { @Composable fun Content() { ... } public override fun getLifecycleOwner(): ScreenLifecycleOwner = ScreenLifecycleOwner() } ``` -------------------------------- ### Integrate Navigator with Scaffold Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/index.md Wrap your navigation with a `Scaffold` to include persistent UI elements like `TopAppBar` and `BottomNavigation`. Use `CurrentScreen()` to display the current screen's content. ```kotlin @Composable override fun Content() { Navigator(HomeScreen) { navigator -> Scaffold( topBar = { /* ... */ }, content = { CurrentScreen() }, bottomBar = { /* ... */ } ) } } ``` -------------------------------- ### Voyager Version Catalog Equivalent Source: https://context7.com/adrielcafe/voyager/llms.txt Define Voyager dependencies using a version catalog in your gradle/libs.versions.toml file for better dependency management. ```toml [versions] voyager = "1.1.0-beta02" [libraries] voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } voyager-screenModel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" } voyager-bottomSheetNavigator = { module = "cafe.adriel.voyager:voyager-bottom-sheet-navigator", version.ref = "voyager" } voyager-tabNavigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" } voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" } voyager-koin = { module = "cafe.adriel.voyager:voyager-koin", version.ref = "voyager" } voyager-hilt = { module = "cafe.adriel.voyager:voyager-hilt", version.ref = "voyager" } voyager-kodein = { module = "cafe.adriel.voyager:voyager-kodein", version.ref = "voyager" } ``` -------------------------------- ### Bind ScreenModel with Kodein Source: https://github.com/adrielcafe/voyager/blob/main/docs/screenmodel/kodein-integration.md Declare your ScreenModel instances using Kodein's bindProvider. Ensure you have imported the voyager-kodein dependency. ```kotlin val homeModule = DI.Module(name = "home") { bindProvider { HomeScreenModel() } } ``` -------------------------------- ### Execute Code Once on Screen Appearance Source: https://github.com/adrielcafe/voyager/blob/main/docs/lifecycle.md Use `LifecycleEffectOnce` within a `Screen`'s `Content` to run a block of code the first time the screen appears. An optional `onDispose` callback can be provided to execute code when the screen is removed from the stack. ```kotlin class PostListScreen : Screen { @Composable override fun Content() { LifecycleEffectOnce { screenModel.initSomething() onDispose { // Do something when the screen is leaving/removed from the Stack } } // ... } } ``` -------------------------------- ### Reusable Fade and Slide Transition Classes Source: https://github.com/adrielcafe/voyager/blob/main/docs/transitions-api.md Create reusable transition classes like FadeTransition and SlideTransition by implementing ScreenTransition. These can be delegated to screens for consistent animations. ```kotlin class FadeTransition : ScreenTransition { override fun enter(lastEvent: StackEvent): EnterTransition { return fadeIn(tween(500, delayMillis = 500)) } override fun exit(lastEvent: StackEvent): ExitTransition { return fadeOut(tween(500)) } } class SlideTransition : ScreenTransition { override fun enter(lastEvent: StackEvent): EnterTransition { return slideIn { val x = if (lastEvent == StackEvent.Pop) -size.width else size.width IntOffset(x = x, y = 0) } } override fun exit(lastEvent: StackEvent): ExitTransition { return slideOut { val x = if (lastEvent == StackEvent.Pop) size.width else -size.width IntOffset(x = x, y = 0) } } } ``` -------------------------------- ### Create a Custom Transition with ScreenTransition Source: https://github.com/adrielcafe/voyager/blob/main/docs/transitions-api.md Implement custom screen transitions by calling `ScreenTransition` and providing a custom `transitionModifier`. Use `navigator.lastEvent` to define enter and exit animations. ```kotlin @Composable fun MyCustomTransition( navigator: Navigator, modifier: Modifier = Modifier, content: ScreenTransitionContent ) { ScreenTransition( navigator = navigator, modifier = modifier, content = content, transition = { val (initialScale, targetScale) = when (navigator.lastEvent) { StackEvent.Pop -> ExitScales else -> EnterScales } scaleIn(initialScale) with scaleOut(targetScale) } ) } setContent { Navigator(HomeScreen) { navigator -> MyCustomTransition(navigator) { screen -> screen.Content() } } } ``` -------------------------------- ### Register Screen Providers in Application Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/multi-module-navigation.md Register screen providers within your `Application` class using `ScreenRegistry`. This makes screens available application-wide. ```kotlin class MyApp : Application() { override fun onCreate() { super.onCreate() ScreenRegistry { register { ListScreen() } register { DetailsScreen(id = provider.id) } } } } ``` -------------------------------- ### Show BottomSheet from Back Layer Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/bottomsheet-navigation.md Access LocalBottomSheetNavigator to show front layer content from the back layer screen. ```kotlin class BackScreen : Screen { @Composable override fun Content() { val bottomSheetNavigator = LocalBottomSheetNavigator.current Button( onClick = { bottomSheetNavigator.show(FrontScreen()) } ) { Text(text = "Show BottomSheet") } } } ``` -------------------------------- ### Run Web Compose Canvas App Source: https://github.com/adrielcafe/voyager/blob/main/samples/multiplatform/README.md Execute this command to run the Web Compose Canvas application in a browser. ```shell ./gradlew :samples:multiplatform:jsBrowserDevelopmentRun ``` -------------------------------- ### Implement TabNavigator with Scaffold Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/tab-navigation.md Use TabNavigator within setContent and integrate it with Scaffold for UI layout. The bottomBar displays TabNavigationItems, and the content area shows the CurrentTab. ```kotlin setContent { TabNavigator(HomeTab) { Scaffold( content = { padding -> Box(modifier = Modifier.padding(padding)) { CurrentTab() } }, bottomBar = { BottomNavigation { TabNavigationItem(HomeTab) TabNavigationItem(FavoritesTab) TabNavigationItem(ProfileTab) } } ) } } ``` -------------------------------- ### Create Feature Module ScreenModule Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/multi-module-navigation.md Organize screen registrations within feature modules by creating a `screenModule`. This helps keep the `Application` class clean. ```kotlin val featurePostsScreenModule = screenModule { register { ListScreen() } register { DetailsScreen(id = provider.id) } } ``` -------------------------------- ### ViewModel KMP Multiplatform Support Source: https://context7.com/adrielcafe/voyager/llms.txt Enable `viewModel {}` and `navigatorViewModel {}` on all platforms by wrapping the app root with `ProvideNavigatorLifecycleKMPSupport`. `navigatorViewModel {}` provides Navigator-scoped instances. ```kotlin // commonMain @Composable fun App() { ProvideNavigatorLifecycleKMPSupport { Navigator(HomeScreen()) } } class HomeScreen : Screen { @Composable override fun Content() { val vm = viewModel { HomeViewModel() } // screen-scoped val sharedVm = navigatorViewModel { SharedViewModel() } // navigator-scoped } } ``` -------------------------------- ### Instantiate ScreenModel in a Screen Source: https://github.com/adrielcafe/voyager/blob/main/docs/screenmodel/index.md Use `rememberScreenModel` within a `Screen`'s `Content` composable to create and remember an instance of your `ScreenModel`. ```kotlin class HomeScreen : Screen { @Composable override fun Content() { val screenModel = rememberScreenModel { HomeScreenModel() } // ... } } ``` -------------------------------- ### Voyager Dependencies using Version Catalog Source: https://github.com/adrielcafe/voyager/blob/main/docs/setup.md Define Voyager dependencies using a version catalog in your gradle.versions.toml file for centralized version management. ```toml [versions] voyager = "1.1.0-beta02" [libraries] voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } voyager-screenModel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" } voyager-bottomSheetNavigator = { module = "cafe.adriel.voyager:voyager-bottom-sheet-navigator", version.ref = "voyager" } voyager-tabNavigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" } voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" } voyager-koin = { module = "cafe.adriel.voyager:voyager-koin", version.ref = "voyager" } voyager-hilt = { module = "cafe.adriel.voyager:voyager-hilt", version.ref = "voyager" } voyager-kodein = { module = "cafe.adriel.voyager:voyager-kodein", version.ref = "voyager" } voyager-rxjava = { module = "cafe.adriel.voyager:voyager-rxjava", version.ref = "voyager" } ``` -------------------------------- ### Add Voyager Dependencies to Gradle Source: https://context7.com/adrielcafe/voyager/llms.txt Include the core and extension modules for Voyager navigation in your project's build.gradle.kts file. Specify the desired Voyager version. ```kotlin // build.gradle.kts dependencies { val voyagerVersion = "1.1.0-beta02" // Core – multiplatform implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion") implementation("cafe.adriel.voyager:voyager-screenmodel:$voyagerVersion") // Extension navigators – multiplatform implementation("cafe.adriel.voyager:voyager-bottom-sheet-navigator:$voyagerVersion") implementation("cafe.adriel.voyager:voyager-tab-navigator:$voyagerVersion") implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion") // DI integrations implementation("cafe.adriel.voyager:voyager-koin:$voyagerVersion") // multiplatform implementation("cafe.adriel.voyager:voyager-kodein:$voyagerVersion") // desktop + android implementation("cafe.adriel.voyager:voyager-hilt:$voyagerVersion") // android only implementation("cafe.adriel.voyager:voyager-livedata:$voyagerVersion") // android only implementation("cafe.adriel.voyager:voyager-rxjava:$voyagerVersion") // desktop + android // KMP Lifecycle / ViewModel support (experimental, since 1.1.0-beta01) implementation("cafe.adriel.voyager:voyager-lifecycle-kmp:$voyagerVersion") } ``` -------------------------------- ### Provide uniqueScreenKey for Screens Source: https://github.com/adrielcafe/voyager/blob/main/docs/transitions-api.md To avoid 'Screen was used multiple times' crashes, assign a unique key to your Screen implementations using `uniqueScreenKey`. ```kotlin class ScreenFoo : Screen { override val key: ScreenKey = uniqueScreenKey @Composable override fun Content() { ... } } ``` -------------------------------- ### Stack API Operations Source: https://context7.com/adrielcafe/voyager/llms.txt Demonstrates various stack manipulation methods available on Navigator or mutableStateStackOf, including push, pop, replace, and popUntil. ```kotlin val stack = mutableStateStackOf("Step 1", "Step 2", "Step 3", "Step 4") stack.lastItemOrNull // "Step 4" stack.push("Step 5") // […, "Step 3", "Step 4", "Step 5"] stack.pop() // […, "Step 3", "Step 4"] stack.popUntil { it == "Step 2" } // ["Step 1", "Step 2"] stack.replace("Step 2b") // ["Step 1", "Step 2b"] stack.replaceAll("Reset") // ["Reset"] println(stack.lastEvent) // StackEvent.Replace val navigator = LocalNavigator.currentOrThrow avigator.push(ScreenB()) avigator.replace(ScreenC()) avigator.replaceAll(ScreenD()) avigator.pop() avigator.popAll() avigator.popUntil { it is HomeScreen } avigator.popUntilRoot() ``` -------------------------------- ### Navigate within Nested Navigator and Switch Tabs Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/tab-navigation.md Demonstrates how to use LocalNavigator to push screens within a tab's nested navigator and LocalTabNavigator to switch between different tabs. ```kotlin class PostListScreen : Screen { @Composable private fun GoToPostDetailsScreenButton(post: Post) { val navigator = LocalNavigator.currentOrThrow Button( onClick = { navigator.push(PostDetailsScreen(post.id)) } ) } @Composable private fun GoToProfileTabButton() { val tabNavigator = LocalTabNavigator.current Button( onClick = { tabNavigator.current = ProfileTab } ) } } ``` -------------------------------- ### Setting Default Screen Transition Source: https://github.com/adrielcafe/voyager/blob/main/docs/transitions-api.md Apply a default transition animation for all screens within a Navigator by passing a ScreenTransition instance to the ScreenTransition composable. ```kotlin setContent { Navigator(FadeScreen) { ScreenTransition( navigator = navigator, defaultTransition = SlideTransition() ) } } ``` -------------------------------- ### Custom Screen Lifecycle Owner Source: https://context7.com/adrielcafe/voyager/llms.txt Implement ScreenLifecycleProvider to provide a custom lifecycle owner for a screen, allowing custom disposal logic. ```kotlin data object AnalyticsScreen : Screen, ScreenLifecycleProvider { @Composable override fun Content() { /* … */ } override fun getLifecycleOwner(): ScreenLifecycleOwner = object : ScreenLifecycleOwner { override fun onDispose(screen: Screen) { Analytics.trackScreenExit(screen.key) } } } ``` -------------------------------- ### Define Shared Screens for Multi-module Navigation Source: https://context7.com/adrielcafe/voyager/llms.txt Define sealed classes implementing ScreenProvider in a shared module to represent screens accessible across different feature modules. ```kotlin // :navigation module — shared by all feature modules sealed class SharedScreen : ScreenProvider { object PostList : SharedScreen() data class PostDetails(val id: Long) : SharedScreen() } ``` -------------------------------- ### Define a Parameterized Screen in Voyager Source: https://context7.com/adrielcafe/voyager/llms.txt Create a screen that accepts parameters by using a `data class` implementing `Screen`. Parameters are automatically saved and restored across process death due to `Screen` being `Serializable`. ```kotlin // Parameterised screen – parameters survive process death because Screen is Serializable data class PostDetailsScreen(val postId: Long) : Screen { @Composable override fun Content() { Text("Details for post $postId") } } ``` -------------------------------- ### State-aware ScreenModel with LiveData Source: https://github.com/adrielcafe/voyager/blob/main/docs/screenmodel/livedata-integration.md Use LiveScreenModel to provide a state that can be observed. Initialize the state in the constructor and update it using mutableState.postValue(). ```kotlin class PostDetailsScreenModel( private val repository: PostRepository ) : LiveScreenModel(State.Init) { sealed class State { object Init : State() object Loading : State() data class Result(val post: Post) : State() } fun getPost(id: String) { coroutineScope.launch { val result = State.Result(post = repository.getPost(id)) mutableState.postValue(result) } } } ``` -------------------------------- ### Koin Integration for ScreenModel Injection Source: https://context7.com/adrielcafe/voyager/llms.txt Declare ScreenModels as 'factory' components in your Koin DI module and inject them using koinScreenModel(). For ScreenModels with parameters, use the 'params' lambda to provide them. ```kotlin // DI module val appModule = module { factory { PostListScreenModel(get()) } factory { params -> PostDetailsScreenModel(postId = params.get(), repository = get()) } } // Screen class PostListScreen : Screen { @Composable override fun Content() { val screenModel: PostListScreenModel = koinScreenModel() // Navigator-scoped variant: // val sharedModel = LocalNavigator.currentOrThrow.getNavigatorScreenModel() } } ``` -------------------------------- ### BottomSheetNavigator Parameters Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/bottomsheet-navigation.md The BottomSheetNavigator accepts parameters similar to ModalBottomSheetLayout for customization. ```kotlin @Composable public fun BottomSheetNavigator( modifier: Modifier = Modifier, hideOnBackPress: Boolean = true, scrimColor: Color = ModalBottomSheetDefaults.scrimColor, sheetShape: Shape = MaterialTheme.shapes.large, sheetElevation: Dp = ModalBottomSheetDefaults.Elevation, sheetBackgroundColor: Color = MaterialTheme.colors.surface, sheetContentColor: Color = contentColorFor(sheetBackgroundColor), // ... ) ``` -------------------------------- ### Inject ScreenModel with @AssistedInject Source: https://github.com/adrielcafe/voyager/blob/main/docs/screenmodel/hilt-integration.md Use `@AssistedInject` for ScreenModels with constructor parameters. Provide a `ScreenModelFactory` annotated with `@AssistedFactory` and use `getScreenModel()` with the factory to create an instance. ```kotlin class PostDetailsScreenModel @AssistedInject constructor( @Assisted val postId: Long ) : ScreenModel { @AssistedFactory interface Factory : ScreenModelFactory { fun create(postId: Long): PostDetailsScreenModel } } ``` ```kotlin data class PostDetailsScreen(val postId: Long): AndroidScreen() { @Composable override fun Content() { val screenModel = getScreenModel { factory -> factory.create(postId) } // ... } } ``` -------------------------------- ### StateScreenModel for State Management Source: https://context7.com/adrielcafe/voyager/llms.txt Use StateScreenModel to combine ScreenModel with a StateFlow-backed state. The state is automatically tied to screenModelScope. Initialize with a default state like Loading. ```kotlin class PostDetailsScreenModel( private val repository: PostRepository ) : StateScreenModel(State.Loading) { sealed class State { object Loading : State() data class Success(val post: Post) : State() data class Error(val message: String) : State() } init { loadPost() } fun loadPost(id: Long = 1L) { screenModelScope.launch { mutableState.value = State.Loading mutableState.value = try { State.Success(repository.getPost(id)) } catch (e: Exception) { State.Error(e.message ?: "Unknown error") } } } } class PostDetailsScreen(private val postId: Long) : Screen { @Composable override fun Content() { val screenModel = rememberScreenModel { PostDetailsScreenModel(PostRepository()) } val state by screenModel.state.collectAsState() when (val s = state) { is PostDetailsScreenModel.State.Loading -> CircularProgressIndicator() is PostDetailsScreenModel.State.Success -> PostContent(s.post) is PostDetailsScreenModel.State.Error -> Text("Error: ${s.message}") } } } ``` -------------------------------- ### Inject ScreenModel with @Inject Source: https://github.com/adrielcafe/voyager/blob/main/docs/screenmodel/hilt-integration.md Annotate your ScreenModel with `@Inject` and use `getScreenModel()` to obtain an instance. Ensure `voyager-hilt` is imported. ```kotlin class HomeScreenModel @Inject constructor() : ScreenModel { // ... } ``` ```kotlin class HomeScreen : AndroidScreen() { @Composable override fun Content() { val screenModel = getScreenModel() // ... } } ``` -------------------------------- ### TabNavigator for Tab Navigation Source: https://context7.com/adrielcafe/voyager/llms.txt Implements tab navigation using `TabNavigator`. Each tab can manage its own back-stack using a nested `Navigator`. Ensure `TabNavigationItem` is used within the `BottomNavigation` for proper tab selection. ```kotlin // 1. Define a Tab object HomeTab : Tab { override val options: TabOptions @Composable get() { val title = "Home" val icon = rememberVectorPainter(Icons.Default.Home) return remember { TabOptions(index = 0u, title = title, icon = icon) } } @Composable override fun Content() { // Each tab owns its own Navigator for independent back-stacks Navigator(PostListScreen()) } } object ProfileTab : Tab { override val options: TabOptions @Composable get() = remember { TabOptions(index = 1u, title = "Profile", icon = rememberVectorPainter(Icons.Default.Person)) } @Composable override fun Content() { ProfileScreen().Content() } } // 2. Wire up TabNavigator with a Scaffold setContent { TabNavigator(HomeTab) { Scaffold( content = { padding -> Box(Modifier.padding(padding)) { CurrentTab() } // NOT tabNavigator.current.Content() }, bottomBar = { BottomNavigation { TabNavigationItem(HomeTab) TabNavigationItem(ProfileTab) } } ) } } // 3. Tab selector helper composable @Composable private fun RowScope.TabNavigationItem(tab: Tab) { val tabNavigator = LocalTabNavigator.current BottomNavigationItem( selected = tabNavigator.current == tab, onClick = { tabNavigator.current = tab }, icon = { Icon(painter = tab.options.icon!!, contentDescription = tab.options.title) } ) } // 4. Inside a tab screen, switch tabs or push within the tab class PostListScreen : Screen { @Composable override fun Content() { val navigator = LocalNavigator.currentOrThrow // push within HomeTab val tabNavigator = LocalTabNavigator.current // switch to sibling tab Button(onClick = { navigator.push(PostDetailsScreen(42L)) }) { Text("Open Post") } Button(onClick = { tabNavigator.current = ProfileTab }) { Text("Go to Profile") } } } ``` -------------------------------- ### Per-Screen Enter and Exit Transitions Source: https://github.com/adrielcafe/voyager/blob/main/docs/transitions-api.md Define custom enter and exit transitions for a specific screen by implementing the ScreenTransition interface. Use lastEvent to determine direction for slide animations. ```kotlin class ExampleSlideScreen : Screen, ScreenTransition { override val key: ScreenKey get() = uniqueScreenKey @Composable override fun Content() { ... } override fun enter(lastEvent: StackEvent): EnterTransition { return slideIn { val x = if (lastEvent == StackEvent.Pop) -size.width else size.width IntOffset(x = x, y = 0) } } override fun exit(lastEvent: StackEvent): ExitTransition { return slideOut { val x = if (lastEvent == StackEvent.Pop) size.width else -size.width IntOffset(x = x, y = 0) } } } ``` -------------------------------- ### Delegating Transitions in Screens Source: https://github.com/adrielcafe/voyager/blob/main/docs/transitions-api.md Use Kotlin delegates to apply reusable transition classes like SlideTransition or FadeTransition to your Screens. This simplifies transition management. ```kotlin class SlideScreen : Screen, ScreenTransition by SlideTransition() { @Composable override fun Content() { ... } } class FadeScreen : Screen, ScreenTransition by FadeTransition() { @Composable override fun Content() { ... } } ``` -------------------------------- ### Create ViewModel in Voyager Screen Source: https://github.com/adrielcafe/voyager/blob/main/docs/android-viewmodel/index.md Instantiate a ViewModel within a Voyager Screen's Composable content. Voyager provides the necessary owners to manage ViewModel lifecycle. ```kotlin class PostListScreen : Screen { @Composable override fun Content() { val viewModel = viewModel() // ... } } ``` -------------------------------- ### State-aware RxScreenModel Source: https://github.com/adrielcafe/voyager/blob/main/docs/screenmodel/rxjava-integration.md Extend `RxScreenModel` to provide a reactive state for your ScreenModel. Use `mutableState` to emit state updates and `subscribeAsState` in your Composable to observe them. ```kotlin class PostDetailsScreenModel( private val repository: PostRepository ) : RxScreenModel() { sealed class State { object Init : State() object Loading : State() data class Result(val post: Post) : State() } fun getPost(id: String) { repository.getPost(id) .doOnSubscribe { mutableState.onNext(State.Loading) } .subscribe { post -> mutableState.onNext(State.Result(post)) } .let(disposables::add) } } ``` ```kotlin class PostDetailsScreen : Screen { @Composable override fun Content() { val screenModel = rememberScreenModel() val state by screenModel.state.subscribeAsState(initial = State.Init) when (state) { is State.Loading -> LoadingContent() is State.Result -> PostContent(state.post) } } } ``` -------------------------------- ### ScreenModel for Scoped Business Logic Source: https://context7.com/adrielcafe/voyager/llms.txt Introduces `ScreenModel` for managing screen-specific business logic that persists across recompositions and is disposed when the screen is removed. Use `rememberScreenModel` to instantiate. ```kotlin // Basic ScreenModel class HomeScreenModel : ScreenModel { override fun onDispose() { // cleanup, e.g. cancel ongoing work } } class HomeScreen : Screen { @Composable override fun Content() { // Factory lambda is only called on first composition val screenModel = rememberScreenModel { HomeScreenModel() } // Multiple instances of the same type differentiated by tag: // val a = rememberScreenModel(tag = "A") { HomeScreenModel() } } } ``` -------------------------------- ### State-aware ScreenModel with Coroutines Source: https://github.com/adrielcafe/voyager/blob/main/docs/screenmodel/coroutines-integration.md Utilize `StateScreenModel` to manage UI state reactively. Use `coroutineScope.launch` to perform asynchronous operations and update the `mutableState` with different states like `Loading` or `Result`. ```kotlin class PostDetailsScreenModel( private val repository: PostRepository ) : StateScreenModel(State.Init) { sealed class State { object Init : State() object Loading : State() data class Result(val post: Post) : State() } fun getPost(id: String) { coroutineScope.launch { mutableState.value = State.Loading mutableState.value = State.Result(post = repository.getPost(id)) } } } ``` -------------------------------- ### Add Maven Central Repository Source: https://github.com/adrielcafe/voyager/blob/main/docs/setup.md Ensure Maven Central is available in your project's repositories. Add this to your module's build.gradle file. ```kotlin repositories { mavenCentral() } ``` -------------------------------- ### Serializable Screen Parameters Source: https://context7.com/adrielcafe/voyager/llms.txt Define screen parameters as Serializable for default state restoration. No special annotations are needed. ```kotlin data class Post(val id: Long, val title: String) : Serializable data class PostDetailsScreen(val post: Post) : Screen { @Composable override fun Content() { Text(post.title) } } ``` -------------------------------- ### Declare ScreenModel as Koin Factory Source: https://github.com/adrielcafe/voyager/blob/main/docs/screenmodel/koin-integration.md Declare your ScreenModels using the `factory` component in a Koin module. This ensures a new instance is created each time it's requested. ```kotlin val homeModule = module { factory { HomeScreenModel() } } ``` -------------------------------- ### Define a Screen with a Custom Stable Key Source: https://context7.com/adrielcafe/voyager/llms.txt When the same screen class needs to appear multiple times within a single `Navigator`, provide a unique `key` to distinguish instances. This ensures stable navigation state. ```kotlin // Custom stable key (required when the same screen class appears more than once in a Navigator) class HomeScreen : Screen { override val key: ScreenKey = uniqueScreenKey // or a hard-coded string @Composable override fun Content() { /* … */ } } ``` -------------------------------- ### Voyager Dependencies for build.gradle Source: https://github.com/adrielcafe/voyager/blob/main/docs/setup.md Include the necessary Voyager modules in your module's build.gradle file. Specify the voyagerVersion variable for easy updates. ```kotlin dependencies { val voyagerVersion = "1.1.0-beta02" // Multiplatform // Navigator implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion") // Screen Model implementation("cafe.adriel.voyager:voyager-screenmodel:$voyagerVersion") // BottomSheetNavigator implementation("cafe.adriel.voyager:voyager-bottom-sheet-navigator:$voyagerVersion") // TabNavigator implementation("cafe.adriel.voyager:voyager-tab-navigator:$voyagerVersion") // Transitions implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion") // Koin integration implementation("cafe.adriel.voyager:voyager-koin:$voyagerVersion") // Android // Hilt integration implementation("cafe.adriel.voyager:voyager-hilt:$voyagerVersion") // LiveData integration implementation("cafe.adriel.voyager:voyager-livedata:$voyagerVersion") // Desktop + Android // Kodein integration implementation("cafe.adriel.voyager:voyager-kodein:$voyagerVersion") // RxJava integration implementation("cafe.adriel.voyager:voyager-rxjava:$voyagerVersion") } ``` -------------------------------- ### Register Concrete Screens in Feature Module Source: https://context7.com/adrielcafe/voyager/llms.txt Register concrete screen implementations within a feature module using screenModule. This allows screens to be resolved by their SharedScreen provider. ```kotlin // :feature-posts module — registers concrete screens val featurePostsScreenModule = screenModule { register { PostListScreen() } register { PostDetailsScreen(id = provider.id) } } ``` -------------------------------- ### Automatic Unique Screen Key Generation Source: https://github.com/adrielcafe/voyager/blob/main/docs/state-restoration.md Use the `uniqueScreenKey` property provided by Voyager to automatically generate a unique key for your screen. This simplifies key management when custom keys are not strictly necessary. ```kotlin override val key = uniqueScreenKey ``` -------------------------------- ### Provide Navigator Lifecycle KMP Support Source: https://github.com/adrielcafe/voyager/blob/main/docs/android-viewmodel/viewmodel-kmp.md Call ProvideNavigatorLifecycleKMPSupport before all Navigator calls to enable ViewModel KMP functionality. This is required for the ViewModel KMP API to work. ```kotlin import androidx.compose.runtime.Composable import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.kmp.ProvideNavigatorLifecycleKMPSupport @Composable fun MainView() { ProvideNavigatorLifecycleKMPSupport { Navigator(...) } } class MyScreen : Screen { @Composable fun Content() { val myViewModel = viewModel { MyScreenViewModel() } } } ``` -------------------------------- ### Define Shared Screen Providers Source: https://github.com/adrielcafe/voyager/blob/main/docs/navigation/multi-module-navigation.md Declare shared screens using the `ScreenProvider` interface in a common navigation module. This makes screens accessible across different feature modules. ```kotlin sealed class SharedScreen : ScreenProvider { object PostList : SharedScreen() data class PostDetails(val id: String) : SharedScreen() } ``` -------------------------------- ### Implement UniqueScreen for Android Source: https://github.com/adrielcafe/voyager/blob/main/docs/migration-to-1.0.0.md Replace AndroidScreen implementations with this abstract class to ensure a unique screen key, preventing potential issues and unexpected behavior. ```kotlin abstract class UniqueScreen : Screen { override val key: ScreenKey = uniqueScreenKey } ```