Hey there! Welcome to my blog on implementing navigation flow between list and detail screen in android using jetpack compose.
Let’s get on with it.

Make sure you are using latest navigation compose dependency. Right now I’m using the below version.

implementation "androidx.navigation:navigation-compose:2.4.0-alpha09"

1. Setup NavHost and controller

This helps navigation to trigger, then composables added to navigation graph builder which needs navigation controller to navigate.

You need navigation controller to handle composable. So let’s create navigation host controller.

val navController = rememberNavController()

Controller should know which one is default or start destination. Let’s save call it cats_list_screen as in a String.

val startDestination: String = "cats_list_screen"

For navigation to occur and show start destination you need to setup NavHost with controller and destination information.

NavHost(
    navController,
    startDestination
) {
    composable(route: String) {
        // Destination
    }
    composable(route: String) {
        // Destination
    }
}

Each compsable inside NavHost is just a new destination and you need to pass route as a String type parameter.

2. Composable routes and arguments

First let’s create an object for managing all destinations routes in one place. Let’s add two destination routes one for showing list and another for details destination.

object CatsDestinations {
    // Route for list of cats
    const val CATS_ROUTE = "cats"
    // Route for single cat details
    const val CAT_DETAIL_ROUTE = "cat"
    // Route for cat Id which is clicked
    const val CAT_DETAIL_ID_KEY = "catId"
}

Let’s create composable destinations.

ListScreen.kt

@Composable
fun CatsList(
    selectedCat: (Int) -> Unit
) {

}

DetailScreen.kt

Trigger composable detail screen navigation with cat Id.

@Composable
fun CatDetails(
    catId: Int
) {
    val context = LocalContext.current
    val cat: Cats = remember(catId) {
        getCat(catId, context)
    }
}

With selected cat Id find the position from list and return Cat details.

fun getCat(
    catId: Int
): Cats = getCatsList().find {
    it.catId == catId
}!!

Add CatsList() function to composable destinations. But first create a variable lambda which takes Int as cat Id and returns nothing (Unit type). Call navigation controller and setup the route for both destinations.

val selectedCat: (Int) -> Unit = { catId: Int ->
    navController.navigate("${CatsDestinations.CAT_DETAIL_ROUTE}/$catId")
}

composable(
    CatsDestinations.CATS_ROUTE
) {
    CatsList(selectedCat = selectedCat)
}

For detail screen composable you need to receive the navigation arguments with type explicitly along with back stack entry.

composable(
    "${CatsDestinations.CAT_DETAIL_ROUTE}/{$CAT_DETAIL_ID_KEY}",
     arguments = listOf(
         navArgument(CAT_DETAIL_ID_KEY) {
             type = NavType.IntType
         }
     )
) { backStackEntry ->
    val arguments = requireNotNull(backStackEntry.arguments)
    CatDetails(
        catId = arguments.getInt(CAT_DETAIL_ID_KEY)
    )
}

3. Compose list and detail screen

Let’s compose items in list for showing list of cats.

ListScreen.kt

@Composable
fun CatsList(
    selectedCat: (Int) -> Unit
) {
    val context = LocalContext.current
    val cats: List<Cats> = getCatsList(context)

    LazyColumn(
        verticalArrangement = Arrangement.spacedBy(12.dp),
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        items(cats) { cat ->
            Row(
                modifier = Modifier
                    .padding(16.dp)
                    .fillMaxWidth()
                    .clickable(onClick = { selectedCat(cat.catId) })
                    .clip(RoundedCornerShape(8.dp)),
                verticalAlignment = Alignment.CenterVertically
            ) {
                Image(
                    painter = painterResource(id = cat.catImage),
                    contentDescription = null,
                    contentScale = ContentScale.Crop,
                    modifier = Modifier
                        .size(72.dp)
                        .clip(RoundedCornerShape(8.dp))
                )
                Text(
                    text = cat.catName,
                    style = MaterialTheme.typography.h4,
                    modifier = Modifier.padding(start = 20.dp)
                )
            }
        }
    }
}

DetailScreen.kt

@Composable
fun CatDetails(
    catId: Int,
) {
    val context = LocalContext.current
    val cat: Cats = remember(catId) {
        getCat(catId, context)
    }

    Surface {
        Column(
            modifier = Modifier.fillMaxSize()
        ) {
            Image(
                painter = painterResource(id = cat.catImage),
                contentDescription = null,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(240.dp),
                contentScale = ContentScale.Crop
            )
            Column(
                modifier = Modifier.padding(horizontal = 16.dp)
            ) {
                Text(
                    text = cat.catName,
                    modifier = Modifier.padding(top = 8.dp),
                    style = MaterialTheme.typography.h4,
                    color = MaterialTheme.colors.onSurface
                )
                Text(
                    text = cat.catDescription,
                    modifier = Modifier.alpha(0.8f),
                    style = MaterialTheme.typography.h6,
                    color = MaterialTheme.colors.onSurface
                )
                Text(
                    text = cat.catAbout,
                    modifier = Modifier
                        .padding(vertical = 12.dp)
                        .alpha(0.7f),
                    style = MaterialTheme.typography.body2,
                    color = MaterialTheme.colors.onSurface,
                    textAlign = TextAlign.Justify
                )
            }
        }
    }
}

4. Navigate up from detail screen

Pass extra argument to CatDetails composable function and new add parameter.

// Attempts to navigate up in the navigation hierarchy.
val navigateUp: () -> Unit = {
    navController.navigateUp()
}

// Let navigateUp trigger onClick the up button.
@Composable
fun CatDetails(
    catId: Int,
    navigateUp: () -> Unit
) {
    IconButton(onClick = navigateUp) {
        Icon(
            imageVector = Icons.Rounded.ArrowBack,
            contentDescription = null
        )
    }
}

5. Structure app navigation

It is much better to keep all your navigation login and code in one file.

A separate function for actions.

class AppActions(
    navController: NavHostController
) {
    val selectedCat: (Int) -> Unit = { catId: Int ->
        navController.navigate("${CatsDestinations.CAT_DETAIL_ROUTE}/$catId")
    }

    val navigateUp: () -> Unit = {
        navController.navigateUp()
    }
}

Move NavHost and composable destinations in single function.

@Composable
fun CatsNavigation() {

    val navController = rememberNavController()
    val actions = remember(navController) { AppActions(navController) }
    val startDestination: String = CatsDestinations.CATS_ROUTE

    NavHost(
        navController = navController,
        startDestination = startDestination
    ) {
        composable(
            CatsDestinations.CATS_ROUTE
        ) {
            CatsList(selectedCat = actions.selectedCat)
        }
        composable(
            "${CatsDestinations.CAT_DETAIL_ROUTE}/{$CAT_DETAIL_ID_KEY}",
            arguments = listOf(
                navArgument(CAT_DETAIL_ID_KEY) {
                    type = NavType.IntType
                }
            )
        ) { backStackEntry ->
            val arguments = requireNotNull(backStackEntry.arguments)
            CatDetails(
                catId = arguments.getInt(CAT_DETAIL_ID_KEY),
                navigateUp = actions.navigateUp
            )
        }
    }
}

That’s it. I layout designs looking bit poor just for sake of this article, you may find better one from GitHub repository below.

6. Project resources

Become Author

We are making content on new declarative UI kit Jetpack Compose, if you are willing to build content join us and we will help you get started.

That’s it guys, thanks for reading and do check my GitHub from below you find more useful repositories.

Here We Go Again : (

if (article == helpful) {
    println("Like and subscribe to blog newsletter.")
} else {
    println("Let me know what i should blog on.")
}

This site uses Akismet to reduce spam. Learn how your comment data is processed.