Navigation in compose using Voyager (Android & KMP)

Voyager is one of the easiest navigation libraries out there, it super easy to setup, customizable and provides type safety when passing arguments through Data Classes. Voyager can be used both in native android projects and in Kotlin Multi-Platform projects that use Compose multiplatform for UI.

Setup

First we need to add the voyager dependency to our module’s build.gradle file. add the dependencies and sync gradle.

dependencies {
    //latest version: https://github.com/adrielcafe/voyager/releases
    val voyagerVersion = "1.1.0-beta02" 

    // Multiplatform
    // Navigator
    implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion")

    // Transitions (optional)
    implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion")
}

Preparing our screen

When using voyager all your navigation destinations should be in a Class, object or Data Class that implements ‘cafe.adriel.voyager.core.screen.Screen’, then we need to overwite the content function and put our screen content there

import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow

class ScreenOne(): Screen {

    @Composable
    override fun Content() {
        ScreenOneContent() // <- screen content here
    }

}

Defining the Navigator

Navigator is the function responsible for defining our start screen and some other functionalities such as handeling our backstack, lifecycle and onBackPressed. but for simplicity and since we won’t need any other configuration we’re just going to define our start destination in our Navigator which is the minimum required argument to setup it up.

we will define our navigator in the setContent block in MainActivity.kt

        setContent {
            DemoClassTheme { 
                Navigator(ScreenOne())
            }
        }

Navigating

we would like to navigate to ScreenTwo when we click the ‘Go’ button on ScreenOne, for that we would call a very handy composition local variable that we can access anywhere in our compose code called ‘LocalNavigator’ we have multiple options to choose from but i personally prefere the .currentOrThrow option since i would like to force my app to crash if there is no navigator (which should never happen since from the verystarting point of my app the Navigator is setup correctly) rather than have the navigator do nothing when it is null.

We’ll use the push function to navigate to ScreenTwo

@Composable
fun ScreenOneContent(modifier: Modifier = Modifier) {
    val navigator = LocalNavigator.currentOrThrow  // <- access voyager navigator
    Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxSize()) {
        Text(text = "Screen 1", fontSize = 45.sp)
        Button(onClick = {
            navigator.push(ScreenTwo()) // <- Navigation
        }) {
            Text(text = "Go")
        }
    }
}

In ScreenTwo we want to navigate back so we’ll use the .pop() function

@Composable
fun ScreenTwoContent(modifier: Modifier = Modifier) {
    val navigator = LocalNavigator.currentOrThrow
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
        modifier = Modifier.fillMaxSize()
    ) {
        Text(text = "Screen 2", fontSize = 45.sp)
        Button(onClick = { navigator.pop() }) {
            Text(text = "Go Back")
        }
    }
}

That’s it! we have succesfully setup navigation in our app.

the source code for this part can be found here

Passing parameters

To pass parameters simply define our screen as a data class, let’s create a string called name in ScreenOne and pass it to ScreenTwo

class ScreenOne(): Screen {

    @Composable
    override fun Content() {
        ScreenOneContent() // <- screen content here
    }

}

@Composable
fun ScreenOneContent(modifier: Modifier = Modifier) {
    val navigator = LocalNavigator.currentOrThrow  // <- access voyager navigator
    var text by remember { mutableStateOf("") }
    Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxSize()) {
        Text(text = "Screen 1", fontSize = 45.sp)
        OutlinedTextField(value = text, onValueChange = {text = it}, label = { Text("Name") })
        Button(onClick = {
            navigator.push(ScreenTwo(name = text)) // <- Navigation
        }) {
            Text(text = "Go")
        }
    }
}
data class ScreenTwo(val name: String) : Screen {

    @Composable
    override fun Content() {
        ScreenTwoContent(name = name)
    }
}

@Composable
fun ScreenTwoContent(modifier: Modifier = Modifier, name: String) {
    val navigator = LocalNavigator.currentOrThrow
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
        modifier = Modifier.fillMaxSize()
    ) {
        Text(text = "Screen 2", fontSize = 45.sp)
        Text(text = "Hello $name", fontSize = 45.sp)
        Button(onClick = { navigator.pop() }) {
            Text(text = "Go Back")
        }
    }
}

the source code for this part can be found here

Adding Transition Effect

To add transitions we simply need to add our transition to the navigator, Voyager comes preloaded with 3 types on transitions, Slide, Fade and Scale for this example we’ll use Slide.

        setContent {
            DemoClassTheme {
                Navigator(screen = ScreenOne(),
                    disposeBehavior = NavigatorDisposeBehavior(disposeSteps = false),){navigator ->
                    SlideTransition(
                        navigator = navigator,
                        disposeScreenAfterTransitionEnd = true
                    )
                }
            }
        }

The NavigatorDisposeBehavior and disposeScreenAfter parameter are recommended by Voyager to avoid a known bug and avoid memory leaks and crashes, more info in the docs.

the source code for this part can be found here

That’s it leave ur questions in the comments if you have any and until next time, peace!.

5 1 vote
Article Rating
Subscribe
Notify of
guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
keplo

نوارة هندسة