image/svg+xml $ $ ing$ ing$ ces$ ces$ Res Res ea ea Res->ea ou ou Res->ou r r ea->r ch ch ea->ch r->ces$ r->ch ch->$ ch->ing$ T T T->ea ou->r

Creating navigatable screens

The first step is to create a NavController that is used to remember the state of navigation (on which screen we are) and also to control it (programmtically go from one screen to another):

val navController = rememberNavController()

One can go to another screen:

navController.navigate("nameOfTheOtherScreen")

The next step is to create a NavHost embedding the different screens (each identified by a name):

NavHost(navController = navController, startDestination = "profile") {
    composable("first") { ... }
    composable("second") { ... }
    composable("third") { ... }
    /*...*/
}

The startDestination must be a constant (must not change with recompositions): it is the first screen that is displayed by default.

Management of the navigation stack

The navigation stack (stored inside the NavController) stores the history of navigation to support the back key (to return to the previous screen).

We can programmatically go to the previous screen on the stack; we can also exit the activity if we have popped all the screen from the stack:

if (!navController.popBackStack()) {
    finish()
}

When we navigate to another screen we can alter the way the stack work with popupTo to pop elements from the stack and launchSingleTop = true to avoid putting several instances of the same screen on the stack. Here is some examples of use (from the documentation):

// Pop everything up to the "home" destination off the back stack before
// navigating to the "friends" destination
navController.navigate(“friends”) {
    popUpTo("home")
}

// Pop everything up to and including the "home" destination off
// the back stack before navigating to the "friends" destination
navController.navigate("friends") {
    popUpTo("home") { inclusive = true }
}

// Navigate to the "search” destination only if we’re not already on
// the "search" destination, avoiding multiple copies on the top of the
// back stack
navController.navigate("search") {
    launchSingleTop = true
}

☞ It is not recommended to use several routes to manage the responsivity of the app. One use a single route for each screen; it is the responsibility of the screen itself to adapt itself to the size and/or the orientation of the screen.

Parameterized routes

Routes can be parameterized with arguments. For example, if we have an activity displaying information about several towns, we can parameterize the route with the name of the town:

NavHost(startDestination = "townInfo/London") {
    ...
    composable("townInfo",
		arguments = listOf(navArgument("townName") { type = NavType.StringType }) { backStackEntry ->
			val param = backStackEntry.arguments?.getString("townName") // we get the value of the argument
	})
}

Parameters can have other types than StringType (IntegerType, FloatType...).

We can navigate to the screen displaying the town information about Paris with: navController.navigate("townInfo/Paris")

One can use also optional arguments after a ? character (like for an HTTP URL). For example if we have a destination displaying the first prime numbers, we can specify an optional parameter with the number of primes to display (with a default value of 100 for example if the parameter is not given):

composable(
    "primeNumbers?number={number}",
    arguments = listOf(navArgument("number") { type = NavType.IntegerType; defaultValue = 100 })
) { backStackEntry ->
    PrimeNumbers(backStackEntry.arguments?.getInt("number"))
}

Deep linking

An app A can open the activity of an app B and navigate directly to a screen.

For example if A wants to open the screen of B with the path townInfo/Paris, we can define an URL prefix linking to our app B (http://towns.example.com) and then following the URL https://towns.example.com/townInfo/Paris: it will open the app B directly to the screen displaying the town information about Paris.

⚠ To allow deep linking to a domain, you must have a control over the domain to put a special file on the webserver at the address https://towns.example.com/.well-known/assetlinks.json like explained here. This file must include the fingerprint of the key used to sign the application.

One must add in the manifest of the app B an intent-filter for the activity that is linked to declare the supported domain:

<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:host="towns.example.com"
        android:scheme="https" />
</intent-filter>

The app A can use an intent to display directly the screen of B:

val deepLinkIntent = Intent(
    Intent.ACTION_VIEW,
    "https://towns.example.com/townInfo/Paris".toUri())

val pending: PendingIntent = TaskStackBuilder.create(context).run {
    addNextIntentWithParentStack(deepLinkIntent)
    getPendingIntent(REQUEST_CODE, PendingIntent.FLAG_UPDATE_CURRENT)
}

It is also possible do launch the screen using ADB:

adb shell am start -W -a android.intent.action.VIEW -d “https://towns.example.com/townInfo/Paris”

Nested graph of navigation

Destinations can be grouped together in a nested graph for a better modularity. For this we use the navigation extension function. It is useful for example for forms that are split into several screens (to avoid scrolling).

Example case: login or creation of a new account

// We create a function for login
fun NavGraphBuilder.loginGraph(navController: NavController) {
    navigation(startDestination = "email", route = "login") {
        composable("email") { ... ; ; navController.navigate("newAccount") }
        composable("password") { ... }
        composable("connected") { ... }
        composable("failed") { ... }
    }
}

// We create a function for the account creation
fun NavGraphBuilder.newAccountGraph(navController: NavController) {
    navigation(startDestination = "email", route = "newAccount") {
        composable("email") { ... }
        composable("validateEmail") { ... } // enter the code received by email to validate the address
        composable("password") { ... } // choose a password
        composable("accountCreated") { ... }
    }
}

// We create a function for the main features of the app
fun NavGraphBuilder.mainGraph(navController: NavController) {
	navigation(startDestination = "home", route = "main") {
		...
	}
}

@Composable
fun navigatableGraph() {
	NavHost(navController, startDestination = "home") {
		loginGraph(navController)
		newAccountGraph(navController)
		mainGraph(navController)
	}
}