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

Overview

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

  • Full type-safety
  • State restoration
  • Nested navigation with independent backstacks
  • Own lifecycle, saved state and view models for every backstack entry
  • Animated transitions
  • Navigation logic may be easily moved to the ViewModel layer
  • No builders, no obligatory superclasses for your composables
  • May be used for managing dialogs

Getting started

Add a single dependency to your project:

implementation("dev.olshevski.navigation:reimagined:1.0.0-beta01")

Define a set of screens, for example, as a sealed class:

sealed class Screen : Parcelable {

    @Parcelize
    object First : Screen()

    @Parcelize
    data class Second(val id: Int) : Screen()

    @Parcelize
    data class Third(val text: String) : Screen()

}

Create a composable with NavController and NavHost:

Column { Text("Second screen: ${screen.id}") Button(onClick = { navController.navigate(Screen.Third(text = "Hello")) }) { Text("To Third screen") } } is Screen.Third -> { Text("Third screen: ${screen.text}") } } } }">
@Composable
fun NavHostScreen() {
    val navController = rememberNavController<Screen>(
        startDestination = Screen.First,
    )

    NavBackHandler(navController)

    NavHost(controller = navController) { screen ->
        when (screen) {
            Screen.First -> Column {
                Text("First screen")
                Button(onClick = {
                    navController.navigate(Screen.Second(id = 42))
                }) {
                    Text("To Second screen")
                }
            }
            is Screen.Second -> Column {
                Text("Second screen: ${screen.id}")
                Button(onClick = {
                    navController.navigate(Screen.Third(text = "Hello"))
                }) {
                    Text("To Third screen")
                }
            }
            is Screen.Third -> {
                Text("Third screen: ${screen.text}")
            }
        }
    }
}

As you can see, NavController is used for switching between screens, NavBackHandler handles the back presses and NavHost simply provides a composable corresponding to the latest destination in the backstack. As simple as that.

Basics

Here is the general workflow of the library:

Let's go into details about each of them.

NavController

This is the main control point of navigation. It keeps record of all current backstack entries and preserves them on activity/process recreation.

NavController may be created with rememberNavController within composition or with navController outside of it. The latter may be used for storing NavController in a ViewModel. As it implements Parcelable interface, it may be (and should be) stored in SavedStateHandle.

Both rememberNavController and navController methods accept startDestination as a parameter. If you want to create NavController with an arbitrary number of backstack items, you may use initialBackstack parameter instead.

Destinations

NavController accepts all types meeting the requirements as destinations. The requirements are:

  1. The type must be either Parcelable, or Serializable, or primitive, or of any other type that can be written to Parcel.

  2. The type must be either Stable, or Immutable, or primitive.

Navigation methods

There is a handful of pre-defined methods suitable for a basic app navigation: navigate, pop, popUpTo, popAll, replaceLast, replaceUpTo, replaceAll. They all are pretty much self-explanatory.

If your use-case calls for some advanced backstack manipulations, you may use setNewBackstackEntries method. In fact, this is the only public method defined in NavController, all other methods are provided as extensions and use setNewBackstackEntries under the hood. You may see how a new extension method navigateToTab is implemented in the sample.

NavBackstack

This is a read-only class that you may use to access current backstack entries. It is backed up by MutableState, so it will notify Compose about changes.

If you want to listen for backstack changes outside of composition you may set onBackstackChange listener in NavController.

NavHost

NavHost is a composable that shows the last entry of a backstack and provides all components associated with this particular entry: Lifecycle, SavedStateRegistry and ViewModelStore. All these components are provided through CompositionLocalProvider inside the corresponding owners LocalLifecycleOwner, LocalSavedStateRegistryOwner and LocalViewModelStoreOwner.

The components are kept around until its associated entry is removed from the backstack (or until the parent entry containing the current child NavHost is removed).

NavHost by itself doesn't provide any animated transitions, it simply jump-cuts to the next destination.

AnimatedNavHost

AnimatedNavHost includes all functionality of the regular NavHost, but also supports animated transitions. Default transition is a simple crossfade, but you can granularly customize every transition with your own AnimatedNavHostTransitionSpec implementation.

Here is one possible implementation of AnimatedNavHostTransitionSpec:

val CustomTransitionSpec = AnimatedNavHostTransitionSpec<Any?> { action, from, to ->
    val direction = when (action) {
        is NavAction.Backward -> AnimatedContentScope.SlideDirection.End
        is NavAction.Forward -> AnimatedContentScope.SlideDirection.Start
    }
    slideIntoContainer(direction) with slideOutOfContainer(direction)
}

Set it into AnimatedNavHost:

AnimatedNavHost(
    controller = navController,
    transitionSpec = CustomTransitionSpec
) { destination ->
    // ...
}

and it'll end up looking like this:

In AnimatedNavHostTransitionSpec you get the parameters:

  • action - the hint about the last NavController method that changed the backstack
  • from - the previous visible destination
  • to - the target visible destination

This information is plenty enough to choose a transition for every possible combination of screens and navigation actions.

NavAction

Here is the diagram of NavAction type hierarchy:

Pop, Replace and Navigate are objects that correspond to pop…, replace…, navigate methods of NavController. You may either check for these specific types or handle general Forward and Backward actions.

You can also create new action types by extending abstract classes Forward and Backward. Pass these new types into setNewBackstackEntries method of NavController and handle them in AnimatedNavHostTransitionSpec.

DialogNavHost

The version of NavHost that is better suited for showing dialogs. It is based on AnimatedNavHost and provides smoother transition between dialogs without scrim/fade flickering.

If you want to see how you can implement dialogs navigation explore the sample.

Note that DialogNavHost doesn't wrap your composables in a Dialog. You need to use use either Dialog or AlertDialog composable inside a contentSelector yourself.

Back handling

Back handling in the library is opt-in, rather than opt-out. By itself, neither NavController nor NavHost handles the back button press. You can add NavBackHandler or usual BackHandler in order to react to the back presses where you need to.

NavBackHandler is the most basic implementation of BackHandler that calls pop until one item in the backstack is left. Then it is disabled, so any upper-level BackHandler may react to the back button press.

Important note: always place your NavBackHandler/BackHandler before the corresponding NavHost. Read the explanation here.

Nested navigation

Adding nested navigation is as simple as placing one NavHost into another. Everything is handled correctly and just works.

You may go as many layers deep as you want. It's like fractals, but in navigation.

Return values to previous destinations

As destination types are not strictly required to be Immutable, you may change them while they are in the backstack. This may be used for returning values from other destinations. Just make a mutable property backed up by mutableStateOf and change it when required. Again, you may see demo of this in the sample.

Note: In general, returning values to the previous destination makes the navigation logic more complicated, so use it with caution and when you are sure what you are doing. Sometimes it may be easier to use a shared state holder.

Documentation and sample

Explore the KDoc documentation of the library for more details about every component and every supported features.

Also, explore the sample. It provides demos of all the functionality mentioned above and even more. The sample shows:

  • nested navigation
  • tab navigation
  • NavHost/AnimatedNavHost usage
  • dialogs
  • passing and returning values
  • ViewModels
  • hoisting NavController to the ViewModel layer

Why beta

I'm very satisfied with the shape and form of the library. I have spent long sleepless nights debugging and polishing all corner cases.

For now I'll be glad to hear a feedback and do a minor fine-tunings of the API (if any at all). If there are any changes you may expect a notice in release notes.

About

I've been thinking about Android app architecture and navigation in particular for the longest time. When introduced to Compose I could finally create the navigation structure that fits perfectly all my needs.

Making it in the form of a public library closes a gestalt for me. I'm finally done with it. Onto new projects!

If you like this library and find it useful, please star the project and share it with your fellow developers. A little bit of promotion never hurts.

Comments
  • Obtaining ViewModel for previous destinations on back stack

    Obtaining ViewModel for previous destinations on back stack

    With androidx.navigation you have getBackStackEntry method that returns implementation of ViewModelStoreOwner interface for any destination that is currently on back stack. This is handy for sharing state between parent and child screens without having to scope ViewModel to activity (which makes your view model live too long if you want to share view model from destination that is not root).

    AFAIK there is no API for this in this library - you can only get ViewModelStoreOwner for current destination or whole Activity.

    opened by equeim 7
  • BottomSheet support

    BottomSheet support

    Hello, I cannot find any mention of bottomsheet. How does it work with this library? Is it expected to use DialogNavigator to show those bottomsheet? I expect that bottomsheet is a bit more complicated as some swiping down should skip further animation (the swipe already animated the destination removal or it should continue animating). Thank you

    opened by hrach 6
  • Restarter must be created only during owner's initialization stage

    Restarter must be created only during owner's initialization stage

    I'm getting the following error and I'm not sure what is causing it:

    java.lang.IllegalStateException: Restarter must be created only during owner's initialization stage
            at androidx.savedstate.SavedStateRegistryController.performRestore(SavedStateRegistryController.java:58)
            at dev.olshevski.navigation.reimagined.NavComponentEntry.restoreState$reimagined_release(NavComponentEntry.kt:83)
            at dev.olshevski.navigation.reimagined.NavComponentHolder.newComponentEntry(NavComponentHolder.kt:148)
            at dev.olshevski.navigation.reimagined.NavComponentHolder.access$newComponentEntry(NavComponentHolder.kt:75)
            at dev.olshevski.navigation.reimagined.NavComponentHolder$lastComponentEntry$1.invoke(NavComponentHolder.kt:101)
            at dev.olshevski.navigation.reimagined.NavComponentHolder$lastComponentEntry$1.invoke(NavComponentHolder.kt:97)
            at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:1798)
            at androidx.compose.runtime.DerivedSnapshotState.currentRecord(DerivedState.kt:117)
            at androidx.compose.runtime.DerivedSnapshotState.getCurrentValue(DerivedState.kt:167)
            at androidx.compose.runtime.DerivedSnapshotState.getValue(DerivedState.kt:161)
            at dev.olshevski.navigation.reimagined.NavComponentHolder.setPostTransitionLifecycleStates(NavComponentHolder.kt:171)
            at dev.olshevski.navigation.reimagined.NavComponentHolder.onTransitionFinish(NavComponentHolder.kt:162)
            at dev.olshevski.navigation.reimagined.BaseNavHostKt$BaseNavHost$1$invoke$$inlined$onDispose$1.dispose(Effects.kt:484)
            at androidx.compose.runtime.DisposableEffectImpl.onForgotten(Effects.kt:85)
            at androidx.compose.runtime.CompositionImpl$RememberEventDispatcher.dispatchRememberObservers(Composition.kt:793)
            at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:647)
            at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:488)
            at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:425)
            at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame(AndroidUiFrameClock.android.kt:34)
            at androidx.compose.ui.platform.AndroidUiDispatcher.performFrameDispatch(AndroidUiDispatcher.android.kt:109)
            at androidx.compose.ui.platform.AndroidUiDispatcher.access$performFrameDispatch(AndroidUiDispatcher.android.kt:41)
            at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame(AndroidUiDispatcher.android.kt:69)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:984)
            at android.view.Choreographer.doCallbacks(Choreographer.java:764)
            at android.view.Choreographer.doFrame(Choreographer.java:696)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:965)
            at android.os.Handler.handleCallback(Handler.java:873)
            at android.os.Handler.dispatchMessage(Handler.java:99)
            at android.os.Looper.loop(Looper.java:214)
            at android.app.ActivityThread.main(ActivityThread.java:7073)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:964)
    

    This started happening when I updated from beta02 to beta03. I don't think I changed anything with my code that would have resulted in something like this.

    opened by nathlrowe 4
  • Cannot create View Model class using hiltViewModel()

    Cannot create View Model class using hiltViewModel()

    The title says it all. It only happen for ViewModel inside of the NavHost

    This is the error

    java.lang.UnsupportedOperationException: SavedStateViewModelFactory constructed with empty constructor supports only calls to create(modelClass: Class<T>, extras: CreationExtras).
    

    Step to reproduce

    • Clone https://github.com/andraantariksa/crates
    • Run the project
    opened by andraantariksa 3
  • Deeplink considerations: NavigateUp vs NavigateBack

    Deeplink considerations: NavigateUp vs NavigateBack

    Thanks to the initialBackstack, I can parse deeplink and prepare the backstack to be able to navigateUp. However, when going from deeplink, the app should behave differently for navigateUp and for navigateBack. navigateBack should ignore the "artificial" backstack and return to the previous app. NavigateUp should correctly pop and show the previous entry from the "artificial" backstack.

    I guess it is possible to somehow hack it and track how much of this stack is "artificial", but I'd say support for this should be directly in the library.

    Any thoughts? Did you consider adding this? Thank you :)

    opened by hrach 2
  • CreationExtras integration

    CreationExtras integration

    androidx.lifecycle 2.5 added new API for easier creation of custom ViewModel factories. It needs some integration from this library.

    Namely, NavHostEntry needs to override getDefaultViewModelCreationExtras() method to set default CreationExtras keys that users expect to be present (application, saved state registry owner, etc). Here is how ComponentActivity does that: https://github.com/androidx/androidx/blob/7f29447f3add52b6db69524dfa944dd1b0a8828b/activity/activity/src/main/java/androidx/activity/ComponentActivity.java#L641

    opened by equeim 1
Releases(1.3.1)
  • 1.3.1(Dec 27, 2022)

    This release introduces new API:

    • NavHostState is public now. It is possible to create it manually and set it into NavHost.
    • NavHostVisibility/NavHostAnimatedVisibility

    The detailed description of both items are covered in this section of the documentation site.

    Source code(tar.gz)
    Source code(zip)
  • 1.3.0(Nov 1, 2022)

    This release contains the next changes:

    • Compose updated to 1.3.0
    • BottomSheetNavHost is available now in a separate reimagined-material artifact
    • New scoping NavHosts for easier ViewModel sharing. You can read more about them here.
    • Pending transition animations are properly queued now. This results in less visual artifacts for AnimatedNavHost when calling navigation methods while the animation is running.
    • Experimental BaseNavHost composable for implementing custom NavHosts

    Breaking changes:

    • Long-deprecated setNewBackstackEntries method is finally hidden. Use setNewBackstack instead.

    Deprecations:

    • AnimatedNavHostTransitionSpec is renamed to shorter NavTransitionSpec. The previous name is deprecated and type-aliased to the new name.
    Source code(tar.gz)
    Source code(zip)
  • 1.2.0(Aug 22, 2022)

    This release contains the next changes:

    • Compose updated to 1.2.1
    • androidx.lifecycle dependencies updated to 2.5.1
    • New API that gives the ability to access ViewModelStores of neighbour entries. This way some of the ViewModels may be easily shared between several screens. This API is available through NavHostScope inside of NavHost.
    • Support for CreationExtras from Lifecycle 2.5
    • other internal improvements/fixes and more test coverage

    Breaking changes:

    • NavBackHandler's parameter navController is renamed to controller for consistency with other methods
    • NavId class' constructor is no longer public. It was never meant to be a part of public API.
    • NavAction abstract class is interface now

    Deprecations:

    • NavComponentEntry is renamed to NavHostEntry. The old name is typealiased and deprecated.
    • onBackstackChange is deprecated. It is easily misused and error-prone. Use recommended Compose methods for listening and reacting to snapshot changes of backstack, e.g. snapshotFlow, derivedStateOf, etc.
    • navController delegate for SavedStateHandle is deprecated in favor of the official saveable delegate
    Source code(tar.gz)
    Source code(zip)
  • 1.1.1(Jul 15, 2022)

    This minor release contains the following changes:

    • androidx.lifecycle dependencies updated to 2.5.0
    • Kotlin is updated to 1.7.0
    • introduced a new artifact reimagined-hilt that contains a long awaited hiltViewModel() method compatible with this library. In order to use it add the dependency:
    implementation("dev.olshevski.navigation:reimagined-hilt:1.1.1")
    
    Source code(tar.gz)
    Source code(zip)
  • 1.1.0(Mar 21, 2022)

    This release contains the following changes:

    • Compose is updated to 1.1.1
    • Fixed the issue when the lifecycle state is improperly set for an unchanged last entry
    • NavBackstack is now an Immutable class. Every time any navigation action is called the whole NavBackstack instance is changed. Now the API should look a bit more reasonable now.
    • setNewBackstackEntries is renamed to setNewBackstack. The old method is deprecated, but still works.
    • The sample app is reworked to be more structured and show different features independently. It is also fully covered with functional tests now.

    Breaking changes:

    • Please make sure you are not saving instance of NavBackstack. The backstack property of NavController is backed up by MutableState and will notify compose of new instances of NavBackstack.
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Feb 25, 2022)

    Finalized release 1.0.0.

    This release contains the following changes:

    • new navController delegate for SavedStateHandle for easy NavController creation and restoration
    • now all ViewModels with SavedStateHandle properly reconnect their SavedStateHandle to a new SavedStateRegistry after activity recreation

    Breaking changes:

    • renamed upToPredicate parameter to predicate for popUpTo and replaceUpTo methods
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta03(Feb 16, 2022)

    This release contains the following changes:

    • Introduced new navigation method moveToTop that may be very convenient for BottomNavigation and tab navigation implementations
    • Fixed an issue where the same NavEntry instance that was placed twice into a backstack would become two instances after process/activity recreation. It might rarely occur in real-life scenarios, but now it provides better guarantees on the uniqueness of every entry.
    • NavComponentEntry provides id and destination properties now
    • Removed unused transitive dependency to "material" package

    Breaking changes:

    • popUpTo and replaceUpTo by default search for the last destination matching the predicate now. You can customize this behaviour with match parameter. It better correlates with the behaviour of Navigation Component.
    • popAll doesn't return boolean now. The result is always considered a successful operation.
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta02(Feb 11, 2022)

    This release contains the following changes:

    • Compose dependencies updated to 1.1.0
    • Pause/stopped lifecycle events can be properly received in your composable now. Previously, a composable could be disposed without receiving these events.
    • NavComponentEntry class is now public. It owns Lifecycle, SavedStateRegistry and ViewModelStore. It is a direct analogue of NavBackStackEntry and may be used for instance checks (e.g. when creating view models with Hilt).
    • NavBackstack now exposes its last NavAction though action property
    • NavAction.Idle is the initial action for every new NavController instance
    • NavAction hierarchy is simplified

    Breaking changes:

    • NavAction.Forward and NavAction.Backward types are gone. Check for specific action types such as NavAction.Pop, NavAction.Navigate, NavAction.Replace directly.
    • onBackstackChange listener now passes NavBackstack parameter instead of List<NavEntry>
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-beta01(Feb 3, 2022)

Owner
Vitali Olshevski
Developing Android apps since 2011
Vitali Olshevski
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
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
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
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
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
Android multi-module navigation built on top of Jetpack Navigation Compose

MultiNavCompose Android library for multi-module navigation built on top of Jetpack Navigation Compose. The goal of this library is to simplify the se

Jeziel Lago 21 Dec 10, 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 customizable and easy to use BottomBar navigation view with sleek animations, with support for ViewPager, ViewPager2, NavController, and badges.

AnimatedBottomBar A customizable and easy to use bottom bar view with sleek animations. Examples Playground app Download the playground app from Googl

Joery 1.2k Dec 30, 2022
A customizable and easy to use BottomBar navigation view with sleek animations, with support for ViewPager, ViewPager2, NavController, and badges.

A customizable and easy to use BottomBar navigation view with sleek animations, with support for ViewPager, ViewPager2, NavController, and badges.

Joery Droppers 1000 Dec 5, 2021
BubbleTabBar is bottom navigation bar with customizable bubble like tabs

BubbleTabBar BubbleTabBar is bottom navigation bar with customizable bubble like tabs Usage <com.fxn.BubbleTabBar android:id="@+id/

Akshay sharma 576 Dec 30, 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
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
🛸Voyager is a pragmatic navigation library built for, and seamlessly integrated with, Jetpack Compose.

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

Adriel Café 831 Dec 26, 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
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
🎉 [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