4

I use Jetpack Compose Navigation in my app.

  • Compose Navigation version: 2.7.7
  • lifecycleRuntimeKtx: 2.8.4
  • Kotlin version: 1.9.25
  • Device: Xiaomi 220333QNY

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.

0

2 Answers 2

2

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

Sign up to request clarification or add additional context in comments.

Comments

-1

When the activity is recreated, the NavController is also recreated, losing the previous back stack state.

The NavBackStackEntry lifecycle state seems to be incorrect (e.g., transitioning from INITIALIZED to DESTROYED), which causes the crash.

You can do the following changes:

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        setContent {
            MyApp(savedInstanceState)
        }
    }
}

@Composable
fun MyApp(savedInstanceState: Bundle?) {
    val navController = rememberSaveable(saver = NavControllerSaver()) {
        rememberNavController()
    }

    NavHost(
        navController = navController,
        startDestination = "screenA"
    ) {
        composable("screenA") {  }
        composable("screenB") {  }
        composable("screenC") {  }
    }
}

@Composable
fun rememberNavControllerWithState(savedInstanceState: Bundle?): NavHostController {
    val navController = rememberNavController()

    savedInstanceState?.let {
        navController.restoreState(it)
    }

    return navController
}

1 Comment

Thanks for answer, but this seems not to be an answer. Function rememberNavControllerWithState() is not called, NavControllerSaver class does not exist

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.