Separate back stack for each tab in BottomNavigationView Android browser using fragments

I am implementing a BottomNavigationView for navigation in an Android app. I use Fragments to set the content for each tab.

I know how to configure one fragment for each tab, and then switch the fragments when I click on the tab. But how can I create a separate stack for each tab? Here is the code to install one fragment:

 Fragment selectedFragment = ItemsFragment.newInstance(); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.content, selectedFragment); transaction.commit(); 

For example, Fragment A and B will be under tabs 1 and Fragment C and D under tab 2. When the application is running, fragment A is displayed and tab 1 is selected. Then Fragment A can be replaced by fragment B. When tab 2 is selected, fragment C should be displayed. If the 1 Fragment tab is selected, B should be displayed again. At this point, you can use the back button to display Slice A.

And here is the code to configure the following Fragment on the same tab:

 Fragment selectedFragment = ItemsFragment.newInstance(); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.content, selectedFragment); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.addToBackStack(null); ft.commit(); 
+15
source share
5 answers

Finally, I found a solution, it was inspired by a previous answer on StackOverflow: Separate back stack for each tab on Android with snippets
I just replaced TabHost with BottomNavigationView and here is the code:
Primary activity

 public class MainActivity extends AppCompatActivity { private HashMap<String, Stack<Fragment>> mStacks; public static final String TAB_HOME = "tab_home"; public static final String TAB_DASHBOARD = "tab_dashboard"; public static final String TAB_NOTIFICATIONS = "tab_notifications"; private String mCurrentTab; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation); navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener); mStacks = new HashMap<String, Stack<Fragment>>(); mStacks.put(TAB_HOME, new Stack<Fragment>()); mStacks.put(TAB_DASHBOARD, new Stack<Fragment>()); mStacks.put(TAB_NOTIFICATIONS, new Stack<Fragment>()); navigation.setSelectedItemId(R.id.navigation_home); } private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener = new BottomNavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { switch (item.getItemId()) { case R.id.navigation_home: selectedTab(TAB_HOME); return true; case R.id.navigation_dashboard: selectedTab(TAB_DASHBOARD); return true; case R.id.navigation_notifications: selectedTab(TAB_NOTIFICATIONS); return true; } return false; } }; private void gotoFragment(Fragment selectedFragment) { FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); fragmentTransaction.replace(R.id.content, selectedFragment); fragmentTransaction.commit(); } private void selectedTab(String tabId) { mCurrentTab = tabId; if(mStacks.get(tabId).size() == 0){ /* * First time this tab is selected. So add first fragment of that tab. * Dont need animation, so that argument is false. * We are adding a new fragment which is not present in stack. So add to stack is true. */ if(tabId.equals(TAB_HOME)){ pushFragments(tabId, new HomeFragment(),true); }else if(tabId.equals(TAB_DASHBOARD)){ pushFragments(tabId, new DashboardFragment(),true); }else if(tabId.equals(TAB_NOTIFICATIONS)){ pushFragments(tabId, new NotificationsFragment(),true); } }else { /* * We are switching tabs, and target tab is already has atleast one fragment. * No need of animation, no need of stack pushing. Just show the target fragment */ pushFragments(tabId, mStacks.get(tabId).lastElement(),false); } } public void pushFragments(String tag, Fragment fragment, boolean shouldAdd){ if(shouldAdd) mStacks.get(tag).push(fragment); FragmentManager manager = getSupportFragmentManager(); FragmentTransaction ft = manager.beginTransaction(); ft.replace(R.id.content, fragment); ft.commit(); } public void popFragments(){ /* * Select the second last fragment in current tab stack.. * which will be shown after the fragment transaction given below */ Fragment fragment = mStacks.get(mCurrentTab).elementAt(mStacks.get(mCurrentTab).size() - 2); /*pop current fragment from stack.. */ mStacks.get(mCurrentTab).pop(); /* We have the target fragment in hand.. Just show it.. Show a standard navigation animation*/ FragmentManager manager = getSupportFragmentManager(); FragmentTransaction ft = manager.beginTransaction(); ft.replace(R.id.content, fragment); ft.commit(); } @Override public void onBackPressed() { if(mStacks.get(mCurrentTab).size() == 1){ // We are already showing first fragment of current tab, so when back pressed, we will finish this activity.. finish(); return; } /* Goto previous fragment in navigation stack of this tab */ popFragments(); } 

}

Home Fragment Example

 public class HomeFragment extends Fragment { @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_home, container, false); Button gotoNextFragment = (Button) view.findViewById(R.id.gotoHome2); gotoNextFragment.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { ((MainActivity)getActivity()).pushFragments(MainActivity.TAB_HOME, new Home2Fragment(),true); } }); return view; } 

}

+24
source

It’s worth noting that the behavior you described is contrary to Google’s recommendations. https://material.io/guidelines/components/bottom-navigation.html#bottom-navigation-behavior

Navigation through the lower navigation bar should reset the task state.

In other words, if Fragment A and Fragment B "inside" Tab 1 is ok, but if the user opens Fragment B, clicks Tab 2, and then press Tab 1 again, they should see fragment A.

+2
source

This behavior is supported by the new component of the navigation architecture ( https://developer.android.com/topic/libraries/architecture/navigation/ ).

Essentially, you can use NavHostFragment , which is the fragment that controls its own back stack:

Each NavHostFragment has a NavController that defines valid navigation in the navigation host. This includes a navigation graph, as well as a navigation state, such as the current location and back stack, that will be saved and restored along with NavHostFragment itself. https://developer.android.com/reference/androidx/navigation/fragment/NavHostFragment

Here is an example: https://github.com/deisold/navigation


Edit: It turns out that the Navigation Architecture Component does not support separate rear stacks in any case, as commentators note. But, as @ r4jiv007 mentioned, they are working on it and meanwhile have suggested an “official hack”: https://github.com/googlesamples/android-architecture-components/tree/master/NavigationAdvancedSample

+1
source

Suppose you have 5 (A, B, C, D, E) BottomNavigationView menu item, then in Activity create 5 FrameLayout (frmlytA, frmlytB, frmlytC, frmlytD, frmlytE) in parallel as a container for each of these menu items . When you click the BottomNavigation A menu item, then hide all the other FrameLayouts (Visibility = GONE) and just show (Visibility = VISIBLE) the FrameLayout 'frmlytA', in which FragmentA will be placed and above this container, perform further transactions, such as (FragmentA → FragmentX) → Fragment Y). And then, if the user clicks B BottomNavigation, then just hide this (frmlytA) container and show "frmlytB". Then, if the user clicks on menu item A again, then displays “frmlytA”, he must maintain his previous state. This way you can switch between the FrameLayouts container and support the back stack of each container.

0
source

Instead of using the replacement method, use the add snippet,

Instead, ft.replace (R.id.content, selectedFragment);

Use this ft.add (R.id.content, selectedFragment);

  Fragment selectedFragment = ItemsFragment.newInstance(); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.(R.id.content, selectedFragment); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.addToBackStack(null); ft.commit(); 
-4
source

Source: https://habr.com/ru/post/1270733/


All Articles