๐Ÿ›ธVoyager is a pragmatic navigation library built for, and seamlessly integrated with, Jetpack Compose.

Overview

Maven metadata URL Android API kotlin ktlint License MIT


Voyager: Compose on Warp Speed

Voyager is a pragmatic navigation library built for, and seamlessly integrated with, Jetpack Compose.

Turn on the Warp Drive and enjoy the trek ๐Ÿ––

Features

Setup

Add the desired dependencies to your module's build.gradle:

dependencies {
    implementation "cafe.adriel.voyager:voyager-navigator:$currentVersion"
    implementation "cafe.adriel.voyager:voyager-tab-navigator:$currentVersion"
}

Current version: Maven metadata URL

Samples

Stack API Basic nav. Tab nav. Nested nav.
navigation-stack navigation-basic navigation-tab navigation-nested

Usage

Let's start by creating the screens: you should implement the Screen interface and override the Content() composable function. Screens can be data class (if you need to send params), class (if no param is required) or even object (useful for tabs).

object HomeScreen : Screen {

    @Composable
    override fun Content() {
        // ...
    }
}

class PostListScreen : Screen {

    @Composable
    override fun Content() {
        // ...
    }
}

data class PostDetailsScreen(val postId: Long) : Screen {

    @Composable
    override fun Content() {
        // ...
    }
}

Now, start the Navigator with the root screen.

class SingleActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            Navigator(HomeScreen)
        }
    }
}

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

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)
            }
        ) {
            // ...
        }
    }
}

Stack API

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.

val stack = mutableStateStackOf("๐Ÿ‡", "๐Ÿ‰", "๐ŸŒ", "๐Ÿ", "๐Ÿฅ", "๐Ÿ‹")
// ๐Ÿ‡, ๐Ÿ‰, ๐ŸŒ, ๐Ÿ, ๐Ÿฅ, ๐Ÿ‹

stack.lastOrNull
// ๐Ÿ‹

stack.push("๐Ÿ")
// ๐Ÿ‡, ๐Ÿ‰, ๐ŸŒ, ๐Ÿ, ๐Ÿฅ, ๐Ÿ‹, ๐Ÿ

stack.pop()
// ๐Ÿ‡, ๐Ÿ‰, ๐ŸŒ, ๐Ÿ, ๐Ÿฅ, ๐Ÿ‹

stack.popUntil { it == "๐Ÿ" }
// ๐Ÿ‡, ๐Ÿ‰, ๐ŸŒ, ๐Ÿ

stack.replace("๐Ÿ“")
// ๐Ÿ‡, ๐Ÿ‰, ๐ŸŒ, ๐Ÿ“

stack.replaceAll("๐Ÿ’")
// ๐Ÿ’

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

State restoration

The Screen interface is Serializable. Every param of your screens will be saved and restored automatically. Because of that, it's important to known what can be passed as param: anything that can be stored inside a Bundle.

// โœ”๏ธ DO
@Parcelize
data class Post(/*...*/) : Parcelable

data class ValidScreen(
    val userId: UUID, // Built-in serializable types
    val post: Post // Your own parcelable and serializable types
) : Screen {
    // ...
}

// ๐Ÿšซ DON'T
class Post(/*...*/)

data class InvalidScreen(
    val context: Context, // Built-in non-serializable types
    val post: Post // Your own non-parcelable and non-serializable types
) : Screen {
    // ...
}

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

// โœ”๏ธ DO
class ValidScreen : Screen {
    
    // Serializable properties
    val tag = "ValidScreen"
    
    // Lazily initialized serializable types
    val randomId by lazy { UUID.randomUUID() }

    // Lambdas
    val callback = { /*...*/ }
}

// ๐Ÿšซ DON'T
class InvalidScreen : Screen {

    // Non-serializable properties
    val postService = PostService()
}

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

// โœ”๏ธ DO
class ValidScreen : Screen {
    
    @Composable
    override fun Content() {
        // Inject your dependencies inside composables
        val postService = get<PostService>()
    }
}

// ๐Ÿšซ DON'T
class InvalidScreen : Screen {

    // Using DI to inject non-serializable types as properties
    val postService by inject<PostService>()
}

Lifecycle

Inside a Screen, you can call LifecycleEffect to listen for some events:

  • onStarted: called when the screen enters the composition
  • onDisposed: called when the screen is disposed
class PostListScreen : Screen {

    @Composable
    override fun Content() {
        LifecycleEffect(
            onStarted = { /*...*/ },
            onDisposed = { /*...*/ }
        )

        // ...
    }
}

Back press

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 {
    Navigator(
        initialScreen = HomeScreen,
        onBackPressed = { currentScreen ->
            false // won't pop the current screen
            // true will pop, default behavior
        }
        // To disable:
        // onBackPressed = null
    )
}

Deep links

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()

setContent {
    Navigator(
        HomeScreen,
        PostListScreen(),
        PostDetailsScreen(postId)
    )
}

Transitions

It's simple to add transition between screens: when initializing the Navigator you can override the default content. You can use, for example, the built-in Crossfade animation.

setContent {
    Navigator(HomeScreen) { navigator ->
        Crossfade(navigator.last) { screen ->
            screen.Content()
        }
    }
}

Want to use a custom animation? No problem, just follow the same principle.

setContent {
    Navigator(HomeScreen) { navigator ->
        MyCustomTransition {
            CurrentScreen()
        }
    }
}

Tab navigation

Voyager provides a handy abstraction over the Navigator and Screen: the TabNavigator and Tab.

The Tab interface, like the Screen, has a Content() function, but also a title and an optional icon. Since tabs aren't usually reused, its OK to create them as object.

object HomeTab : Tab {

    override val title: String
        @Composable get() = stringResource(R.string.home)

    override val icon: Painter
        @Composable get() = rememberVectorPainter(Icons.Default.Home)

    @Composable
    override fun Content() {
        // ...
    }
}

The TabNavigator unlike the Navigator:

  • Don't handle back presses, because the tabs are siblings
  • Don't exposes the Stack API, just a current property

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

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

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

@Composable
private fun RowScope.TabNavigationItem(tab: Tab) {
    val tabNavigator = LocalTabNavigator.current

    BottomNavigationItem(
        selected = tabNavigator.current == tab,
        onClick = { tabNavigator.current = tab },
        icon = { Icon(painter = tab.icon, contentDescription = tab.title) }
    )
}

Nested navigation

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's go back to the previous example.

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

But now, the HomeTab will have it's own Navigator.

object HomeTab : Screen {

    @Composable
    override fun Content() {
        Navigator(PostListScreen())
    }
}

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

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

Going a little further, it's 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.

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

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

Credits

Comments
  • How to multiple instance viewmodel for same compose fun

    How to multiple instance viewmodel for same compose fun

    I have a list of items and a detail screen. Detail Screen is a compose function that takes a (post) parameter that I pass as an argument to the ViewModel. val viewModel = getStateViewModel<PostDetailViewModel>(parameters = { parametersOf(args) }) The problem is that it looks like the viewModel is cached, and all the time it opens the first post (argument) that was followed.

    In jetpack navigation works correctly.

    https://user-images.githubusercontent.com/27068529/129693071-ade8b0bc-1c0e-4bc0-a367-cceac22c4e53.mp4

    opened by zakrodionov 17
  • [Hilt] [ViewModel] popUntil not working as expected?

    [Hilt] [ViewModel] popUntil not working as expected?

    Observed behavior:

    • 3 destinations A, B, C - all AndroidScreen with Hilt ViewModels.
    • Pushing A -> B -> C

    When trying to use popUntil to A from C - the B screen and it's ViewModel are not being removed. If I try to push B again from A the ViewModel is the same instance holding latest state.

    If I try to go A -> B -> pop -> A -> B everything is fine.

    Is this expected? Propbably not - I'd expect behaviour described in docs: image image

    opened by michaldrabik 13
  • screen model doesn't survive the configuration changes

    screen model doesn't survive the configuration changes

    I try use Screen + ScreenModel + Hilt.

    I create Screen like this:

    object SearchScreen : Screen {
    
        @Composable
        override fun Content() {
            val screenModel = getScreenModel<SearchScreenModel>()
            Log.e("!!!!","!!!!Use screenmodel: $screenModel")
            SearchScreen(screenModel)
        }
    }
    

    And my ScreenModel:

    class SearchScreenModel @Inject constructor(
        private val searchUseCase: SearchUseCase
    ) : ScreenModel {
       //Some code
    }
    

    But when i rotate my phone screen model recreated. Shouldn't the screen model survive the configuration changes? OR am I doing something wrong?

    opened by Anton111111 11
  • Beta13  hiltviewmodel crash when navigating back to same destination

    Beta13 hiltviewmodel crash when navigating back to same destination

    So the fix for the replace issue have now generated a worse issue.

        java.lang.IllegalArgumentException: SavedStateProvider with the given key is already registered
            at androidx.savedstate.SavedStateRegistry.registerSavedStateProvider(SavedStateRegistry.java:111)
            at androidx.lifecycle.SavedStateHandleController.attachToLifecycle(SavedStateHandleController.java:50)
            at androidx.lifecycle.SavedStateHandleController.create(SavedStateHandleController.java:70)
            at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:67)
            at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:84)
            at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:109)
            at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:171)
            at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:139)
    

    it will crash whenever you navigate again to a destination.

    So push(Screen) pop() push(Screen) will crash.

    As said in https://github.com/adrielcafe/voyager/issues/17 the root issue is more about transitions and autodispose happening too early.

    There's side effects too with ScreenModel that are disposed too early (even before the next screen is composed). This can corrupt what is displayed during transition and cause hard to diagnose side effects.

    This is more a core issue for @adrielcafe than an hilt specific one. (The "fix" stills needs to be reverted) While we can disable autodispose at the navigator level we have no way to built custom transitions that would do a proper dispose after the end of the transition.

    bug enhancement 
    opened by Tolriq 7
  • Wrap Navigator with AnimatedVisibility produce reset navigator to initial screen

    Wrap Navigator with AnimatedVisibility produce reset navigator to initial screen

    Hi, many thanks for your library. I have tried it a bit and it's very powerful.

    I have noticed when I wrap Navigator with AnimatedVisibility it is reset to the initial screen.

    I created my own scaffold with slots, I need to use different navigators in each slot. In one slot in the scaffold root, I need to use AnimatedVisibility to produce hide/collapse animation, and in this slot I need to maintain some navigation. So after I create navigation in that AnimatedVisibility composable I have noticed that when it exit to the invisible state navigator also resets its state to the initial state.

    Can you provide some ideas of how I can achieve some animation around box that contains navigator ?

    opened by Alexbeard 7
  • Lifecycle onPause/onStop/onDestory are never called

    Lifecycle onPause/onStop/onDestory are never called

    While testing my app with Voyager I noticed some of my AndroidView states were never being saved. Investigating that lead me to my LifecycleObserver's never filling the bundle contained in my rememberSavable variable. I then logged the states that the lifecycle observer goes through and noticed that only onCreate/OnStart/OnResume are ever called, and never the last 3.

    Here are the test composables I used

    @Composable
    private fun rememberLoggingLifecycleObserver(): LifecycleEventObserver {
        val bundle = rememberSaveable { Bundle() }
        return remember {
            LifecycleEventObserver { _, event ->
                when (event) {
                    Lifecycle.Event.ON_CREATE -> {
                        logcat("Lifecycle") { "OnCreate" }
                        logcat("Lifecycle") { bundle.keySet().joinToString() }
                        bundle.clear()
                    }
                    Lifecycle.Event.ON_START -> logcat("Lifecycle") { "OnStart" }
                    Lifecycle.Event.ON_RESUME -> logcat("Lifecycle") { "OnResume" }
                    Lifecycle.Event.ON_PAUSE -> logcat("Lifecycle") { "OnPause" }
                    Lifecycle.Event.ON_STOP -> {
                        logcat("Lifecycle") { "OnStop" }
                        bundle.putAll(bundleOf("Yes" to "1200"))
                    }
                    Lifecycle.Event.ON_DESTROY -> logcat("Lifecycle") { "OnDestroy" }
                    else -> throw IllegalStateException()
                }
            }
        }
    }
    
    @Composable
    fun SetupLoggerLifecycleObserver() {
        val loggerObserver = rememberLoggingLifecycleObserver()
        val lifecycle = LocalLifecycleOwner.current.lifecycle
        DisposableEffect(lifecycle) {
            lifecycle.addObserver(loggerObserver)
            onDispose {
                // lifecycle.removeObserver(loggerObserver)
            }
        }
    }
    
    bug 
    opened by Syer10 6
  • Hilt integration doesn't support Screen classes

    Hilt integration doesn't support Screen classes

    I don't if that is intentional.Hilt integration only works with AndroidScreen. Also, docs seems incomplete... There is a sample project for that though.

    opened by xinkev 6
  • 1.0 RC01 side effect on tab navigation

    1.0 RC01 side effect on tab navigation

    The fix (https://github.com/adrielcafe/voyager/pull/55) have the side effect of now fully unloading the view models when navigating between tabs.

    While the fix is normal and wanted, due to the current limitations of LazyList not proposing any proper way to delay the state restoration, this makes restoring scroll position insanely complex until they propose something.

    It would be nice to have a way to opt out or a workaround for this use case.

    opened by Tolriq 5
  • Re: Wanted to show you project I made with your library

    Re: Wanted to show you project I made with your library

    Hi @adrielcafe, I wanted to show you my sample app I made with Voyager: https://github.com/JohnBuhanan/MVI-Public

    I studied a dozen github repos and felt like yours was closest to what I wanted.

    Three things I thought you might find interesting:

    1. I wrapped your Navigator so that I can do routing from ViewModels.
    2. Each feature contains an api gradle module and an impl gradle module. The impls can be commented out from settings.gradle and app.gradle to unload them from Android Studio indexing and gradle builds. This allows scaling for massively multi-module apps.
    3. I used reflection to get access to the "internal factories" of ScreenRegistry.

    Biggest thing I am thinking about now is best way to "navigate for result".

    opened by JohnBuhanan 5
  • Accessing Tab's navigator through TabNavigator

    Accessing Tab's navigator through TabNavigator

    I've been using your library for a month or something, and I'm pretty satisfied of it. It's cool!

    But there's a thing. I want my app to have the following behaviour: when user clicks on a tab in BottomNavigation and the tab isn't selected yet, the library just switches Tabs, but when the tab is already selected, I want my app to reset navigation state of the tab (i.e. pop navigator to root).

    I tried to implement the behaviour myself though, but the problem is that I can't access almost anything from TabNavigator... image

    Did I miss something? Any help will be appreciated. Thanks in advance!

    opened by 4nk1r 4
  • Screen Model test Exception

    Screen Model test Exception

    
        fun login(email: String, password: String) = coroutineScope.launch {
            val userCredentials = UserCredentials(email, password)
            logInUseCase.invoke(userCredentials)
        }
    
        @Test
        fun `on login success state is SignIn`() = runTest {
            loginModel.login("email", "password")
            loginModel.state
                .take(1)
                .onEach { assertEquals(it::class.simpleName, AuthState.SignedIn::class.simpleName) }
                .collect()
        }
    
    java.lang.IllegalStateException: ScreenModel not found: ui.login.LoginModel
    	at cafe.adriel.voyager.core.model.ScreenModelStore.getDependencyKey(ScreenModelStore.kt:39)
    	at com.netguru.common.ScreenExtensionsKt.getModelScope(ScreenExtensions.kt:29)
    	at ui.login.LoginModel.login(LoginModel.kt:19)
    	at ui.LoginModelTest$on login success state is SignIn$1.invokeSuspend(LoginModelTest.kt:40)
    	at ui.LoginModelTest$on login success state is SignIn$1.invoke(LoginModelTest.kt)
    	at ui.LoginModelTest$on login success state is SignIn$1.invoke(LoginModelTest.kt)
    	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTestCoroutine$2.invokeSuspend(TestBuilders.kt:208)
    	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTestCoroutine$2.invoke(TestBuilders.kt)
    	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTestCoroutine$2.invoke(TestBuilders.kt)
    	at kotlinx.coroutines.intrinsics.UndispatchedKt.startCoroutineUndispatched(Undispatched.kt:55)
    	at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:112)
    	at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
    	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTestCoroutine(TestBuilders.kt:207)
    	at kotlinx.coroutines.test.TestBuildersKt.runTestCoroutine(Unknown Source)
    	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$1$1.invokeSuspend(TestBuilders.kt:167)
    	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$1$1.invoke(TestBuilders.kt)
    	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$1$1.invoke(TestBuilders.kt)
    	at kotlinx.coroutines.test.TestBuildersJvmKt$createTestResult$1.invokeSuspend(TestBuildersJvm.kt:13)
    	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
    	at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
    	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
    	at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
    	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
    	at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
    	at kotlinx.coroutines.test.TestBuildersJvmKt.createTestResult(TestBuildersJvm.kt:12)
    	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTest(TestBuilders.kt:166)
    	at kotlinx.coroutines.test.TestBuildersKt.runTest(Unknown Source)
    	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTest(TestBuilders.kt:154)
    	at kotlinx.coroutines.test.TestBuildersKt.runTest(Unknown Source)
    	at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTest$default(TestBuilders.kt:147)
    	at kotlinx.coroutines.test.TestBuildersKt.runTest$default(Unknown Source)
    	at ui.LoginModelTest.on login success state is SignIn(LoginModelTest.kt:39)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
    	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
    	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
    	at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
    	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
    	at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
    	at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:176)
    	at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
    	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
    	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
    	at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
    	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
    	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
    	at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
    	at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
    
    

    I can't use rememberScreenModel or ScreenModelStore.getOrPut in the test so that's why the error is thrown. Any idea how to work around this? @adrielcafe @terrakok @programadorthi @DevSrSouza

    opened by ynsok 4
  • Tab transitions

    Tab transitions

    Hello. It would be great if we could add transitions to TabNavigator content in the same manner as we do for normal screens:

    TabNavigator(initialMainTab.tab) { tabNavigator ->
        Scaffold(
            content = {
                Box(Modifier.padding(it)) {
                    // Instead of CurrentTab(), use
                    FadeTabTransition(tabNavigator)
                }
            },
            bottomBar = {
                // BottomNavigation(...)
            }
        )
    }
    

    Would that be possible to implement?

    opened by arslan-charyyev-pc 0
  • Add `tag` argument to `Screen.getScreenModel`

    Add `tag` argument to `Screen.getScreenModel`

    Hello. My use case involves creating multiple instances of the same ScreenModel via hilt's assissted injection, as demonstrated here: https://voyager.adriel.cafe/screenmodel/hilt-integration#assistedinject. The issue is that the current Screen.getScreenModel function does not allow us to specify a tag to differentiate screen models. This PR is designed to address that.

    opened by arslan-charyyev-pc 0
  • SavedStateHandle doesn't work

    SavedStateHandle doesn't work

    I save the screen state in the SavedStateHandle in the viewmodel, but after recreating the activity, the SavedStateHandle has null data. Version: 1.0.0-rc03

    opened by zakrodionov 0
  • iOS / Full Multiplatform Support?

    iOS / Full Multiplatform Support?

    Getting official support for ios, and the other kotlin multiplatform targets would be great. It seems like people are forking the library to add this. It'd be great if the main project just did it.

    opened by ScottPierce 0
  • Allow custom navigators to dispose, such as the TabNavigator

    Allow custom navigators to dispose, such as the TabNavigator

    Make navigator.dispose public and implement it in the TabNavigator

    navigator.dispose should be public to allow custom navigators to cleanup thier screens properly and remove any possible memory leaks. I use a custom navigator in my application and it was a leak mess until I added this to my fork.

    Also added the current tab to saveableState, allowing for more custom implementations

    opened by Syer10 0
  • Implement SavedStateHandle in a working manner

    Implement SavedStateHandle in a working manner

    Closes #99 SavedStateHandle support didn't work since the outstate was always null or never got saved outside the screen lifecycle owner. I found a way to test SavedStateHandle to get it working how its meant to be used.

    1. Put the app in the background for a few seconds, at least until the activity hits the stop state
    2. Use the (old) android studio logcat's menu to use its unique terminate app button
    3. When starting up the app should try to return to its old state with SavedStateHandles and everything

    Whats left to do for Android lifecycle integration(will happen in other PRs):

    • Attach the lifecycle to the parent lifecycle, so if the activity hits onStop, the ScreenLifecycle will as well
    opened by Syer10 3
Releases(1.0.0-rc03)
  • 1.0.0-rc03(Nov 24, 2022)

    First of all, sorry for being so absent the last few months. I plan to return to working on Voyager in January, but I will gladly accept PRs anytime!

    I want to thank @Syer10 for his great work on fixing bugs, we are closer to a stable 1.0 release because of you โค๏ธ

    • Check if the activity is changing configurations before clearing the navigator (#84) by @Syer10
    • Handle Lifecycle.Event instead of managing the states themselves, include a onStart and onStop function to say when the screen is displayed (#87) by @Syer10
    • Add support for HasDefaultViewModelProviderFactory by returning a SavedStateViewModelFactory (#88) by @Syer10
    • Creates replaceAll(items: List<Item>) allowing a step to replace the navigation stack with another stack (#95) by @Alaksion
    • Updated dependencies
      • Kotlin 1.7.10
      • Jetpack Compose 1.3.1
      • Compose Multiplatform 1.2.1
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-rc02(Apr 30, 2022)

    • Added a disposeNestedNavigators param on TabNavigator to control the disposal behavior of nested navigators inside tabs (#58)
    • AndroidScreenLifecycleOwner now provides a LocalLifecycleOwner and emits onPause/onStop/onDestroy states (#42)
    • Downgraded jvmTarget to 1.8 for better compatibility
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-rc01(Apr 25, 2022)

    • Moved Kodein integration from jvmMain to commonMain (#56)
    • Fixed ScreenModel disposal on root screens (#24) by @programadorthi
    • Updated dependencies
      • Jetpack Compose 1.1.1
      • Compose Multiplatform 1.1.1
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta16(Feb 21, 2022)

  • 1.0.0-beta15(Jan 30, 2022)

    • Fixed broken builds caused by multiplatform (#34) by @DevSrSouza
    • Migrate modules from jvmMain to commonMain (#40) by @DevSrSouza
    • Migrate the buildscript from Groovy to Kotlin (#38) by @DevSrSouza
    • Migrate voyager-rxjava to multiplatform (#36) by @terrakok
    • Update Jetpack Compose to 1.1.0-rc03
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta14(Dec 20, 2021)

    • Compose Multiplatform support (#33) by @terrakok
    • Fixes for Hilt integration (#21, #22) by @programadorthi
    • Fixes for BottomSheetNavigator (#28, #32) by @Tolriq
    • Updated dependencies
      • Kotlin 1.6.10
      • Jetpack Compose 1.1.0-rc01
      • Compose Multiplatform 1.0.1-rc2
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta13(Oct 19, 2021)

  • 1.0.0-beta12(Oct 6, 2021)

  • 1.0.0-beta11(Sep 17, 2021)

  • 1.0.0-beta10(Sep 3, 2021)

  • 1.0.0-beta09(Aug 27, 2021)

  • 1.0.0-beta08(Aug 25, 2021)

    • Updated Compose to 1.1.0-alpha02 (now requires targetSdk 31)
    • Replaced custom transition implementation with AnimatedContent
    • Added support for SavedStateHandle (#7)
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta07(Aug 24, 2021)

  • 1.0.0-beta06(Aug 23, 2021)

    • Fixed ViewModel integration (#7)
    • Added voyager-androidx module that provides AndroidScreen (docs)
    • Added Screen.uniqueScreenKey extension property to generate screen keys (docs)
    • Added hooks support (docs)
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta05(Aug 9, 2021)

  • 1.0.0-beta04(Aug 8, 2021)

  • 1.0.0-beta03(Aug 7, 2021)

    • Updated Kotlin to 1.5.21 and Compose to 1.0.1
    • Added TabOptions and deprecated Tab.title and Tab.icon
    • Replaced NavigatorCache with SaveableStateHolder (#4 and #5, special thanks to @Grouen and @andkulikov!)
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta02(Jul 30, 2021)

    • Updated Compose to 1.0.0 :tada:
    • Added transitions, initially with fade, scale and slide animations
    • Added StackEvent and Stack.lastEvent
    • Added Stack.lastItemOrNull and Navigator.lastItem
    • Deprecated Stack.lastOrNull and Navigator.last
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta01(Jul 30, 2021)

Owner
Adriel Cafรฉ
In a love-hate relationship with Android for a decade
Adriel Cafรฉ
Bottom-App-Bar-with-Bottom-Navigation-in-Jetpack-compose-Android - Bottom App Bar with Bottom Navigation in Jetpack compose

Bottom-App-Bar-with-Bottom-Navigation-in-Jetpack-compose-Android This is simple

Shruti Patel 1 Jul 11, 2022
New style for app design simple bottom navigation with side navigation drawer UI made in Jetpack Compose.๐Ÿ˜‰๐Ÿ˜Ž

BottomNavWithSideDrawer New style for app design simple bottom navigtaion with side navigation drawer UI made in Jetpack Compose. ?? ?? (Navigation Co

Arvind Meshram 5 Nov 24, 2022
Alligator is a modern Android navigation library that will help to organize your navigation code in clean and testable way.

Alligator Alligator is a modern Android navigation library that will help to organize your navigation code in clean and testable way. Features Any app

Artur Artikov 290 Dec 9, 2022
[ACTIVE] Simple Stack, a backstack library / navigation framework for simpler navigation and state management (for fragments, views, or whatevers).

Simple Stack Why do I want this? To make navigation to another screen as simple as backstack.goTo(SomeScreen()), and going back as simple as backstack

Gabor Varadi 1.3k Jan 2, 2023
A small and simple, yet fully fledged and customizable navigation library for Jetpack Compose

A small and simple, yet fully fledged and customizable navigation library for Jetpack Compose

Vitali Olshevski 290 Dec 29, 2022
Animated Tab Bar is an awesome navigation extension that you can use to add cool, animated and fully customizable tab navigation in your apps

Animated Tab Bar is an awesome navigation extension that you can use to add cool, animated and fully customizable tab navigation in your apps. The extension provides handy methods and properties to change the behaviour as well as the appearance of the navigation bar.

Zain Ul Hassan 4 Nov 30, 2022
A small navigation library for Jetpack Compose with state saving, backstack and animations support.

A small navigation library for Jetpack Compose with state saving, backstack and animations support.

Xinto 5 Dec 27, 2022
DSC Moi University session on using Navigation components to simplify creating navigation flow in our apps to use best practices recommended by the Google Android Team

Navigation Components Navigate between destination using safe args How to use the navigation graph and editor How send data between destinations Demo

Breens Mbaka 6 Feb 3, 2022
Navigation Component: THE BEST WAY to create navigation flows for your app

LIVE #017 - Navigation Component: A MELHOR FORMA de criar fluxos de navegaรงรฃo para o seu app! Cรณdigo fonte do projeto criado na live #017, ensinando c

Kaique Ocanha 4 Jun 15, 2022
Navigation Drawer Bottom Navigation View

LIVE #019 - Toolbar, Navigation Drawer e BottomNavigationView com Navigation Com

Kaique Ocanha 6 Jun 15, 2022
A small navigation library for Android to ease the use of fragment transactions & handling backstack (also available for Jetpack Compose).

A small navigation library for Android to ease the use of fragment transactions & handling backstack (also available for Jetpack Compose).

Kaustubh Patange 88 Dec 11, 2022
A library that you can use for bottom navigation bar. Written with Jetpack Compose

FancyBottomNavigationBar A library that you can use for bottom navigation bar. W

Alperen ร‡evlik 3 Jul 27, 2022
Implementing bottom navigation in jetpack compose

Compose-Bottom-Navigation Implementing bottom navigation in jetpack compose Add the Navigation dependency Open the app's build file, found at app/buil

Steve Chacha 5 Dec 26, 2021
๐ŸŽ‰ [Android Library] A light-weight library to easily make beautiful Navigation Bar with ton of ๐ŸŽจ customization option.

Bubble Navigation ?? A light-weight library to easily make beautiful Navigation Bars with a ton of ?? customization options. Demos FloatingTopBarActiv

Gaurav Kumar 1.7k Dec 31, 2022
CustomNavigationDrawer - Custom Navigation Drawer with compose

An easy sample to explore navigation component and navigation drawer from Jetpac

rafael altamirano 0 Jan 4, 2022
Navigation for compose multiplatform

Navigation for compose multiplatform

Rachieru Dragos 1 Apr 11, 2022
A simple, highly customizable compose navigation component for Android & Desktop platform.

ไป‹็ป ไธ€ไธช็ฎ€ๅ•ๅนถๆไพ›้ซ˜ๅบฆๆ‰ฉๅฑ•ๅŠŸ่ƒฝ็š„ Compose ๅฏผ่ˆช็ป„ไปถ๏ผŒๅŒๆ—ถๆ”ฏๆŒ Android ๅ’Œ Desktop ๅนณๅฐใ€‚ ๅธธ็”จๅŠŸ่ƒฝ ไฝฟ็”จ ไธ‹่ฝฝ // Android implementation("io.github.succlz123:compose-screen-android:0.0.1") //

Ning 15 Nov 24, 2022
AndroidBriefActions - Android library for sending and observing non persistent actions such as showing a message; nice readable way to call navigation actions from ViewModel or Activity/Fragment.

implementation "com.vladmarkovic.briefactions:briefactions:$briefActionsVersion" Benefits Why use brief-actions library pattern: Prevent short-term ac

null 2 Dec 22, 2022
A simple navigation library for Android ๐Ÿ—บ๏ธ

Enro ??๏ธ A simple navigation library for Android "The novicesโ€™ eyes followed the wriggling path up from the well as it swept a great meandering arc ar

Isaac Udy 216 Dec 16, 2022