tomerpacific
tomerpacific

Reputation: 6565

Jetpack Compose Navigation Is Triggering Recomposition

I have a simple Jetpack Compose application that uses the Navigation Component.

My UI is comprised of the following composables (redacted UI to be concise):

NavHost:

 @Composable
    fun NavHost(
        navController: NavHostController = rememberNavController(),
        startDestination: String = "main"
    ) {
        NavHost(
            navController = navController,
            startDestination = startDestination) {
            composable("main") {
                SomeList(onNavigateToItemView = {
                    navController.navigate("listItem")
                })
            }
            composable("listItem") {
                ItemView()
            }
        }
    }

SomeList:

@Composable
fun SomeList(onNavigateToItemView: () -> Unit) {
    Column {
        Row ( Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.Center
        ) {
            Text(text = Constants.APP_TITLE, fontSize = 30.sp, fontWeight = FontWeight.Bold)
        }
        Box(modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            LazyColumn(
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                items(items) { item->
                    ItemCard(item, onNavigateToItemView)
                }
            }
        }
    }
}

ItemCard:

@Composable
fun ItemCard(item: ItemModel, onNavigateToItemView: () -> Unit) {
    Card(
        border = BorderStroke(2.dp, Color.Cyan),
        modifier = Modifier
            .fillMaxWidth()
            .padding(bottom = 5.dp)
            .clickable {       
                onNavigateToItemView()
            }
    ) {
        Row(
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.SpaceBetween
        )
        {
         ....
        }
     }
 }

Now, whenever the user clicks on an ItemCard, he/she transitions to an ItemView. What I am seeing is that the ItemView is being recomposed several times when navigating to it and also when navigating back from it.

According to the guide linked above,

The NavController's navigate function modifies the NavController's internal state. To comply with the single source of truth principle as much as possible, only the composable function or state holder that hoists the NavController instance and those composable functions that take the NavController as a parameter should make navigation calls. Navigation events triggered from other composable functions lower in the UI hierarchy need to expose those events to the caller appropriately using functions.

And as you can see above, I am following that practice.

So, am I doing something wrong in my implementation or is this just how navigation and Jetpack Compose work together?

The multiple recomposition calls are affecting my UI unnecessarily.

Upvotes: 7

Views: 5837

Answers (3)

blacktiago
blacktiago

Reputation: 393

There must be something that is being mutated in your state dependent for your child composable. Try to inspect what is changing like this:

enter image description here Source: https://developer.android.com/studio/releases/past-releases/as-hedgehog-release-notes#compose-state-in-debugger

Upvotes: 0

PurpleBamboo
PurpleBamboo

Reputation: 31

How is item being used in ItemCard? When I have seen this glitch in the past it means that I have two parameters that are both being changed and causing each other to recompose.

This makes me think that your item parameter might be changing.

Upvotes: 0

Jolan DAUMAS
Jolan DAUMAS

Reputation: 1374

Short Answer

Yes, navigating using compose navigation will recompose your composable each time you are navigating to it.

Workaround

When you are using navController.navigate() it will automatically recompose the targeted route. However, you can use an option to save the current state of the screen that you leave and restore the state of the screen that you target.

For example :

A->B->A->B

The first time you will load A & B you will recompose because you have no state saved yet. The moment you go from B to A (B->A) the state will be restored so you'll not recompose. Same things occurs the second time you go from A to B (A->B)

Use

Accord to documentation, you can make an extension function like this

fun NavController.popUpTo(destination: String) = navigate(destination) {
    popUpTo(graph.findStartDestination().id) {
        saveState = true
    }
    // Restore state when reselecting a previously selected item
    restoreState = true
}

And use it like this


 SomeList(onNavigateToItemView = {
                    navController.popUpTo("listItem")
                })

Upvotes: 2

Related Questions