18

I have 2 fragment call CreateRoomFragment and DisplayPhotoFragment,the navigation graph is look like this:

<navigation>    
<fragment
    android:id="@+id/createRoomFragment"
    android:name="package.room.CreateRoomFragment"
    android:label="Create a room"
    tools:layout="@layout/fragment_create_room">
    <action
        android:id="@+id/action_createRoomFragment_to_roomFragment"
        app:destination="@id/roomFragment" />
    <action
        android:id="@+id/action_createRoomFragment_to_displayPhotoFragment"
        app:destination="@id/displayPhotoFragment" />
</fragment>

<fragment
    android:id="@+id/displayPhotoFragment"
    android:name="package.fragment.DisplayPhotoFragment"
    android:label="fragment_display_photo"
    tools:layout="@layout/fragment_display_photo" >
    <argument android:name="bitmap"
              app:argType="android.graphics.Bitmap"/>
</fragment>

So when I wanna to move from CreateRoomFragment to DisplayPhotoFragment,I use the do as below:

NavDirections action = CreateRoomFragmentDirections.actionCreateRoomFragmentToDisplayPhotoFragment(selectedPhoto);
Navigation.findNavController(view).navigate(action);

Doing this,I can navigate to DisplayPhotoFragment.

But when I press back button of the device and also the Back arrow from the toolbar,it cant go back to CreateRoomFragment.

I tried this,but still unable to back to previous fragment:

requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(),
                new OnBackPressedCallback(true) {
                    @Override
                    public void handleOnBackPressed() {

                       navController.navigateUp();  //I tried this 
                       navController.popBackStack(R.id.createRoomFragment,false); //and also this
                    }
                });

Main Problem now:

By using the code above,the screen didnt go back to previous Fragment(CreateRoomFragment).It still stuck in DisplayPhotoFragment,but at the same time,an API method in CreateRoomFragment onViewCreated section is being called.

What causing this? and how can I solve this problem?

ken
  • 2,426
  • 5
  • 43
  • 98

6 Answers6

30

I had the same problem. For me the issue was that I was using a LiveData boolean to decide when to go to the next fragment. When I then navigated back/up the boolean was still true so it would automatically navigate forward again.

Myk
  • 979
  • 6
  • 16
  • 4
    Seriously... That was the same situation for me, tried everything and could not find whats going on. Many many thanks – fairon201 Jun 23 '20 at 23:23
  • 1
    exactly the same issue for me too. Lifesaver. – Rowan Gontier Nov 13 '20 at 06:13
  • @myk how you fix this issue – Shihab Uddin Dec 14 '20 at 11:08
  • @ShihabUddin I decided not to use live-data for this back then. These days you can use single events to do this, or you can use the navigation library and pass in the NavigationController to your ViewModel and make the view model perform the navigation itself. – Myk Dec 17 '20 at 02:00
22

Android maintains a back stack that contains the destinations you've visited. The first destination of your app is placed on the stack when the user opens the app. Each call to the navigate() method puts another destination on top of the stack. Tapping Up or Back calls the NavController.navigateUp() and NavController.popBackStack() methods, respectively, to remove (or pop) the top destination off of the stack.

NavController.popBackStack() returns a boolean indicating whether it successfully popped back to another destination. The most common case when this returns false is when you manually pop the start destination of your graph.

When the method returns false, NavController.getCurrentDestination() returns null. You are responsible for either navigating to a new destination or handling the pop by calling finish() on your Activity.

When navigating using an action, you can optionally pop additional destinations off of the back stack by using popUpTo and popUpToInclusive parameter of the action.

class MyFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val onBackPressedCallback = object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (true == conditionForCustomAction) {
                    CustomActionHere()
                } else  NavHostFragment.findNavController(this@MyFragment).navigateUp();    
        }
    }
    requireActivity().onBackPressedDispatcher.addCallback(
        this, onBackPressedCallback
    )
    ...
}
Sanjay Bhalani
  • 2,424
  • 18
  • 44
2

The best solution for handling navigation using live data is to use the SingleLiveEvent.

You can always use this class which is an extension of MutableLiveData.

https://gist.githubusercontent.com/cdmunoz/ebe5c4104dadc2a461f512ea1ca71495/raw/a17f76754f86a4c0b1a6b43f5c6e6d179535e627/SingleLiveEvent.kt

For a detail run down of this check:

https://proandroiddev.com/singleliveevent-to-help-you-work-with-livedata-and-events-5ac519989c70

Tinashe Makuti
  • 131
  • 1
  • 3
2

Had a similar issue. We still have multiple activities with nav component. So imagine activity A -> activity B, activity B has its own nav and fragments. When the initial fragment tries to pop the back stack there is nowhere to pop back to and the nav controller does not know to finish the activity. So one solution I found was to do

if (!findNavController().popBackStack()) activity?.finish()

If nav controller can not pop back it will finish activity.

  • This is not a solution! , My app is running on single activity using several fragment, using your idea will close the app – Vishva Vijay Aug 07 '22 at 22:21
  • This is not a good comment! My solution explicitly calls out that it works for multi activity, many fragment situation. From what I can tell this would only close the app if there were no fragments on the back stack, in that case closing the app is expected. Could maybe help more if you provided more context to your situation. – Tyler Turnbull Aug 08 '22 at 20:24
0

You can use MutableSharedFlow instead on MutableLiveData if you want to observe the Event only once.

in your viewModel:

 private val _events = MutableSharedFlow<Event>()
    val events = _events.asSharedFlow() // read-only public view

    suspend fun postEvent() {
        _events.emit(event) // suspends until subscribers receive it
    }

In your Activity/Fragment class:

lifecycleScope.launchWhenStarted {
   viewModel.events.collect {
   }
}

viewModel.postEvent()

This will prevent observing data continuously when going back to fragment.

Nishant Rajput
  • 2,053
  • 16
  • 28
0

I had a similar problem to yours: I decided to take advantage of the HomeFragment floating button that comes with the navigation drawer to create a search button, so I used navController.navigate to go to another item in the navigation drawer. Well, it worked perfectly, however, the user could not go back to the previous item...

I solved this by adding navController.popBackStack() before navigating to the new navigation drawer item.

val drawerLayout: DrawerLayout = binding.drawerLayout
val navView: NavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_content_main)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(
    setOf(
        R.id.nav_home, R.id.nav_search
    ), drawerLayout
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
binding.appBarMain.fab.setOnClickListener { view ->
    navController.popBackStack()
    navController.navigate(R.id.nav_search)
}