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.
You can take reference of this GitHub Repository if you need while following this article.
implementation "androidx.navigation:navigation-compose:2.5.1"
1. Setup NavHost and controller
This helps navigation to trigger, then composables added to navigation graph builder which needs navigation controller to navigate.
We will structure whole navigation layer logic code later, so for now let’s start by creating a composable function for navigation entry point or navigation to trigger.
You need navigation controller to handle composable. So let’s create navigation host controller.
@Composable
fun AppNavigation() {
val navController = rememberNavController()
}
Controller should know which one is default or start destination. Let’s create a route and 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 add new 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. This layout designs look bit poor just for sake of this article to keep t simple, you may find better one from GitHub repository below.
6. Project resources
That’s it guys, thanks for reading and do check my GitHub from below you find more useful repositories.

Rajasekhar K E
Hi ! I’m Rajasekhar a Programmer who does Android Development, Creative & Technical writing, Kotlin enthusiast and Engineering graduate. I learn from Open Source and always happy to assist others with my work. I spend most of time Training, Assisting & Mentoring students who are absolute Beginners in android development. I’m also running my startup named Developers Breach which mostly works on contributing to open source.
Here We Go Again : (
if (article == helpful) {
println("Like and subscribe to blog newsletter.")
} else {
println("Let me know what i should blog on.")
}
You must be logged in to post a comment.