简体   繁体   中英

Why does the Composable for the route recompose when state changes in the view model?

TLDR: The question is, why do I see both "Route Composable" and "Home Screen" in logcat when composable doesn't depend on the value being updated in my ViewModel? I expected to only see "Home Screen" since that was the only composable that used the state value from the ViewModel.

I recently came across a situation in a production application where my route composable was being called twice, causing a slight flicker in the UI. After digging into it for a few hours I discovered that the cause for the flicker was from using a hiltViewModel within the route for providing the ViewModel to my compose screen. I dug in further only to find out that any time there was a state change within the ViewModel, the Route's composable was recomposed. I would've thought that only the child composable would've recomposed since it was the only one using the state value?

The full project is here but the basic Compose code looks like this. (I removed as much boilerplate as I could)

@Composable
fun AppNav() {
    val navController = rememberNavController()

    NavHost(navController = navController, startDestination = Screen.Home.route) {
        addHome()
    }
}

private fun NavGraphBuilder.addHome() {
    composable(Screen.Home.route) {
        Log.i("Home", "Route Composable")
        val homeViewModel = hiltViewModel<HomeViewModel>()
        HomeScreen(
            homeCount = homeViewModel.homeCount,
            updateCount = { homeViewModel.updateCount() },
        )
    }
}

@Composable
fun HomeScreen(
    homeCount: Int,
    updateCount: () -> Unit,
) {
    Log.i("Home", "Home Screen")
    Column(modifier = Modifier.fillMaxSize()) {
        Spacer(modifier = Modifier.height(25.dp))
        Button(onClick = { updateCount() }) {
            Text("Increment count")
        }
        Spacer(modifier = Modifier.height(25.dp))
        Text("Count is: $homeCount")
    }
}

I guess you need to look at how to use saveState and restoreState when navigating to different routes. Here is an article for this thing: https://developer.android.com/jetpack/compose/navigation#bottom-nav

After reading the documentation more carefully and confirming in the Slack Channel I discovered that the core issue was a misunderstanding of how State works with Compose. I was thinking that it only changed the Composable where the State was consumed when in fact any Composable that contains (reads) State will recompose when the State changes. The code in my example is essentially the same as doing:

composable(Screen.Home.route) {
    Log.i("Home", "Route Composable")
    var homeCount by remember { mutableStateOf(0) }
    HomeScreen(
        homeCount = homeCount,
        updateCount = { homeCount++ },
    )
}

It makes no difference if the State was being updated directly in the Composable like this version or if the State was updated in the ViewModel like the original code. Either way, the Composable containing the State will recompose.

Any time a state is updated a recomposition takes place.

and

Any changes to value will schedule recomposition of any composable functions that read value.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM