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:
SubscriptionsListScreen
Is a Composable representing the Subscription list page with the subscription list parameter.SubscriptionsListItem
Represents the UI for each Subscription item, taking a Subscription class.SubscriptionDetailsScreen
Represents 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.