When I navigate from one screen to another, the composable screen which I'm navigating away from is getting recomposed many times as a result. I'm just curious why this is happening and if i'm doing something wrong. This is the code for my NavHost which I call in my Scaffold in my MainScreen composable which is inside the setContent block of my MainActivity. So it's setContent -> MainScreen -> Scaffold -> MyAppNavHost
MainScreen
MaterialTheme {
val appState = rememberAppState()
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val route = NavRoutes.allRoutes().find { navBackStackEntry?.destination?.route?.contains(it) == true }
Scaffold(
scaffoldState = appState.scaffoldState,
snackbarHost = { hostState ->
hostState.currentSnackbarData?.let {
SnackbarHost(
hostState = hostState,
snackbar = {
Snackbar(
snackbarData = it,
backgroundColor = Color.Red,
contentColor = Color.White
)
}
)
}
},
backgroundColor = colorResource(id = R.color.windowBackground),
topBar = {
TopBar(
isVisible = route !in NavRoutes.topBarInvisibleRoutes(),
title = getTopBarTitle(route, navBackStackEntry?.arguments),
closeButtonEnabled = route in NavRoutes.topBarCloseIconRoutes()
) { navController.navigate(NavRoutes.Home.route) { popUpTo(navController.graph.id) { inclusive = true } } }
},
bottomBar = { BottomNavBar(navController, viewModel.bottomBarState) }
) {
MyAppNavHost(
modifier = Modifier.padding(it),
navController = navController,
showSnackbar = { message, duration ->
appState.showSnackbar(message, duration) {}
},
startDestination = viewModel.getStartRoute()
)
}
LaunchedEffect(navBackStackEntry) {
handleBottomBarState(viewModel, route)
if (route != null && route !in NavRoutes.noAuthRoutes()) {
viewModel.rescheduleOrLogOut()
}
}
if (viewModel.logoutState) {
LogoutDialog(
onDismiss = {
viewModel.logout()
navController.navigate(NavRoutes.Login.route) { popUpTo(navController.graph.id) { inclusive = true } }
}
)
}
}
}
NavHost
@Composable
fun MyAppNavHost(
modifier: Modifier = Modifier,
navController: NavHostController = rememberNavController(),
startDestination: String = NavRoutes.Home.route,
showSnackbar: (String, SnackbarDuration) -> Unit
) {
NavHost(
modifier = modifier,
navController = navController,
startDestination = startDestination
) {
composable(NavRoutes.OnBoarding.route) {
OnBoardingScreen(navController = navController, showSnackbar)
}
composable(NavRoutes.Login.route) {
LoginScreen(viewModel = koinNavViewModel(), navController = navController, showSnackbar = showSnackbar)
}
composable(NavRoutes.Home.route) {
HomeScreen(homeViewModel = koinNavViewModel(), healthViewModel = koinNavViewModel(), navController = navController, showSnackbar = showSnackbar)
}
composable(
route = "${NavRoutes.StartSurvey.route}/{surveyId}/{surveyTitle}",
arguments = listOf(
navArgument("surveyId") { type = NavType.StringType },
navArgument("surveyTitle") { type = NavType.StringType }
)
) { backStackEntry ->
val args = backStackEntry.arguments
val surveyId = args?.getString("surveyId").orEmpty()
val surveyTitle = args?.getString("surveyTitle").orEmpty()
StartSurveyScreen(
surveyId = surveyId,
surveyTitle = surveyTitle,
viewModel = koinNavViewModel { parametersOf(surveyId, surveyTitle) },
navController = navController
)
}
I have tested this on 2 of my screens (showing LoginScreen as an example) and I'm getting the same behavior on both as mentioned in my first paragraph. "LoginScreen recomposed" appears over 10 times at once in Logcat when my app is navigating away from LoginScreen.
@Composable
fun LoginScreen(
viewModel: LoginViewModel = koinViewModel(),
navController: NavController,
showSnackbar: (String, SnackbarDuration) -> Unit
) {
val context = LocalContext.current
Timber.tag("Recomposition").i("LoginScreen recomposed")
val bankIdResultLauncher = bankIdResultLauncher(viewModel = viewModel)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
ScreenContent(
isLoading = uiState.isLoading,
userInput = viewModel.ssnInput.takeIf {
it.length != SSN_LENGTH
} ?: stringResource(id = R.string.saved_ssn, viewModel.ssnInput.substring(0, 8)),
onInputValueChanged = { viewModel.updateSsnUserInput(it) },
onLogInClick = { viewModel.sendBankIdRequest() },
onKeyboardDone = {
(context as Activity).hideKeyboard()
viewModel.sendBankIdRequest()
},
onClearClick = { viewModel.clearInputText() }
)
HandleError(viewModel.error, showSnackbar)
viewModel.loginRequest?.let {
if (it is CallbackRequestSuccess) navController.navigate(NavRoutes.Home.route)
HandleLoginRequest(
request = it,
context = context,
onBankIdNotInstalled = { viewModel.listenAfterBankIdCallback() },
onCallBackSuccess = { viewModel.callbackSuccess(null) },
bankIdResultLauncher = bankIdResultLauncher
)
}
}
I used koinNavViewModel instead of koinViewModel for the viewmodels but I knew it wouldnt make a difference and it didn't. I've also checked this Jetpack Compose NavHost recomposition composable multiple times but It seems the recomposition is happening excessively in my case.