Skip to content

Compose Navigator Setup

Kaustubh Patange edited this page Jun 5, 2022 · 11 revisions

This setup is an excerpt from Basic Sample app.

Initialize ComposeNavigator in the Activity

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        val navigator = ComposeNavigator.with(this, savedInstanceState).initialize()
        setContent {
            ...
            MainScreen(navigator)
        }
    }
}

@Composable
fun MainScreen(navigator: ComposeNavigator) {
    // starts here...
}

This will automatically manage back navigation whenever a backpress occurs. You can disable default back navigation logic for backpress using disableDefaultBackPressLogic() when configuring Navigator from Builder options.

The savedInstanceState will ensure that all the necessary backstack operations are saved (when appropriate lifecycle occurs) & will be restored after the configuration or process death.

Define a route

Destinations can be represented as children of a sealed class.

The constructor parameters becomes the arguments for the destination screen. Note, only data types that can be stored in a bundle are supported.

sealed class MainRoute : Route {
    @Immutable @Parcelize
    data class First(val data: String) : MainRoute()
    @Immutable @Parcelize
    data class Second(private val noArg: String = "") : MainRoute() // no arg route

    companion object Key : Route.Key<MainRoute> // <-- Unique key for the root
}

@Composable // associated with MainRoute.First
fun FirstScreen(data: String, changed: (screen: Route) -> Unit) {...}

@Composable // associated with MainRoute.Second
fun SecondScreen() {...}

The MainRoute must implement com.kpstv.navigation.compose.Route interface which internally implements Parcelable to ensure that the state of navigation will persist accross configuration change.

We also have declared a key variable in the companion object of the MainRoute (which will be used during Setup). This key will be used to uniquely identify the Route in the composition tree. This is how findNavController<T>() was able to find & retrieve the Controller associated with that specified key.

From above snippet, you can notice we have declared a private noArg parameter for Second screen. The reason is we should not use object or simple class to represent empty argument constructor because SaveStateProvider does not restore rememberSaveables after process death. Read more about this issue here.

Setup Navigation in MainScreen

@Composable
fun MainScreen(navigator: ComposeNavigator) {
    // create instance of navigation controller to manage
    // navigation of type MainRoute.
    val navController = rememberNavController<MainRoute>()

    navigator.Setup(key = MainRoute.key, initial = MainRoute.First("Hello world"), controller = navController) { dest ->
        val onChanged: (screen: MainRoute) -> Unit = { value ->
            navController.navigateTo(value)
        }
        when (dest) {
            is MainRoute.First -> FirstScreen(dest.data, onChanged)
            is MainRoute.Second -> SecondScreen()
        }
    }
}

Navigation to other destination is managed within the respective @Composable screens. To go back programmatically you can call controller.goBack().

Each Setup provides a unique instance of Controller<T> for managing destination & can be accessed by using findNavController(key) within child composables.

A when statement is used to switch between different @Composable screens based on the target destination dest.