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