NavController is central to navigation, thus, created first.
val navController = rememberNavController()
It can be obtained by calling rememberNavController()
. NavController should be created and place at the top level of the hierarchy being the single source of truth for navigation and managing back stack.
Container for destinations and displays current destination of the nav graph.
Use NavController to navigate between composables, and the NavHost recomposes as the destination changes.
Unique string that represents destination in navigation graph. Compose use serializable object or class to define a route. Navigation graph is created with composable and routes.
Route can be added using Navigation.composable
extension function.
interface RallyDestination {
val icon: ImageVector
val route: String
}
object Overview : RallyDestination {
override val icon = Icons.Filled.PieChart
override val route = "overview"
}
NavHost(
navController = navController,
startDestination = Overview.route,
modifier = Modifier.padding(innerPadding)
) { // this : NavGraphBuilder
composable(route = Overview.route){
OverviewScreen()
}
composable(route = Accounts.route) {
AccountsScreen()
}
composable(route = Bills.route) {
BillsScreen()
}
}
@Serializable
object Profile
@Serializable
object FriendsList
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = Profile,
) {
composable<Profile> { ProfileScreen( /* ... */ ) }
composable<FriendsList> { FriendsListScreen( /* ... */ ) }
// Add more destinations similarly.
}
val navGraph by remember(navController) {
navController.createGraph(startDestination = Profile)) {
composable<Profile> { ProfileScreen( /* ... */ ) }
composable<FriendsList> { FriendsListScreen( /* ... */ ) }
}
}
NavHost(navController, navGraph)
@Serializable
object Profile
@Serializable
object FriendsList
@Composable
fun MyAppNavHost(
modifier: Modifier = Modifier,
navController: NavHostController = rememberNavController(),
) {
NavHost(
modifier = modifier,
navController = navController,
startDestination = Profile
) {
composable<Profile> {
ProfileScreen(
// callback for navigation
onNavigateToFriends = { navController.navigate(route = FriendsList) },
/*...*/
)
}
composable<FriendsList> { FriendsListScreen(/*...*/) }
}
}
@Composable
fun ProfileScreen(
onNavigateToFriends: () -> Unit,
/*...*/
) {
/*...*/
Button(onClick = onNavigateToFriends) {
Text(text = "See friends list")
}
}
Provide callbacks for the exact navigation and do not pass NavControllers directly to keep the code testable and reusable.
Argument can be passed when navigating by appending it to the route in a form route/{argument}
.
composable(
route = SingleAccount.routeWithArgs, // "${SingleAccount.route}/{${SingleAccount.accountTypeArg}}"
arguments = SingleAccount.arguments // list of navArguments
){ navBackStackEntry ->
// Retrieve the passed argument
val accountType =
navBackStackEntry.arguments?.getString(SingleAccount.accountTypeArg)
// Pass accountType to SingleAccountScreen
SingleAccountScreen(accountType)
}
object SingleAccount : RallyDestination {
override val route = "single_account"
const val accountTypeArg = "account_type"
val routeWithArgs = "${route}/{${accountTypeArg}}"
val arguments = listOf(
navArgument(name = accountTypeArg) { type = NavType.StringType }
)
}
private fun NavHostController.navigateToSingleAccount(accountType: String) {
this.navigateSingleTopTo("${SingleAccount.route}/$accountType")
}
onAccountClick = { accountType ->
navController.navigateToSingleAccount(accountType)
}
navArgument
Creates an argument with the given name that is supported by NavDestination. In the builder, type is defined with an optional default value or nullable, if the type supports it, that is used to read or write it in the Bundle.
Argument can be retrieved from the navBackStackEntry
with matching name.
Navigating with deeplink uses route/{argument}
format with some addition.
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="rally" android:host="single_account" />
</intent-filter>
Add intent-filter to open the app and navigate to destination.
composable(
route = SingleAccount.routeWithArgs,
arguments = SingleAccount.arguments,
deepLinks = SingleAccount.deepLinks
) {...}
object SingleAccount : RallyDestination {
// ...
val deepLinks = listOf(
navDeepLink { uriPattern = "rally://$route/{$accountTypeArg}"}
)
}
Deeplink can be added to the existing composable destination with the form of scheme://route/{argument}
. Everything else is the same as using argument in navigation.
adb shell am start -d "rally://single_account/Checking" -a android.intent.action.VIEW
Deeplink can be tested with adb.
fun NavHostController.navigateSingleTopTo(route: String) =
this.navigate(route) {
popUpTo(
this@navigateSingleTopTo.graph.findStartDestination().id
) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
SingleTop
Maintain only one copy of the destination on top of the back stack.
fun NavHostController.navigateSingleTopTo(route: String) =
this.navigate(route) { launchSingleTop = true }
PopUpTo
Pop up to the start destination of the graph.
{ popUpTo(startDestination) { saveState = true } }
restoreState
Whether to restore the state saved previously with saveState
. If no state was saved, it has no effect.
{ restoreState = true }
currentBackStackEntryAsState()
Returns top entry of the back stack as mutable state.
val currentBackStatck by navController.currentBackStackEntryAsState()
val currentDestination = currentBackStatck?.destination