Issue
I am using NavigationUI with bottom navigation view and have also set it up with the action bar.
My app starts with the home fragment and when I switch to my search fragment OnDestroyView()
is called on the homeFragment
(expected) but when I switch back from the search fragment to my home fragment onDestroy()
is called and then a new instance of homefragment
is displayed rather than the one that it resuming at the state I left it in. How do I get it to resume at the state it was before?
This is how I have set up my NavigationUI:
NavController navController = Navigation.findNavController(this,
R.id.navigation_host_fragment);
NavigationUI.setupWithNavController(mainBinding.mainActivityBn, navController);
NavigationUI.setupActionBarWithNavController(this,navController);
My Nav Graph:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_activity_navigation"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name=<name>
android:label="Home"
tools:layout="@layout/fragment_home" />
<fragment
android:id="@+id/serachFragment"
android:name=<name>
android:label="Search"
tools:layout="@layout/fragment_search" />
</navigation>
Edit: I am using Bottom Navigation View to navigate between the fragments. When I press the back button the home fragment is loaded as it was in the previous state (expected behaviour).
Solution
As per this issue:
To understand the logic here, we need to look at what
NavigationUI
does when you select a destination. Looking at the source code and removing the unrelated code (animations, secondary menu code):NavOptions.Builder builder = new NavOptions.Builder() .setLaunchSingleTop(true); builder.setPopUpTo(findStartDestination(navController.getGraph()).getId(), false); NavOptions options = builder.build(); navController.navigate(item.getItemId(), null, options);
So the
popUpTo
pops up to the root destination, but usesfalse
to not pop the root destination itself. It then usessetLaunchSingleTop(true)
to avoid having two instances of the start destination on the back stack. The intent here is to not clear anyViewModel
orSavedStateHandle
associated with that root destination when you go back to it.So NavController calls
navigate()
, we pop everything up to but not including the start destination (your first screen) off the back stack, correctly removing the second screen.NavController then calls through to
FragmentNavigator
to do thenavigate()
call.FragmentNavigator
sees thelaunchSingleTop
is true and correctly determines that the fragment is already on the top of the stack. However, Fragments, unlike activities, do not have any equivalent toonNewIntent()
foronNewArguments()
for mimicking thesingleTop
behavior, soFragmentNavigator
pops the previous fragment and adds a new instance of the Fragment, passing in the new arguments (here, justnull
).
So due to how Fragments handle the setLaunchSingleTop(true)
flag, clicking the first item in the bottom nav destroys and recreates the Fragment again. This is why the system back button acts differently - the system back button is just calling popBackStack()
and not using any of the single top behavior.
That issue is still open and indicates that:
The behavior of
FragmentNavigator
with single top is bad and should feel bad. It does not match the behavior of activities and is unintuitive. If you don't mind, let's use this bug to track that work. Ideally, a new instance would not be created, meaning your original MapFragment would just be resumed and would be right back where it was. This would be consistent with what happens when you hit the system back button.
There's no direct workaround to that issue, so the best result (not having this happen) would require that you star that issue and wait for it be fixed. However, you'll note that any state or ViewModels you save into your NavBackStackEntry would be saved over a singleTop
operation:
// Instead of using this:
myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
// You could use:
NavController navController = NavHostFragment.findNavController(this);
NavBackStackEntry entry = navController.getBackStackEntry(R.id.homeFragment);
myViewModel = new ViewModelProvider(entry).get(MyViewModel.class);
However, this would not help with saving the Fragment's view state (i.e., scroll position) since those aren't possible to save into the NavBackStackEntry, but are only saved at the Fragment level.
Answered By - ianhanniballake
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.