DEV Community

Cover image for Compose Navigation Reimagined
Vitali Olshevski
Vitali Olshevski

Posted on • Edited on

Compose Navigation Reimagined

Hi there!

Today I would like to share with you a small and sweet library I was meticulously crafting since December of the last year. It started as a simple proof-of-concept, but eventually evolved into something much more elaborate.

Here I present to you the result of my work: Compose Navigation Reimagined - the navigation library built specifically for Android and Jetpack Compose.

But why?

Ever since I started learning Compose and dug into the official Navigation Component, I was quite disappointed with the suggested way of handling navigation. The library has its fair share of flaws and redundancies. And I've already tried working around all its weirdnesses that one time.

But I knew that it could be done much better. Yes, "reinventing the wheel", you would say. But more wheels to choose from is always a benefit. Judge for yourself.

What can it do?

Here is the list of the main features:

  • 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

This is pretty much everything the official library can do, and even more. Yes, there are no directional graphs and automatic deep-link handling, but that can be easily added if you need to. The main goal of the library is to allow developers to expand on the core functionality, rather than fight against its strict rules.

Show me the code

Ok, first you define a set of destinations of an arbitrary type. The type must be writable to Parcel and Stable.

Let's use a sealed class, as it is the most Kotlin way:

sealed class Screen : Parcelable {

    @Parcelize
    object First : Screen()

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

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

}
Enter fullscreen mode Exit fullscreen mode

Next, we create a composable with NavController and NavHost:

@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}")
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

For more details about the library, visit the github page of the project.

The status of the library

The library is currently in Beta, as I want to hear feedback and do minor fine-tuning of the API (if any at all).

But for the most part, I consider it done and ready for some action.

Anyways, I hope you like it and find it useful.

And if you do, please bookmark/star the project and share it with your fellow developers. A little bit of promotion never hurts.

Top comments (0)