Search code examples
androidandroid-jetpack-composeandroid-navigationandroid-navigation-graphjetpack-compose-navigation

onBackPressedDispatcher breaks Jetpack Compose Navigation


I've found a bug when using onBackPressedDispatcher in a Jetpack compose app.

Here's a simple example to demonstrate the bug. Imagine that the app has the following setup:

  • It has one MainActivity that has MainFragment inside
  • MainFragment contains a NavGraph implemented in Jetpack Compose
  • Nav Graph has 3 Composable screens

in MainActivity onCreate method I have the following line:

onBackPressedDispatcher.addCallback(this, onBackPressedCallback)

For custom handling of application wide backstack

With this setup navigation back and forth works correctly. However, if you bring the activity to onPause state (e.g. by clicking on the device home button and opening the app again) Jetpack Compose navigation somehow breaks and it defaults to the activity navigation implemented with onBackPressedDispatcher instead of composable screens navigation.

In the following video you can see:

  1. I can navigate forth and back correctly
  2. Once I click the home button and open the app again navigation back breaks. I can no longer go back

enter image description here

I've published this demo project on GitHub, so you can check it out yourself: https://github.com/konnovdev/NavigationBugDemo/tree/main

I've tried moving onBackPressedDispatcher subscription to different lifecycle callbacks inside of activity but it didn't make any difference. Note that in this particular app example onBackPressedDispatcher looks redundant as there is only one composable nav graph that has its own navigation handling, but in my real app I do rely on this mechanism.

Am I missing something here? I'm confused why is onPause state breaks the navigation. And I'm not able to find a good workaround for this issue


Solution

  • update, best fix, remove "this" from OnBackpressCallback

    onBackPressedDispatcher.addCallback(onBackPressedCallback)
    

    fix (old)

    connected fragment navcontroller and mainactivity navcontroller to handleOnBackPressed properly, through a sharedviewmodel.

    https://github.com/konnovdev/NavigationBugDemo/pull/1/commits/971e0ec5b5d9589f54bc78559bbe62eaf3d0eede

        private val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            if (!navController.navigateUp()) {
                println("No more back navigation, can exit app or do any other task")
                finish() // Close the app or replace with custom back press handling
            }
        }
    

    and in oncreate

            sharedViewModel.navController.observe(this) { controller ->
               navController = controller
               // Setup back press callback with NavController
    
               onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
            }
    

    viewmodel

    class SharedViewModel : ViewModel() {
        private val _navController = MutableLiveData<NavController>()
        val navController: LiveData<NavController> = _navController
    
        fun setNavController(controller: NavController) {
            _navController.value = controller
        }
    }
    

    and in fragment, setNavController

     NavigationBugDemoTheme {
         val navController = rememberNavController()
         sharedViewModel.setNavController(navController)
         //rest of the code