Navigation With Jetpack Compose in Android

The original link

Navigate between composable functions, use custom objects as arguments, and more

In this article you will learn how to use Navigation in Jetpack Compose. You will also learn how to pass base type and custom type data when navigate.

Note: Jetpack Compose has recently released a beta version. This means there won't be much change in the API structure. So now it's time to learn how to develop the next generation of UI tools on Android. Navigation is one of the core aspects of Android development, so read this article.

Using Jetpack Compose Beta requires the Android Studio Canary Arctic Fox version.

introduce

Jetpack Compose can interact with Android components such as Fragments. So if you have an existing project that you want to switch to Jetpack Compose, you don’t need to do any revisions.

But if you want to migrate an entire app without requiring any Android components such as Fragments, or if you want to create a new app with Compose, this article is for you.

We will use Navigation, an architectural component, to jump between pages. If you are not familiar with Navigation, I strongly recommend reading this article: JitPack-Navigation-Component

Create a two-page application with Compose

Unlike traditional Android development, Jetpack Compose does not have Fragment or Activity constraints. Jetpack Compose allows you to Compose a Composable function to represent a portion of a page or the entire page. If you are building a project from scratch, I recommend using only Composable to take advantage of Compose’s power.

In order to fully understand the Navigation of Compose, let’s create a Subscription contains two pages: SubscriptionsListScreen and SubscriptionDetailsScreen. Take a look at the code:

class Subscription(val name : String, val price : String, val details : String)

@Composable
fun SubscriptionsListScreen(subscriptionslist : List<Subscription>){
    LazyColumn(
        modifier = Modifier.fillMaxWidth()
    ){
        items(subscriptionslist){subscription->
            SubscriptionsListItem(subscription)
        }
    }
}

@Composable
fun SubscriptionsListItem(subscription : Subscription){
    Column(
        modifier = Modifier.fillMaxWidth()
    ){
        Text(text = subscription.name, style = MaterialTheme.typography.h6)
    }
}

@Composable
fun SubscriptionDetailsScreen(subscriptionName : String){
    Column(
        modifier = Modifier.fillMaxWidth()
    ){
        val subscription = SubscriptionsDatabase.subsList.single { it.name.equals(subscriptionName, true)  }
        Text(text = subscription.name, style = MaterialTheme.typography.h5)
        Text(text = subscription.name, style = MaterialTheme.typography.body1)
        Text(text = subscription.details, style = MaterialTheme.typography.body2)
    }
}
Copy the code

In this code, there are three Composable functions representing two pages:

  • SubscriptionsListScreenIs a Composable representing the Subscription list page with the subscription list parameter.
  • SubscriptionsListItemRepresents the UI for each Subscription item, taking a Subscription class.
  • SubscriptionDetailsScreenRepresents the details of the Subscription, which can be called from the list to the detail page, with the Subscription class.

Here, SubscriptionsListScreen and SubscriptionDetailsScreen respectively lists and details page. They are the same as activities and fragments compared to traditional development. SubscriptionsListItem represents part of a page, just like a RecyclerView Adapter Item.

Our task is to use Navigation framework to jump between these two pages and pass the subscription information.

integration

In order for Navigation to work in Compose, we need to add the following dependencies. With this dependency Navigation can be compatible with the Composable function:

implementation "Androidx. Navigation: navigation - compose: 1.0.0 - alpha08"
Copy the code

Navigation framework

Before you get to Navigation, you need to know what Remember stands for in Android. In declarative UI systems, the code itself describes the UI. You need to be able to represent the UI at all times, not just at initialization.

To do this, we need to maintain a state as easily and efficiently as possible, and Jetpack Compose provides Remember. Remember keeps track of the state. To better understand and learn about Remember and other Components of Jetpack Compose, read the article Jetpack Compose Component (Part 2) below.

It is now time to implement a Navigation framework for the Composable. The Navigation component is fully integrated with the Composable via the integrated libraries above.

The first thing we need to learn is the Composable function remmeberNavController, which creates a NavHostController to handle the ComposeNavigator. We can use NavHostController to jump between two Composable functions.

val navController = rememberNavController()
Copy the code

Next, we need to create a sealed class that contains objects for all pages. For our example, we have two objects: SubscriptionsList and SubscriptionDetails. Take a look at the code:

sealed class Screen(val route: String, @StringRes val resourceId: Int) {
    object Home : Screen("SubscriptionsList", R.string.subs_list)
    object Details : Screen("SubscriptionDetails", R.string.sub_details)
}
Copy the code

Now you can create the main Composable by nesting all of the compose pages into the NavHost Composable function. Once this is called, any composables configured in the NavGraphBuilder can be jumped through the navController. In our example, there are two pages that need to be declared in NavHost, as follows:

@Composable
fun SubscriptionsMain(a) {
    val navController = rememberNavController()
    NavHost(navController, startDestination = Screen.Home.route) {
        composable(Screen.Home.route) {
            SubscriptionsListScreen( SubscriptionsDatabase.subsList)
        }
        composable(Screen.Details.route) {
            SubscriptionDetailsScreen( /* TODO paramter implementation pending */)}}}Copy the code

Composable is an extension function of NavGraphBuilder that adds Composable to NavGraphBuilder. The next step is to pass the parameters. SubscriptionDetailsScreen combination function needs a data class to display the details of the Subscription.

Imagine that we need to render the SubscriptionsMain composite function into the MainActivity class.

To support arguments and deep links, the Composable function has two arguments of type list that default to emptyList. Let’s take a look at what this function looks like:

public fun NavGraphBuilder.composable(
    route: String,
    arguments: List<NamedNavArgument> = emptyList(),
    deepLinks: List<NavDeepLink> = emptyList(),
    content: @Composable (NavBackStackEntry) -> Unit
)
Copy the code

Argumens and deepLinks are optional here. In our example, we need to pass the parameters to the details page; the home page doesn’t need either parameter. Basically, we need to pass in the name of the subscription as a string. Look at the code:

@Composable
fun SubscriptionsMain(a) {
    val navController = rememberNavController()
    NavHost(navController, startDestination = Screen.Home.route) {
        composable(Screen.Home.route) {
            SubscriptionsListScreen( SubscriptionsDatabase.subsList)
        }
        composable(Screen.Details.route,
            arguments = listOf(navArgument("name") { type = NavType.StringType }) { SubscriptionDetailsScreen( it.arguments? .getString("name") ?: 0)}}}Copy the code

If you have seen our updated SubscriptionsMain function, we have declared the subscription name as a string type in the Composable of the detail page, and we use it in the expression of the lambda.

Implement jumps between Composable functions

In general, you need navController to jump between any Android components. The Composable function is the same. A navController is required to trigger or start one Composable from another.

Unlike the traditional approach, the Composable cannot find the navController directly. So we need to pass in the navController we created in SubscriptionsMain. The target Composable function does not use navController as an entry parameter. In our example, SubscriptionsListScreen and SubscriptionsListItem require navController. Take a look at the Composable modified:

@Composable
fun SubscriptionsListScreen(navController: NavController, subscriptionslist : List<Subscription>){
    LazyColumn(
        modifier = Modifier.fillMaxWidth()
    ){
        items(subscriptionslist){subscription->
            SubscriptionsListItem(navController, subscription)
        }
    }
}

@Composable
fun SubscriptionsListItem(navController: NavController, subscription : Subscription){
    Column(
        modifier = Modifier.fillMaxWidth()
    ){
        Text(text = subscription.name, style = MaterialTheme.typography.h6)
    }
}
Copy the code

Now we need to pass the navController to the modified function:

@Composable
fun SubscriptionsMain(a) {
    val navController = rememberNavController()
    NavHost(navController, startDestination = Screen.Home.route) {
        composable(Screen.Home.route) {
            SubscriptionsListScreen( navController, SubscriptionsDatabase.subsList)
        }
        composable(Screen.Details.route,
            arguments = listOf(navArgument("name") { type = NavType.StringType }) { SubscriptionDetailsScreen( it.arguments? .getString("name") ?: 0)}}}Copy the code

Now we can jump from the subscription list page to the details page and look at the code:

@Composable
fun SubscriptionsListItem(navController: NavController, 
                          subscription : Subscription){
    Column(
        modifier = Modifier.fillMaxWidth()
            .clickable {
                /* Navigation to details screen with subscription name */
                navController.navigate("details/${subscription.name}")
            }
    ){
        Text(text = subscription.name, style = MaterialTheme.typography.h6)
    }
}
Copy the code

That’s all. We have learned how to create a Navigation framework and pass parameters without page-hopping.

The parameters of a custom object

Jetpack’s Navigation is currently in alpha, and requirements like passing Parcelable objects are not currently supported. One solution is to convert the object to a Gson string and then parse it back into an object where it is received.

First we need to add the following Gson dependencies to our project:

implementation "Com. Google. Code. Gson: gson: 2.8.6"
Copy the code

Then we need to convert the custom object to a string, taking the string as an argument, code:

val gsonString = Gson().toJson(subscription)
navController.navigate("details/$gsonString")
Copy the code

On the other side, we can convert strings into objects using fromJson. Look at the code:

@Composable
fun SubscriptionsMain(a) {
    val navController = rememberNavController()
    NavHost(navController, startDestination = Screen.Home.route) {
        composable(Screen.Home.route) {
            SubscriptionsListScreen( navController, SubscriptionsDatabase.subsList)
        }
        composable(Screen.Details.route,
            arguments = listOf(navArgument("name") { type = NavType.StringType })) { it.arguments? .getString("name")? .let {val subscription = Gson().fromJson(it,Subscription::class.java)
                SubscriptionDetailsScreen( subscription )
            }
        }
    }
}
Copy the code

That’s All

Hope you had some fun and thank you for reading.