I use Jetpack Compose Navigation in my app.
I have 2 screens: A, B.
The navigation graph looks like: A⟷B
Where → is navigate(route), and ← is navigateUp() or hardware back button.
I use ViewModel for screen A:
class MyViewModel(private val _openScreenB: () -> Unit) : ViewModel() {
fun openScreenB() {
_openScreenB()
}
}
NavContoller and NavHost:
setContent {
val navController = rememberNavController()
NavHost(
navController = navController,
...
) {
composable("ScreenA") {
val viewModel: MyViewModel = viewModel(
factory = MyViewModelFactory(
openScreenB = {
navController.navigate("ScreenB") {
launchSingleTop = true
}
}
)
)
ScreenA(
navigateToScreenB = {
viewModel.openScreenB()
}
)
}
composable("ScreenB") {
ScreenB(
navigateBack = {
navController.navigateUp()
}
)
}
}
}
Everything works fine until activity recreation (orientation change or switch light/dark mode). After recreation, line val navController = rememberNavController() is called again. The NavController recreates, and now navigateUp() does nothing, hardware back works, and navigate(route) crashes app with error.
java.lang.IllegalStateException: State must be at least CREATED to move to DESTROYED, but was INITIALIZED in component NavBackStackEntry(2d8f0727-c13b-4635-ac94-f279608c3cfc) destination=Destination(0x67884f5a) route=Editor
at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.jvm.kt:131)
at androidx.lifecycle.LifecycleRegistry.setCurrentState(LifecycleRegistry.jvm.kt:107)
at androidx.navigation.NavBackStackEntry.updateState(NavBackStackEntry.kt:186)
at androidx.navigation.NavBackStackEntry.setMaxLifecycle(NavBackStackEntry.kt:159)
at androidx.navigation.NavController.updateBackStackLifecycle$navigation_runtime_release(NavController.kt:1100)
at androidx.navigation.NavController.dispatchOnDestinationChanged(NavController.kt:996)
at androidx.navigation.NavController.navigate(NavController.kt:1882)
at androidx.navigation.NavController.navigate(NavController.kt:1817)
at androidx.navigation.NavController.navigate(NavController.kt:2225)
at androidx.navigation.NavController.navigate$default(NavController.kt:2220)
at ...
I tried to navigate with saveState = true, restoreState = true, and launchSingleTop = true properties, but this doesn't fix the problem.
I also tried to use navController saveState() in onSaveInstanceState and restoreState(bundle) after rememberNavController, but it also doesn't help.
At the first version of my question I don't know that problem was in my ViewModel. I tried to create a minimal reproducible example without ViewModel:
setContent {
val navController = rememberNavController()
NavHost(
navController = navController,
...
) {
composable("ScreenA") {
ScreenA(
navigateToScreenB = {
navController.navigate("ScreenB") {
launchSingleTop = true
}
}
)
}
composable("ScreenB") {
ScreenB(
navigateBack = {
navController.navigateUp()
}
)
}
}
}
When I created it, I realised all was working perfectly. So I guess that the problem was in my ViewModel.
I was passing navigation lambdas to ViewModel constructor for a long time. It caused crashes. I think that saving a navController as a ViewModel property breaks lifecycle of the navController.
Removing all the lambdas with navController from ViewModel constructor solved my problem
P.S. I passed that navigation lambdas directly to composables
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With