Jetpack Compose: Navigation object argument

Jetpack Compose: Navigation with Object Arguments

Jetpack Compose is a powerful toolkit for building native Android UIs with a declarative approach. It simplifies UI creation by leveraging Kotlin code instead of XML layouts. In this article, we will explore an interesting feature of Jetpack Compose: passing complex objects through navigation using Gson, focusing on how to pass an object between screens in a Compose-based application.

Navigation in Jetpack Compose provides a simple way to handle navigation between different composables. However, unlike primitive types like strings or integers, passing more complex data such as objects requires a bit more setup. In this example, we'll see how to pass an object by serializing it into a JSON string and restoring it on the target screen using Gson, a popular JSON library in Android.

MainActivity: Entry Point and Scaffold Setup

The main entry point for the application is the MainActivity, which extends AppCompatActivity. Inside the onCreate method, we use setContent to define the UI using Composable functions. The root composable, GetScaffold, contains a Scaffold, which is a common layout structure in Jetpack Compose that allows setting up material components such as a TopAppBar and content.

In GetScaffold, the TopAppBar displays a title, and the main content is controlled by MainContent, which accepts a NavHostController to manage navigation between different screens. The background colors of the Scaffold and the TopAppBar are set using the Color class, adding visual consistency to the UI.

Navigation Setup

The navigation logic is handled within the MainNavigation composable. This composable defines a NavHost that manages the navigation structure, with listScreen being the start destination. The composable function is used to define individual screens, and in this case, we have two screens: ListScreen and DetailsScreen.

To pass an object to the DetailsScreen, we define a route that includes a placeholder for a string representation of the object (colorObject). When navigating to the details screen, this string is retrieved from the back stack entry arguments. The string is then deserialized back into a ColorObject using the Gson library.

ListScreen: Displaying and Navigating with Objects

ListScreen is where a list of color objects is displayed to the user. These objects are defined using the ColorObject data class, which contains a color name and a hex color value. A Column composable arranges the color cards vertically, with spacing and padding applied for better readability.

Each card in the list is clickable, and when clicked, it triggers the navigation to DetailsScreen. Before navigating, the selected ColorObject is serialized into a JSON string using Gson. This string is then passed through the navigation system as a route argument.

DetailsScreen: Displaying the Selected Object

The DetailsScreen composable is responsible for displaying the color object passed from the ListScreen. It accepts a ColorObject? as an argument and displays the color's name and background. The background color of the screen is dynamically set using the hex value from the ColorObject. If no object is passed, the screen defaults to a white background.

The text inside the screen is centered using a Box composable with the fillMaxSize modifier. The Text composable shows the color name in a larger font and is aligned to the center of the screen for simplicity.

Summary

In this example, we've demonstrated how to pass objects between screens using Jetpack Compose's navigation component and the Gson library. The key takeaway is that objects need to be serialized into a string format (JSON) before passing them as arguments in navigation, as Jetpack Compose's navArgument only supports primitive types. On the receiving side, we can deserialize the JSON string back into an object.

Jetpack Compose's declarative approach makes UI development more intuitive, and combining it with the power of Kotlin and libraries like Gson opens up possibilities for building dynamic and responsive applications. This example showcases how easy it can be to integrate navigation with complex data passing in a Compose-based Android app.


MainActivity.kt

package com.cfsuman.jetpackcompose

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.google.gson.Gson


class MainActivity : AppCompatActivity() {
    override fun onCreate(savedObjectState: Bundle?) {
        super.onCreate(savedObjectState)
        setContent {
            GetScaffold()
        }
    }


    @Composable
    fun GetScaffold(){
        val navController:NavHostController = rememberNavController()

        Scaffold(
            topBar = {
                TopAppBar(
                    title = { Text(
                        text = "Compose - Nav Object Argument"
                    )},
                    backgroundColor = Color(0xFFC0E8D5),
                )
            },
            content = {MainContent(navController)},
            backgroundColor = Color(0xFFEDEAE0),
        )
    }


    @Composable
    fun MainContent(navController: NavHostController){
        Box(
            modifier = Modifier.fillMaxSize(),
        ){
            MainNavigation(navController)
        }
    }


    @Composable
    fun MainNavigation(navController: NavHostController){
        NavHost(
            navController = navController,
            startDestination = "listScreen"
        ){
            composable("listScreen"){ListScreen(navController)}

            composable(
                route = "detailsScreen/{colorObject}",
                arguments = listOf(
                    navArgument("colorObject")
                        { type = NavType.StringType },
                )
            ){ backStackEntry ->
                backStackEntry.arguments
                    ?.getString("colorObject").let { json->
                    val colorObject = Gson().fromJson(
                        json,ColorObject::class.java
                    )
                    DetailsScreen(colorObject = colorObject)

                }
            }
        }
    }


    @Composable
    fun ListScreen(navController: NavController){
        fun navigateToColorObject(colorObject: ColorObject){
            val colorJson = Gson().toJson(colorObject)
            navController.navigate("detailsScreen/$colorJson")
        }

        val colors = listOf<ColorObject>(
            ColorObject("Brick red",0xFFCB4154),
            ColorObject("Baby blue",0xFF89CFF0),
            ColorObject("Brilliant rose",0xFFFF55A3),
            ColorObject("Cadmium orange",0xFFED872D),
            ColorObject("Cameo pink",0xFFEFBBCC)
        )
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .padding(4.dp),
            verticalArrangement = Arrangement.spacedBy(4.dp),
            horizontalAlignment = Alignment.Start
        ) {
            Text(
                text = "List Screen",
                style = MaterialTheme.typography.h4,
                modifier = Modifier.padding(bottom = 24.dp)
            )

            colors.forEach {
                Card(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(1.dp)
                        .height(75.dp)
                        .clickable {
                            navigateToColorObject(it)
                        }
                ) {
                    Box(modifier = Modifier
                        .fillMaxSize()
                        .wrapContentSize(Alignment.Center)) {
                        Text(
                            text = it.name,
                            fontWeight = FontWeight.Bold
                        )
                    }
                }
            }
        }
    }


    @Composable
    fun DetailsScreen(colorObject:ColorObject?){
        Box(
            modifier = Modifier
                .fillMaxSize()
                .background(Color(colorObject?.value ?: 0xFFFFFF)),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = colorObject?.name?:"",
                style = MaterialTheme.typography.h4,
                modifier = Modifier.padding(bottom = 24.dp),
                textAlign = TextAlign.Center
            )
        }
    }
}


data class ColorObject(
    val name:String,
    val value:Long
)
build.gradle [dependencies]

implementation 'androidx.navigation:navigation-compose:2.4.0-alpha10'
implementation 'com.google.code.gson:gson:2.8.6'
More android jetpack compose tutorials