Android Jetpack Navigation, BottomNavigationView with Youtube or Instagram like proper back navigation...
Android Jetpack Navigation, BottomNavigationView with auto fragment back stack on back button click?
What I wanted, after choosing multiple tabs one after another by user and user click on back button app must redirect to the last page he/she opened.
I achieved the same using Android ViewPager, by saving the currently selected item in an ArrayList. Is there any auto back stack after Android Jetpack Navigation Release? I want to achieve it using navigation graph
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainActivity">
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</android.support.constraint.ConstraintLayout>
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_people"
android:icon="@drawable/ic_group"
android:title="@string/title_people" />
<item
android:id="@+id/navigation_organization"
android:icon="@drawable/ic_organization"
android:title="@string/title_organization" />
<item
android:id="@+id/navigation_business"
android:icon="@drawable/ic_business"
android:title="@string/title_business" />
<item
android:id="@+id/navigation_tasks"
android:icon="@drawable/ic_dashboard"
android:title="@string/title_tasks" />
</menu>
also added
bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_host_fragment))
I got one answer from Levi Moreira, as follows
navigation.setOnNavigationItemSelectedListener {item ->
onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_host_fragment))
}
But by doing this only happening is that last opened fragment's instance creating again.
Providing proper Back Navigation for BottomNavigationView
add a comment |
Android Jetpack Navigation, BottomNavigationView with auto fragment back stack on back button click?
What I wanted, after choosing multiple tabs one after another by user and user click on back button app must redirect to the last page he/she opened.
I achieved the same using Android ViewPager, by saving the currently selected item in an ArrayList. Is there any auto back stack after Android Jetpack Navigation Release? I want to achieve it using navigation graph
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainActivity">
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</android.support.constraint.ConstraintLayout>
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_people"
android:icon="@drawable/ic_group"
android:title="@string/title_people" />
<item
android:id="@+id/navigation_organization"
android:icon="@drawable/ic_organization"
android:title="@string/title_organization" />
<item
android:id="@+id/navigation_business"
android:icon="@drawable/ic_business"
android:title="@string/title_business" />
<item
android:id="@+id/navigation_tasks"
android:icon="@drawable/ic_dashboard"
android:title="@string/title_tasks" />
</menu>
also added
bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_host_fragment))
I got one answer from Levi Moreira, as follows
navigation.setOnNavigationItemSelectedListener {item ->
onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_host_fragment))
}
But by doing this only happening is that last opened fragment's instance creating again.
Providing proper Back Navigation for BottomNavigationView
Hi @BincyBaby i need same thing did you get any solutions?
– Ramki Anba
Aug 10 '18 at 4:28
not yet got answer
– Bincy Baby
Aug 12 '18 at 11:28
2
Commenting a bit late but upon some digging I found that thepopBackStackis called from theNavController.navigate()function whenNavOptionsare not null. My guess is that at the moment it is not possible to do it out of the box. A custom implementation of NavController is required that accesses themBackStackthrough reflection or something like that.
– HawkPriest
Sep 6 '18 at 14:18
1
If you add a listener to the bottom nav you can override the navigation so that it will pop back stack if the stack already contains the new destination or otherwise perform the normal navigation if it doesn't.if (!navHost.popBackStack(it.itemId, false)) navHost.navigate(it.itemId)
– Marcus Hooper
Oct 18 '18 at 6:25
A workaround for the fragment recreation problem - stackoverflow.com/a/51684125/6024687
– jL4
Dec 6 '18 at 5:55
add a comment |
Android Jetpack Navigation, BottomNavigationView with auto fragment back stack on back button click?
What I wanted, after choosing multiple tabs one after another by user and user click on back button app must redirect to the last page he/she opened.
I achieved the same using Android ViewPager, by saving the currently selected item in an ArrayList. Is there any auto back stack after Android Jetpack Navigation Release? I want to achieve it using navigation graph
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainActivity">
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</android.support.constraint.ConstraintLayout>
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_people"
android:icon="@drawable/ic_group"
android:title="@string/title_people" />
<item
android:id="@+id/navigation_organization"
android:icon="@drawable/ic_organization"
android:title="@string/title_organization" />
<item
android:id="@+id/navigation_business"
android:icon="@drawable/ic_business"
android:title="@string/title_business" />
<item
android:id="@+id/navigation_tasks"
android:icon="@drawable/ic_dashboard"
android:title="@string/title_tasks" />
</menu>
also added
bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_host_fragment))
I got one answer from Levi Moreira, as follows
navigation.setOnNavigationItemSelectedListener {item ->
onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_host_fragment))
}
But by doing this only happening is that last opened fragment's instance creating again.
Providing proper Back Navigation for BottomNavigationView
Android Jetpack Navigation, BottomNavigationView with auto fragment back stack on back button click?
What I wanted, after choosing multiple tabs one after another by user and user click on back button app must redirect to the last page he/she opened.
I achieved the same using Android ViewPager, by saving the currently selected item in an ArrayList. Is there any auto back stack after Android Jetpack Navigation Release? I want to achieve it using navigation graph
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainActivity">
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</android.support.constraint.ConstraintLayout>
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_people"
android:icon="@drawable/ic_group"
android:title="@string/title_people" />
<item
android:id="@+id/navigation_organization"
android:icon="@drawable/ic_organization"
android:title="@string/title_organization" />
<item
android:id="@+id/navigation_business"
android:icon="@drawable/ic_business"
android:title="@string/title_business" />
<item
android:id="@+id/navigation_tasks"
android:icon="@drawable/ic_dashboard"
android:title="@string/title_tasks" />
</menu>
also added
bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_host_fragment))
I got one answer from Levi Moreira, as follows
navigation.setOnNavigationItemSelectedListener {item ->
onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_host_fragment))
}
But by doing this only happening is that last opened fragment's instance creating again.
Providing proper Back Navigation for BottomNavigationView
edited Aug 12 '18 at 10:33
Bincy Baby
asked May 29 '18 at 6:06
Bincy BabyBincy Baby
89612245
89612245
Hi @BincyBaby i need same thing did you get any solutions?
– Ramki Anba
Aug 10 '18 at 4:28
not yet got answer
– Bincy Baby
Aug 12 '18 at 11:28
2
Commenting a bit late but upon some digging I found that thepopBackStackis called from theNavController.navigate()function whenNavOptionsare not null. My guess is that at the moment it is not possible to do it out of the box. A custom implementation of NavController is required that accesses themBackStackthrough reflection or something like that.
– HawkPriest
Sep 6 '18 at 14:18
1
If you add a listener to the bottom nav you can override the navigation so that it will pop back stack if the stack already contains the new destination or otherwise perform the normal navigation if it doesn't.if (!navHost.popBackStack(it.itemId, false)) navHost.navigate(it.itemId)
– Marcus Hooper
Oct 18 '18 at 6:25
A workaround for the fragment recreation problem - stackoverflow.com/a/51684125/6024687
– jL4
Dec 6 '18 at 5:55
add a comment |
Hi @BincyBaby i need same thing did you get any solutions?
– Ramki Anba
Aug 10 '18 at 4:28
not yet got answer
– Bincy Baby
Aug 12 '18 at 11:28
2
Commenting a bit late but upon some digging I found that thepopBackStackis called from theNavController.navigate()function whenNavOptionsare not null. My guess is that at the moment it is not possible to do it out of the box. A custom implementation of NavController is required that accesses themBackStackthrough reflection or something like that.
– HawkPriest
Sep 6 '18 at 14:18
1
If you add a listener to the bottom nav you can override the navigation so that it will pop back stack if the stack already contains the new destination or otherwise perform the normal navigation if it doesn't.if (!navHost.popBackStack(it.itemId, false)) navHost.navigate(it.itemId)
– Marcus Hooper
Oct 18 '18 at 6:25
A workaround for the fragment recreation problem - stackoverflow.com/a/51684125/6024687
– jL4
Dec 6 '18 at 5:55
Hi @BincyBaby i need same thing did you get any solutions?
– Ramki Anba
Aug 10 '18 at 4:28
Hi @BincyBaby i need same thing did you get any solutions?
– Ramki Anba
Aug 10 '18 at 4:28
not yet got answer
– Bincy Baby
Aug 12 '18 at 11:28
not yet got answer
– Bincy Baby
Aug 12 '18 at 11:28
2
2
Commenting a bit late but upon some digging I found that the
popBackStack is called from the NavController.navigate() function when NavOptions are not null. My guess is that at the moment it is not possible to do it out of the box. A custom implementation of NavController is required that accesses the mBackStack through reflection or something like that.– HawkPriest
Sep 6 '18 at 14:18
Commenting a bit late but upon some digging I found that the
popBackStack is called from the NavController.navigate() function when NavOptions are not null. My guess is that at the moment it is not possible to do it out of the box. A custom implementation of NavController is required that accesses the mBackStack through reflection or something like that.– HawkPriest
Sep 6 '18 at 14:18
1
1
If you add a listener to the bottom nav you can override the navigation so that it will pop back stack if the stack already contains the new destination or otherwise perform the normal navigation if it doesn't.
if (!navHost.popBackStack(it.itemId, false)) navHost.navigate(it.itemId)– Marcus Hooper
Oct 18 '18 at 6:25
If you add a listener to the bottom nav you can override the navigation so that it will pop back stack if the stack already contains the new destination or otherwise perform the normal navigation if it doesn't.
if (!navHost.popBackStack(it.itemId, false)) navHost.navigate(it.itemId)– Marcus Hooper
Oct 18 '18 at 6:25
A workaround for the fragment recreation problem - stackoverflow.com/a/51684125/6024687
– jL4
Dec 6 '18 at 5:55
A workaround for the fragment recreation problem - stackoverflow.com/a/51684125/6024687
– jL4
Dec 6 '18 at 5:55
add a comment |
6 Answers
6
active
oldest
votes
You don't really need a ViewPager to work with BottomNavigation and the new Navigation architecture component. I have been working in a sample app that uses exactly the two, see here.
The basic concept is this, you have the main activity that will host the BottomNavigationView and that is the Navigation host for your navigation graph, this is how the xml for it look like:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainActivity">
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</android.support.constraint.ConstraintLayout>
The navigation Menu (tabs menu) for the BottomNavigationView looks like this:
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_people"
android:icon="@drawable/ic_group"
android:title="@string/title_people" />
<item
android:id="@+id/navigation_organization"
android:icon="@drawable/ic_organization"
android:title="@string/title_organization" />
<item
android:id="@+id/navigation_business"
android:icon="@drawable/ic_business"
android:title="@string/title_business" />
<item
android:id="@+id/navigation_tasks"
android:icon="@drawable/ic_dashboard"
android:title="@string/title_tasks" />
</menu>
All of this is just the BottomNavigationView setup. Now to make it work with the Navigation Arch Component you need to go into the navigation graph editor, add all your fragment destinations (in my case I have 5 of them, one for each tab) and set the id of the destination with the same name as the one in the navigation.xml file:

This will tell android to make a link between the tab and the fragment, now every time the user clicks the "Home" tab android will take care of loading up the correct fragment.
There is also one piece of kotlin code that needs to be added to your NavHost (the main activity) to wire things up with the BottomNavigationView:
You need to add in your onCreate:
bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_host_fragment))
This tells android to do the wiring between the Navigation architecture component and the BottomNavigationView. See more in the docs.
To get the same beahvior you have when you use youtube, just add this:
navigation.setOnNavigationItemSelectedListener {item ->
onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_host_fragment))
}
This will make destinations go into the backstack so when you hit the back button, the last visited destination will be popped up.
3
The secret sauce was adding the id in the nav graph. I'm using Navigation Drawer, but the principal is the same
– JamesSugrue
May 31 '18 at 23:21
3
but back navigation is not working properly
– Bincy Baby
Jun 1 '18 at 18:04
Can we have single instance of fragment ?
– Bincy Baby
Jun 2 '18 at 10:12
10
This is working fine with the back-button. But if user click on bottom tabs its not restoring the previously open child-fragment of that tab(if available). It just opening the new instant of (parent) fragment every time user click on bottom tabs. So this way will lead to a confusing/ frustrating nevigation experience to the users if navigated using bottom tabs many times. Dangerous implementation
– Niroshan
Jun 25 '18 at 5:26
2
There's no max number, but material design rules state you should only have 5 tabs max
– Levi Moreira
Sep 30 '18 at 20:36
|
show 6 more comments
You have to set host navigation like below xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary" />
<fragment
android:id="@+id/navigation_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemIconTint="@drawable/color_state_list"
app:itemTextColor="@drawable/color_state_list"
app:menu="@menu/menu_bottom_navigation" />
</LinearLayout>
Setup With Navigation Controller :
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_host_fragment);
NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.getNavController());
menu_bottom_navigation.xml :
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@id/tab1" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab1" />
<item
android:id="@id/tab2" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab2" />
<item
android:id="@id/tab3" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab3" />
</menu>
nav_graph.xml :
<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/nav_graph"
app:startDestination="@id/tab1">
<fragment
android:id="@+id/tab1"
android:name="com.navigationsample.Tab1Fragment"
android:label="@string/tab1"
tools:layout="@layout/fragment_tab_1" />
<fragment
android:id="@+id/tab2"
android:name="com.navigationsample.Tab2Fragment"
android:label="@string/tab2"
tools:layout="@layout/fragment_tab_2"/>
<fragment
android:id="@+id/tab3"
android:name="com.simform.navigationsample.Tab3Fragment"
android:label="@string/tab3"
tools:layout="@layout/fragment_tab_3"/>
</navigation>
By setting up the same id of "nav_graph" to "menu_bottom_navigation" will handle the click of bottom navigation.
You can handle back action using popUpTo property in action tag.

can you elaborate use of popUpTo ?
– Bincy Baby
Aug 12 '18 at 10:49
@BincyBaby popUpTo property helps you to return on particular fragment on back press.
– SANAT
Aug 18 '18 at 15:51
1
@SANAT but how to set up the popUpTo to the fragment imediatelly pressed before? Like, if you were in frag1 went to frag2 and then to frag3, back press should go back to frag2. If you were in frag1 and went directly to frag3, back press sould go back to frag1. ThepopUpToseems to only let you choose one fragment to go back idependently of the user path.
– Allan Veloso
Jan 30 at 22:57
add a comment |
You can have a viewpager setup with bottom navigation view. Each fragment in the viewpager will be a container fragment, it will have child fragments with its own backstack. You can maintain backstack for each tab in viewpager this way
I was using that way, but app starting takes too much time to first launch
– Bincy Baby
May 29 '18 at 6:16
Then you must be doing something wrong, make sure you are not doing some heavy work in the oncreate or oncreateview of the fragments. There is no way it would take time
– Suhaib Roomy
May 29 '18 at 6:21
I have to load contents , i don't think youtube or instagram used ViewPager
– Bincy Baby
May 29 '18 at 6:48
Its definitely a viewpager. Just scroll on one page and try switiching tabs, its really fast and it resumes from the same state. There is no way you can achieve it by changing fragments on the same container, these are multiple fragments viewed using a viewpager
– Suhaib Roomy
May 29 '18 at 9:28
My guess is also that YouTube or Instagram do not useViewPager. The restoring happens because of the backStack pop action that resumes the underlying fragment that is added in the first place not replaced
– HawkPriest
Sep 6 '18 at 14:13
add a comment |
I have made an app like this (still not published on PlayStore) that has the same navigation, maybe its implementation is different from what Google does in their apps, but the functionality is the same.
the structure involves I have Main Activity that I switch the content of it by showing/hiding fragments using:
public void switchTo(final Fragment fragment, final String tag /*Each fragment should have a different Tag*/) {
// We compare if the current stack is the current fragment we try to show
if (fragment == getSupportFragmentManager().getPrimaryNavigationFragment()) {
return;
}
// We need to hide the current showing fragment (primary fragment)
final Fragment currentShowingFragment = getSupportFragmentManager().getPrimaryNavigationFragment();
final FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (currentShowingFragment != null) {
fragmentTransaction.hide(currentShowingFragment);
}
// We try to find that fragment if it was already added before
final Fragment alreadyAddedFragment = getSupportFragmentManager().findFragmentByTag(tag);
if (alreadyAddedFragment != null) {
// Since its already added before we just set it as primary navigation and show it again
fragmentTransaction.setPrimaryNavigationFragment(alreadyAddedFragment);
fragmentTransaction.show(alreadyAddedFragment);
} else {
// We add the new fragment and then show it
fragmentTransaction.add(containerId, fragment, tag);
fragmentTransaction.show(fragment);
// We set it as the primary navigation to support back stack and back navigation
fragmentTransaction.setPrimaryNavigationFragment(fragment);
}
fragmentTransaction.commit();
}
add a comment |
If you have a bottomNavigationView with 3 items corresponding to 3 Fragments: FragmentA, FragmentB and FragmentC where FragmentA is the startDestination in your navigation graph, then when you're on FragmentB or FragmentC and you click back, you're going to be redirected to FragmentA, that's the behavior recommended by Google and that's implemented by default.
If however you wish to alter this behavior, you'll need to either use a ViewPager as was suggested by some of the other answers, or manually handle the fragments backStack and back transactions yourself -which in a way would undermine the use of the Navigation component altogether-.
3
But youtube, Instagram, Saavn has different behaviour
– Bincy Baby
Aug 29 '18 at 2:05
True, there's no right or wrong way of doing it, it's just about what google supports by default (and thus recommends) and what your needs are. If these two don't align you need to work around it.
– Husayn Hakeem
Aug 30 '18 at 18:26
But the problem is that if you are using the JetPack Navigation the backStack will be empty. Apparently JetPack is not adding nothing to the back stack when handling BottomNavigation clicks.
– Allan Veloso
Jan 30 at 23:12
add a comment |
First, let me clarify how Youtube and Instagram handles fragment navigation.
- When the user is on a detail fragment, back or up pop the stack once, with the state properly restaured. A second click on the already selected bottom bar item pop all the stack to the root, refreshing it
- When the user is on a root fragment, back goes to the last menu selected on the bottom bar, displaying the last detail fragment, with the state properly restaured (JetPack doesn't)
- When the user is on the start destination fragment, back finishes activity
None of the other answers above solve all this problems using the jetpack navigation.
JetPack navigation has no standard way to do this, the way that I found more simple is to dividing the navigation xml graph into one for each bottom navigation item, handling the back stack between the navigation items myself using the activity FragmentManager and use the JetPack NavController to handle the internal navigation between root and detail fragments (its implementation uses the childFragmentManager stack).
Suppose you have in your navigation folder this 3 xmls:
res/navigation/
navigation_feed.xml
navigation_explore.xml
navigation_profile.xml
Have your destinationIds inside the navigation xmls the same of your bottomNavigationBar menu ids. Also, to each xml set the app:startDestination to the fragment that you want as the root of the navigation item.
Create a class BottomNavController.kt:
class BottomNavController(
val context: Context,
@IdRes val containerId: Int,
@IdRes val appStartDestinationId: Int
) {
private val navigationBackStack = BackStack.of(appStartDestinationId)
lateinit var activity: Activity
lateinit var fragmentManager: FragmentManager
private var listener: OnNavigationItemChanged? = null
private var navGraphProvider: NavGraphProvider? = null
interface OnNavigationItemChanged {
fun onItemChanged(itemId: Int)
}
interface NavGraphProvider {
@NavigationRes
fun getNavGraphId(itemId: Int): Int
}
init {
var ctx = context
while (ctx is ContextWrapper) {
if (ctx is Activity) {
activity = ctx
fragmentManager = (activity as FragmentActivity).supportFragmentManager
break
}
ctx = ctx.baseContext
}
}
fun setOnItemNavigationChanged(listener: (itemId: Int) -> Unit) {
this.listener = object : OnNavigationItemChanged {
override fun onItemChanged(itemId: Int) {
listener.invoke(itemId)
}
}
}
fun setNavGraphProvider(provider: NavGraphProvider) {
navGraphProvider = provider
}
fun onNavigationItemReselected(item: MenuItem) {
// If the user press a second time the navigation button, we pop the back stack to the root
activity.findNavController(containerId).popBackStack(item.itemId, false)
}
fun onNavigationItemSelected(itemId: Int = navigationBackStack.last()): Boolean {
// Replace fragment representing a navigation item
val fragment = fragmentManager.findFragmentByTag(itemId.toString())
?: NavHostFragment.create(navGraphProvider?.getNavGraphId(itemId)
?: throw RuntimeException("You need to set up a NavGraphProvider with " +
"BottomNavController#setNavGraphProvider")
)
fragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.nav_default_enter_anim,
R.anim.nav_default_exit_anim,
R.anim.nav_default_pop_enter_anim,
R.anim.nav_default_pop_exit_anim
)
.replace(containerId, fragment, itemId.toString())
.addToBackStack(null)
.commit()
// Add to back stack
navigationBackStack.moveLast(itemId)
listener?.onItemChanged(itemId)
return true
}
fun onBackPressed() {
val childFragmentManager = fragmentManager.findFragmentById(containerId)!!
.childFragmentManager
when {
// We should always try to go back on the child fragment manager stack before going to
// the navigation stack. It's important to use the child fragment manager instead of the
// NavController because if the user change tabs super fast commit of the
// supportFragmentManager may mess up with the NavController child fragment manager back
// stack
childFragmentManager.popBackStackImmediate() -> {
}
// Fragment back stack is empty so try to go back on the navigation stack
navigationBackStack.size > 1 -> {
// Remove last item from back stack
navigationBackStack.removeLast()
// Update the container with new fragment
onNavigationItemSelected()
}
// If the stack has only one and it's not the navigation home we should
// ensure that the application always leave from startDestination
navigationBackStack.last() != appStartDestinationId -> {
navigationBackStack.removeLast()
navigationBackStack.add(0, appStartDestinationId)
onNavigationItemSelected()
}
// Navigation stack is empty, so finish the activity
else -> activity.finish()
}
}
private class BackStack : ArrayList<Int>() {
companion object {
fun of(vararg elements: Int): BackStack {
val b = BackStack()
b.addAll(elements.toTypedArray())
return b
}
}
fun removeLast() = removeAt(size - 1)
fun moveLast(item: Int) {
remove(item)
add(item)
}
}
}
// Convenience extension to set up the navigation
fun BottomNavigationView.setUpNavigation(bottomNavController: BottomNavController, onReselect: ((menuItem: MenuItem) -> Unit)? = null) {
setOnNavigationItemSelectedListener {
bottomNavController.onNavigationItemSelected(it.itemId)
}
setOnNavigationItemReselectedListener {
bottomNavController.onNavigationItemReselected(it)
onReselect?.invoke(it)
}
bottomNavController.setOnItemNavigationChanged { itemId ->
menu.findItem(itemId).isChecked = true
}
}
Do your layout main.xml like this:
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottomNavigationView"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
Use on your activity like this:
class MainActivity : AppCompatActivity(),
BottomNavController.NavGraphProvider {
private val navController by lazy(LazyThreadSafetyMode.NONE) {
Navigation.findNavController(this, R.id.container)
}
private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) {
BottomNavController(this, R.id.container, R.id.navigation_feed)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
bottomNavController.setNavGraphProvider(this)
bottomNavigationView.setUpNavigation(bottomNavController)
if (savedInstanceState == null) bottomNavController
.onNavigationItemSelected()
// do your things...
}
override fun getNavGraphId(itemId: Int) = when (itemId) {
R.id.navigation_feed -> R.navigation.navigation_feed
R.id.navigation_explore -> R.navigation.navigation_explore
R.id.navigation_profile -> R.navigation.navigation_profile
else -> R.navigation.navigation_feed
}
override fun onSupportNavigateUp(): Boolean = navController
.navigateUp()
override fun onBackPressed() = bottomNavController.onBackPressed()
}
this solution looks good but there are some things i noticed :<FrameLayout />should be aNavHostFragment, every graph got it's own home default so doing thisif (savedInstanceState == null) bottomNavController .onNavigationItemSelected()will trigger the fragment two times , it doesn't hold states for the fragments.
– Sim
Mar 4 at 6:46
The idea of the saved instance state exactly avoided the fragment be created two times. I can't check this because I ended up extending theNavController, and creating a custom Navigator exclusively forNavHostFragments, adding it to this NavController (I calledNavHostFragmentNavController). Then I create a graph called navigation_main.xml with the <nav-fragment> elements that represent each a bottom navigation item. The new implementations are bigger but the usage quite simple. The code still has some small bugs that I did not finish yet. I will post it when I fix them.
– Allan Veloso
Mar 4 at 14:52
yes i guess for now extending theNavControlleris a sane solution, till Google release the sample.
– Sim
Mar 5 at 7:18
Shouldn't here a destination be passed as the last parameter instead ofR.id.navigation_feed?private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) { BottomNavController(this, R.id.container, R.id.navigation_feed) }
– WWJD
Mar 5 at 10:19
1
@WWJD, R.id.navigation_feed is a destination. I named the graph id with the same name as it's an initial destination, soR.navigation.navigation_feedhas aR.id.navigation_feeddestination.
– Allan Veloso
Mar 5 at 14:09
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f50577356%2fandroid-jetpack-navigation-bottomnavigationview-with-youtube-or-instagram-like%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
6 Answers
6
active
oldest
votes
6 Answers
6
active
oldest
votes
active
oldest
votes
active
oldest
votes
You don't really need a ViewPager to work with BottomNavigation and the new Navigation architecture component. I have been working in a sample app that uses exactly the two, see here.
The basic concept is this, you have the main activity that will host the BottomNavigationView and that is the Navigation host for your navigation graph, this is how the xml for it look like:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainActivity">
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</android.support.constraint.ConstraintLayout>
The navigation Menu (tabs menu) for the BottomNavigationView looks like this:
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_people"
android:icon="@drawable/ic_group"
android:title="@string/title_people" />
<item
android:id="@+id/navigation_organization"
android:icon="@drawable/ic_organization"
android:title="@string/title_organization" />
<item
android:id="@+id/navigation_business"
android:icon="@drawable/ic_business"
android:title="@string/title_business" />
<item
android:id="@+id/navigation_tasks"
android:icon="@drawable/ic_dashboard"
android:title="@string/title_tasks" />
</menu>
All of this is just the BottomNavigationView setup. Now to make it work with the Navigation Arch Component you need to go into the navigation graph editor, add all your fragment destinations (in my case I have 5 of them, one for each tab) and set the id of the destination with the same name as the one in the navigation.xml file:

This will tell android to make a link between the tab and the fragment, now every time the user clicks the "Home" tab android will take care of loading up the correct fragment.
There is also one piece of kotlin code that needs to be added to your NavHost (the main activity) to wire things up with the BottomNavigationView:
You need to add in your onCreate:
bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_host_fragment))
This tells android to do the wiring between the Navigation architecture component and the BottomNavigationView. See more in the docs.
To get the same beahvior you have when you use youtube, just add this:
navigation.setOnNavigationItemSelectedListener {item ->
onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_host_fragment))
}
This will make destinations go into the backstack so when you hit the back button, the last visited destination will be popped up.
3
The secret sauce was adding the id in the nav graph. I'm using Navigation Drawer, but the principal is the same
– JamesSugrue
May 31 '18 at 23:21
3
but back navigation is not working properly
– Bincy Baby
Jun 1 '18 at 18:04
Can we have single instance of fragment ?
– Bincy Baby
Jun 2 '18 at 10:12
10
This is working fine with the back-button. But if user click on bottom tabs its not restoring the previously open child-fragment of that tab(if available). It just opening the new instant of (parent) fragment every time user click on bottom tabs. So this way will lead to a confusing/ frustrating nevigation experience to the users if navigated using bottom tabs many times. Dangerous implementation
– Niroshan
Jun 25 '18 at 5:26
2
There's no max number, but material design rules state you should only have 5 tabs max
– Levi Moreira
Sep 30 '18 at 20:36
|
show 6 more comments
You don't really need a ViewPager to work with BottomNavigation and the new Navigation architecture component. I have been working in a sample app that uses exactly the two, see here.
The basic concept is this, you have the main activity that will host the BottomNavigationView and that is the Navigation host for your navigation graph, this is how the xml for it look like:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainActivity">
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</android.support.constraint.ConstraintLayout>
The navigation Menu (tabs menu) for the BottomNavigationView looks like this:
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_people"
android:icon="@drawable/ic_group"
android:title="@string/title_people" />
<item
android:id="@+id/navigation_organization"
android:icon="@drawable/ic_organization"
android:title="@string/title_organization" />
<item
android:id="@+id/navigation_business"
android:icon="@drawable/ic_business"
android:title="@string/title_business" />
<item
android:id="@+id/navigation_tasks"
android:icon="@drawable/ic_dashboard"
android:title="@string/title_tasks" />
</menu>
All of this is just the BottomNavigationView setup. Now to make it work with the Navigation Arch Component you need to go into the navigation graph editor, add all your fragment destinations (in my case I have 5 of them, one for each tab) and set the id of the destination with the same name as the one in the navigation.xml file:

This will tell android to make a link between the tab and the fragment, now every time the user clicks the "Home" tab android will take care of loading up the correct fragment.
There is also one piece of kotlin code that needs to be added to your NavHost (the main activity) to wire things up with the BottomNavigationView:
You need to add in your onCreate:
bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_host_fragment))
This tells android to do the wiring between the Navigation architecture component and the BottomNavigationView. See more in the docs.
To get the same beahvior you have when you use youtube, just add this:
navigation.setOnNavigationItemSelectedListener {item ->
onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_host_fragment))
}
This will make destinations go into the backstack so when you hit the back button, the last visited destination will be popped up.
3
The secret sauce was adding the id in the nav graph. I'm using Navigation Drawer, but the principal is the same
– JamesSugrue
May 31 '18 at 23:21
3
but back navigation is not working properly
– Bincy Baby
Jun 1 '18 at 18:04
Can we have single instance of fragment ?
– Bincy Baby
Jun 2 '18 at 10:12
10
This is working fine with the back-button. But if user click on bottom tabs its not restoring the previously open child-fragment of that tab(if available). It just opening the new instant of (parent) fragment every time user click on bottom tabs. So this way will lead to a confusing/ frustrating nevigation experience to the users if navigated using bottom tabs many times. Dangerous implementation
– Niroshan
Jun 25 '18 at 5:26
2
There's no max number, but material design rules state you should only have 5 tabs max
– Levi Moreira
Sep 30 '18 at 20:36
|
show 6 more comments
You don't really need a ViewPager to work with BottomNavigation and the new Navigation architecture component. I have been working in a sample app that uses exactly the two, see here.
The basic concept is this, you have the main activity that will host the BottomNavigationView and that is the Navigation host for your navigation graph, this is how the xml for it look like:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainActivity">
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</android.support.constraint.ConstraintLayout>
The navigation Menu (tabs menu) for the BottomNavigationView looks like this:
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_people"
android:icon="@drawable/ic_group"
android:title="@string/title_people" />
<item
android:id="@+id/navigation_organization"
android:icon="@drawable/ic_organization"
android:title="@string/title_organization" />
<item
android:id="@+id/navigation_business"
android:icon="@drawable/ic_business"
android:title="@string/title_business" />
<item
android:id="@+id/navigation_tasks"
android:icon="@drawable/ic_dashboard"
android:title="@string/title_tasks" />
</menu>
All of this is just the BottomNavigationView setup. Now to make it work with the Navigation Arch Component you need to go into the navigation graph editor, add all your fragment destinations (in my case I have 5 of them, one for each tab) and set the id of the destination with the same name as the one in the navigation.xml file:

This will tell android to make a link between the tab and the fragment, now every time the user clicks the "Home" tab android will take care of loading up the correct fragment.
There is also one piece of kotlin code that needs to be added to your NavHost (the main activity) to wire things up with the BottomNavigationView:
You need to add in your onCreate:
bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_host_fragment))
This tells android to do the wiring between the Navigation architecture component and the BottomNavigationView. See more in the docs.
To get the same beahvior you have when you use youtube, just add this:
navigation.setOnNavigationItemSelectedListener {item ->
onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_host_fragment))
}
This will make destinations go into the backstack so when you hit the back button, the last visited destination will be popped up.
You don't really need a ViewPager to work with BottomNavigation and the new Navigation architecture component. I have been working in a sample app that uses exactly the two, see here.
The basic concept is this, you have the main activity that will host the BottomNavigationView and that is the Navigation host for your navigation graph, this is how the xml for it look like:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main.MainActivity">
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</android.support.constraint.ConstraintLayout>
The navigation Menu (tabs menu) for the BottomNavigationView looks like this:
navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_people"
android:icon="@drawable/ic_group"
android:title="@string/title_people" />
<item
android:id="@+id/navigation_organization"
android:icon="@drawable/ic_organization"
android:title="@string/title_organization" />
<item
android:id="@+id/navigation_business"
android:icon="@drawable/ic_business"
android:title="@string/title_business" />
<item
android:id="@+id/navigation_tasks"
android:icon="@drawable/ic_dashboard"
android:title="@string/title_tasks" />
</menu>
All of this is just the BottomNavigationView setup. Now to make it work with the Navigation Arch Component you need to go into the navigation graph editor, add all your fragment destinations (in my case I have 5 of them, one for each tab) and set the id of the destination with the same name as the one in the navigation.xml file:

This will tell android to make a link between the tab and the fragment, now every time the user clicks the "Home" tab android will take care of loading up the correct fragment.
There is also one piece of kotlin code that needs to be added to your NavHost (the main activity) to wire things up with the BottomNavigationView:
You need to add in your onCreate:
bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_host_fragment))
This tells android to do the wiring between the Navigation architecture component and the BottomNavigationView. See more in the docs.
To get the same beahvior you have when you use youtube, just add this:
navigation.setOnNavigationItemSelectedListener {item ->
onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_host_fragment))
}
This will make destinations go into the backstack so when you hit the back button, the last visited destination will be popped up.
edited Jun 1 '18 at 18:43
answered May 31 '18 at 14:18
Levi MoreiraLevi Moreira
8,44731333
8,44731333
3
The secret sauce was adding the id in the nav graph. I'm using Navigation Drawer, but the principal is the same
– JamesSugrue
May 31 '18 at 23:21
3
but back navigation is not working properly
– Bincy Baby
Jun 1 '18 at 18:04
Can we have single instance of fragment ?
– Bincy Baby
Jun 2 '18 at 10:12
10
This is working fine with the back-button. But if user click on bottom tabs its not restoring the previously open child-fragment of that tab(if available). It just opening the new instant of (parent) fragment every time user click on bottom tabs. So this way will lead to a confusing/ frustrating nevigation experience to the users if navigated using bottom tabs many times. Dangerous implementation
– Niroshan
Jun 25 '18 at 5:26
2
There's no max number, but material design rules state you should only have 5 tabs max
– Levi Moreira
Sep 30 '18 at 20:36
|
show 6 more comments
3
The secret sauce was adding the id in the nav graph. I'm using Navigation Drawer, but the principal is the same
– JamesSugrue
May 31 '18 at 23:21
3
but back navigation is not working properly
– Bincy Baby
Jun 1 '18 at 18:04
Can we have single instance of fragment ?
– Bincy Baby
Jun 2 '18 at 10:12
10
This is working fine with the back-button. But if user click on bottom tabs its not restoring the previously open child-fragment of that tab(if available). It just opening the new instant of (parent) fragment every time user click on bottom tabs. So this way will lead to a confusing/ frustrating nevigation experience to the users if navigated using bottom tabs many times. Dangerous implementation
– Niroshan
Jun 25 '18 at 5:26
2
There's no max number, but material design rules state you should only have 5 tabs max
– Levi Moreira
Sep 30 '18 at 20:36
3
3
The secret sauce was adding the id in the nav graph. I'm using Navigation Drawer, but the principal is the same
– JamesSugrue
May 31 '18 at 23:21
The secret sauce was adding the id in the nav graph. I'm using Navigation Drawer, but the principal is the same
– JamesSugrue
May 31 '18 at 23:21
3
3
but back navigation is not working properly
– Bincy Baby
Jun 1 '18 at 18:04
but back navigation is not working properly
– Bincy Baby
Jun 1 '18 at 18:04
Can we have single instance of fragment ?
– Bincy Baby
Jun 2 '18 at 10:12
Can we have single instance of fragment ?
– Bincy Baby
Jun 2 '18 at 10:12
10
10
This is working fine with the back-button. But if user click on bottom tabs its not restoring the previously open child-fragment of that tab(if available). It just opening the new instant of (parent) fragment every time user click on bottom tabs. So this way will lead to a confusing/ frustrating nevigation experience to the users if navigated using bottom tabs many times. Dangerous implementation
– Niroshan
Jun 25 '18 at 5:26
This is working fine with the back-button. But if user click on bottom tabs its not restoring the previously open child-fragment of that tab(if available). It just opening the new instant of (parent) fragment every time user click on bottom tabs. So this way will lead to a confusing/ frustrating nevigation experience to the users if navigated using bottom tabs many times. Dangerous implementation
– Niroshan
Jun 25 '18 at 5:26
2
2
There's no max number, but material design rules state you should only have 5 tabs max
– Levi Moreira
Sep 30 '18 at 20:36
There's no max number, but material design rules state you should only have 5 tabs max
– Levi Moreira
Sep 30 '18 at 20:36
|
show 6 more comments
You have to set host navigation like below xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary" />
<fragment
android:id="@+id/navigation_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemIconTint="@drawable/color_state_list"
app:itemTextColor="@drawable/color_state_list"
app:menu="@menu/menu_bottom_navigation" />
</LinearLayout>
Setup With Navigation Controller :
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_host_fragment);
NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.getNavController());
menu_bottom_navigation.xml :
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@id/tab1" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab1" />
<item
android:id="@id/tab2" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab2" />
<item
android:id="@id/tab3" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab3" />
</menu>
nav_graph.xml :
<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/nav_graph"
app:startDestination="@id/tab1">
<fragment
android:id="@+id/tab1"
android:name="com.navigationsample.Tab1Fragment"
android:label="@string/tab1"
tools:layout="@layout/fragment_tab_1" />
<fragment
android:id="@+id/tab2"
android:name="com.navigationsample.Tab2Fragment"
android:label="@string/tab2"
tools:layout="@layout/fragment_tab_2"/>
<fragment
android:id="@+id/tab3"
android:name="com.simform.navigationsample.Tab3Fragment"
android:label="@string/tab3"
tools:layout="@layout/fragment_tab_3"/>
</navigation>
By setting up the same id of "nav_graph" to "menu_bottom_navigation" will handle the click of bottom navigation.
You can handle back action using popUpTo property in action tag.

can you elaborate use of popUpTo ?
– Bincy Baby
Aug 12 '18 at 10:49
@BincyBaby popUpTo property helps you to return on particular fragment on back press.
– SANAT
Aug 18 '18 at 15:51
1
@SANAT but how to set up the popUpTo to the fragment imediatelly pressed before? Like, if you were in frag1 went to frag2 and then to frag3, back press should go back to frag2. If you were in frag1 and went directly to frag3, back press sould go back to frag1. ThepopUpToseems to only let you choose one fragment to go back idependently of the user path.
– Allan Veloso
Jan 30 at 22:57
add a comment |
You have to set host navigation like below xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary" />
<fragment
android:id="@+id/navigation_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemIconTint="@drawable/color_state_list"
app:itemTextColor="@drawable/color_state_list"
app:menu="@menu/menu_bottom_navigation" />
</LinearLayout>
Setup With Navigation Controller :
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_host_fragment);
NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.getNavController());
menu_bottom_navigation.xml :
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@id/tab1" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab1" />
<item
android:id="@id/tab2" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab2" />
<item
android:id="@id/tab3" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab3" />
</menu>
nav_graph.xml :
<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/nav_graph"
app:startDestination="@id/tab1">
<fragment
android:id="@+id/tab1"
android:name="com.navigationsample.Tab1Fragment"
android:label="@string/tab1"
tools:layout="@layout/fragment_tab_1" />
<fragment
android:id="@+id/tab2"
android:name="com.navigationsample.Tab2Fragment"
android:label="@string/tab2"
tools:layout="@layout/fragment_tab_2"/>
<fragment
android:id="@+id/tab3"
android:name="com.simform.navigationsample.Tab3Fragment"
android:label="@string/tab3"
tools:layout="@layout/fragment_tab_3"/>
</navigation>
By setting up the same id of "nav_graph" to "menu_bottom_navigation" will handle the click of bottom navigation.
You can handle back action using popUpTo property in action tag.

can you elaborate use of popUpTo ?
– Bincy Baby
Aug 12 '18 at 10:49
@BincyBaby popUpTo property helps you to return on particular fragment on back press.
– SANAT
Aug 18 '18 at 15:51
1
@SANAT but how to set up the popUpTo to the fragment imediatelly pressed before? Like, if you were in frag1 went to frag2 and then to frag3, back press should go back to frag2. If you were in frag1 and went directly to frag3, back press sould go back to frag1. ThepopUpToseems to only let you choose one fragment to go back idependently of the user path.
– Allan Veloso
Jan 30 at 22:57
add a comment |
You have to set host navigation like below xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary" />
<fragment
android:id="@+id/navigation_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemIconTint="@drawable/color_state_list"
app:itemTextColor="@drawable/color_state_list"
app:menu="@menu/menu_bottom_navigation" />
</LinearLayout>
Setup With Navigation Controller :
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_host_fragment);
NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.getNavController());
menu_bottom_navigation.xml :
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@id/tab1" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab1" />
<item
android:id="@id/tab2" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab2" />
<item
android:id="@id/tab3" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab3" />
</menu>
nav_graph.xml :
<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/nav_graph"
app:startDestination="@id/tab1">
<fragment
android:id="@+id/tab1"
android:name="com.navigationsample.Tab1Fragment"
android:label="@string/tab1"
tools:layout="@layout/fragment_tab_1" />
<fragment
android:id="@+id/tab2"
android:name="com.navigationsample.Tab2Fragment"
android:label="@string/tab2"
tools:layout="@layout/fragment_tab_2"/>
<fragment
android:id="@+id/tab3"
android:name="com.simform.navigationsample.Tab3Fragment"
android:label="@string/tab3"
tools:layout="@layout/fragment_tab_3"/>
</navigation>
By setting up the same id of "nav_graph" to "menu_bottom_navigation" will handle the click of bottom navigation.
You can handle back action using popUpTo property in action tag.

You have to set host navigation like below xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary" />
<fragment
android:id="@+id/navigation_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemIconTint="@drawable/color_state_list"
app:itemTextColor="@drawable/color_state_list"
app:menu="@menu/menu_bottom_navigation" />
</LinearLayout>
Setup With Navigation Controller :
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_host_fragment);
NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.getNavController());
menu_bottom_navigation.xml :
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@id/tab1" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab1" />
<item
android:id="@id/tab2" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab2" />
<item
android:id="@id/tab3" // Id of navigation graph
android:icon="@mipmap/ic_launcher"
android:title="@string/tab3" />
</menu>
nav_graph.xml :
<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/nav_graph"
app:startDestination="@id/tab1">
<fragment
android:id="@+id/tab1"
android:name="com.navigationsample.Tab1Fragment"
android:label="@string/tab1"
tools:layout="@layout/fragment_tab_1" />
<fragment
android:id="@+id/tab2"
android:name="com.navigationsample.Tab2Fragment"
android:label="@string/tab2"
tools:layout="@layout/fragment_tab_2"/>
<fragment
android:id="@+id/tab3"
android:name="com.simform.navigationsample.Tab3Fragment"
android:label="@string/tab3"
tools:layout="@layout/fragment_tab_3"/>
</navigation>
By setting up the same id of "nav_graph" to "menu_bottom_navigation" will handle the click of bottom navigation.
You can handle back action using popUpTo property in action tag.

edited Jun 20 '18 at 12:34
answered Jun 20 '18 at 12:27
SANATSANAT
3,9592643
3,9592643
can you elaborate use of popUpTo ?
– Bincy Baby
Aug 12 '18 at 10:49
@BincyBaby popUpTo property helps you to return on particular fragment on back press.
– SANAT
Aug 18 '18 at 15:51
1
@SANAT but how to set up the popUpTo to the fragment imediatelly pressed before? Like, if you were in frag1 went to frag2 and then to frag3, back press should go back to frag2. If you were in frag1 and went directly to frag3, back press sould go back to frag1. ThepopUpToseems to only let you choose one fragment to go back idependently of the user path.
– Allan Veloso
Jan 30 at 22:57
add a comment |
can you elaborate use of popUpTo ?
– Bincy Baby
Aug 12 '18 at 10:49
@BincyBaby popUpTo property helps you to return on particular fragment on back press.
– SANAT
Aug 18 '18 at 15:51
1
@SANAT but how to set up the popUpTo to the fragment imediatelly pressed before? Like, if you were in frag1 went to frag2 and then to frag3, back press should go back to frag2. If you were in frag1 and went directly to frag3, back press sould go back to frag1. ThepopUpToseems to only let you choose one fragment to go back idependently of the user path.
– Allan Veloso
Jan 30 at 22:57
can you elaborate use of popUpTo ?
– Bincy Baby
Aug 12 '18 at 10:49
can you elaborate use of popUpTo ?
– Bincy Baby
Aug 12 '18 at 10:49
@BincyBaby popUpTo property helps you to return on particular fragment on back press.
– SANAT
Aug 18 '18 at 15:51
@BincyBaby popUpTo property helps you to return on particular fragment on back press.
– SANAT
Aug 18 '18 at 15:51
1
1
@SANAT but how to set up the popUpTo to the fragment imediatelly pressed before? Like, if you were in frag1 went to frag2 and then to frag3, back press should go back to frag2. If you were in frag1 and went directly to frag3, back press sould go back to frag1. The
popUpTo seems to only let you choose one fragment to go back idependently of the user path.– Allan Veloso
Jan 30 at 22:57
@SANAT but how to set up the popUpTo to the fragment imediatelly pressed before? Like, if you were in frag1 went to frag2 and then to frag3, back press should go back to frag2. If you were in frag1 and went directly to frag3, back press sould go back to frag1. The
popUpTo seems to only let you choose one fragment to go back idependently of the user path.– Allan Veloso
Jan 30 at 22:57
add a comment |
You can have a viewpager setup with bottom navigation view. Each fragment in the viewpager will be a container fragment, it will have child fragments with its own backstack. You can maintain backstack for each tab in viewpager this way
I was using that way, but app starting takes too much time to first launch
– Bincy Baby
May 29 '18 at 6:16
Then you must be doing something wrong, make sure you are not doing some heavy work in the oncreate or oncreateview of the fragments. There is no way it would take time
– Suhaib Roomy
May 29 '18 at 6:21
I have to load contents , i don't think youtube or instagram used ViewPager
– Bincy Baby
May 29 '18 at 6:48
Its definitely a viewpager. Just scroll on one page and try switiching tabs, its really fast and it resumes from the same state. There is no way you can achieve it by changing fragments on the same container, these are multiple fragments viewed using a viewpager
– Suhaib Roomy
May 29 '18 at 9:28
My guess is also that YouTube or Instagram do not useViewPager. The restoring happens because of the backStack pop action that resumes the underlying fragment that is added in the first place not replaced
– HawkPriest
Sep 6 '18 at 14:13
add a comment |
You can have a viewpager setup with bottom navigation view. Each fragment in the viewpager will be a container fragment, it will have child fragments with its own backstack. You can maintain backstack for each tab in viewpager this way
I was using that way, but app starting takes too much time to first launch
– Bincy Baby
May 29 '18 at 6:16
Then you must be doing something wrong, make sure you are not doing some heavy work in the oncreate or oncreateview of the fragments. There is no way it would take time
– Suhaib Roomy
May 29 '18 at 6:21
I have to load contents , i don't think youtube or instagram used ViewPager
– Bincy Baby
May 29 '18 at 6:48
Its definitely a viewpager. Just scroll on one page and try switiching tabs, its really fast and it resumes from the same state. There is no way you can achieve it by changing fragments on the same container, these are multiple fragments viewed using a viewpager
– Suhaib Roomy
May 29 '18 at 9:28
My guess is also that YouTube or Instagram do not useViewPager. The restoring happens because of the backStack pop action that resumes the underlying fragment that is added in the first place not replaced
– HawkPriest
Sep 6 '18 at 14:13
add a comment |
You can have a viewpager setup with bottom navigation view. Each fragment in the viewpager will be a container fragment, it will have child fragments with its own backstack. You can maintain backstack for each tab in viewpager this way
You can have a viewpager setup with bottom navigation view. Each fragment in the viewpager will be a container fragment, it will have child fragments with its own backstack. You can maintain backstack for each tab in viewpager this way
answered May 29 '18 at 6:11
Suhaib RoomySuhaib Roomy
1,3511616
1,3511616
I was using that way, but app starting takes too much time to first launch
– Bincy Baby
May 29 '18 at 6:16
Then you must be doing something wrong, make sure you are not doing some heavy work in the oncreate or oncreateview of the fragments. There is no way it would take time
– Suhaib Roomy
May 29 '18 at 6:21
I have to load contents , i don't think youtube or instagram used ViewPager
– Bincy Baby
May 29 '18 at 6:48
Its definitely a viewpager. Just scroll on one page and try switiching tabs, its really fast and it resumes from the same state. There is no way you can achieve it by changing fragments on the same container, these are multiple fragments viewed using a viewpager
– Suhaib Roomy
May 29 '18 at 9:28
My guess is also that YouTube or Instagram do not useViewPager. The restoring happens because of the backStack pop action that resumes the underlying fragment that is added in the first place not replaced
– HawkPriest
Sep 6 '18 at 14:13
add a comment |
I was using that way, but app starting takes too much time to first launch
– Bincy Baby
May 29 '18 at 6:16
Then you must be doing something wrong, make sure you are not doing some heavy work in the oncreate or oncreateview of the fragments. There is no way it would take time
– Suhaib Roomy
May 29 '18 at 6:21
I have to load contents , i don't think youtube or instagram used ViewPager
– Bincy Baby
May 29 '18 at 6:48
Its definitely a viewpager. Just scroll on one page and try switiching tabs, its really fast and it resumes from the same state. There is no way you can achieve it by changing fragments on the same container, these are multiple fragments viewed using a viewpager
– Suhaib Roomy
May 29 '18 at 9:28
My guess is also that YouTube or Instagram do not useViewPager. The restoring happens because of the backStack pop action that resumes the underlying fragment that is added in the first place not replaced
– HawkPriest
Sep 6 '18 at 14:13
I was using that way, but app starting takes too much time to first launch
– Bincy Baby
May 29 '18 at 6:16
I was using that way, but app starting takes too much time to first launch
– Bincy Baby
May 29 '18 at 6:16
Then you must be doing something wrong, make sure you are not doing some heavy work in the oncreate or oncreateview of the fragments. There is no way it would take time
– Suhaib Roomy
May 29 '18 at 6:21
Then you must be doing something wrong, make sure you are not doing some heavy work in the oncreate or oncreateview of the fragments. There is no way it would take time
– Suhaib Roomy
May 29 '18 at 6:21
I have to load contents , i don't think youtube or instagram used ViewPager
– Bincy Baby
May 29 '18 at 6:48
I have to load contents , i don't think youtube or instagram used ViewPager
– Bincy Baby
May 29 '18 at 6:48
Its definitely a viewpager. Just scroll on one page and try switiching tabs, its really fast and it resumes from the same state. There is no way you can achieve it by changing fragments on the same container, these are multiple fragments viewed using a viewpager
– Suhaib Roomy
May 29 '18 at 9:28
Its definitely a viewpager. Just scroll on one page and try switiching tabs, its really fast and it resumes from the same state. There is no way you can achieve it by changing fragments on the same container, these are multiple fragments viewed using a viewpager
– Suhaib Roomy
May 29 '18 at 9:28
My guess is also that YouTube or Instagram do not use
ViewPager. The restoring happens because of the backStack pop action that resumes the underlying fragment that is added in the first place not replaced– HawkPriest
Sep 6 '18 at 14:13
My guess is also that YouTube or Instagram do not use
ViewPager. The restoring happens because of the backStack pop action that resumes the underlying fragment that is added in the first place not replaced– HawkPriest
Sep 6 '18 at 14:13
add a comment |
I have made an app like this (still not published on PlayStore) that has the same navigation, maybe its implementation is different from what Google does in their apps, but the functionality is the same.
the structure involves I have Main Activity that I switch the content of it by showing/hiding fragments using:
public void switchTo(final Fragment fragment, final String tag /*Each fragment should have a different Tag*/) {
// We compare if the current stack is the current fragment we try to show
if (fragment == getSupportFragmentManager().getPrimaryNavigationFragment()) {
return;
}
// We need to hide the current showing fragment (primary fragment)
final Fragment currentShowingFragment = getSupportFragmentManager().getPrimaryNavigationFragment();
final FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (currentShowingFragment != null) {
fragmentTransaction.hide(currentShowingFragment);
}
// We try to find that fragment if it was already added before
final Fragment alreadyAddedFragment = getSupportFragmentManager().findFragmentByTag(tag);
if (alreadyAddedFragment != null) {
// Since its already added before we just set it as primary navigation and show it again
fragmentTransaction.setPrimaryNavigationFragment(alreadyAddedFragment);
fragmentTransaction.show(alreadyAddedFragment);
} else {
// We add the new fragment and then show it
fragmentTransaction.add(containerId, fragment, tag);
fragmentTransaction.show(fragment);
// We set it as the primary navigation to support back stack and back navigation
fragmentTransaction.setPrimaryNavigationFragment(fragment);
}
fragmentTransaction.commit();
}
add a comment |
I have made an app like this (still not published on PlayStore) that has the same navigation, maybe its implementation is different from what Google does in their apps, but the functionality is the same.
the structure involves I have Main Activity that I switch the content of it by showing/hiding fragments using:
public void switchTo(final Fragment fragment, final String tag /*Each fragment should have a different Tag*/) {
// We compare if the current stack is the current fragment we try to show
if (fragment == getSupportFragmentManager().getPrimaryNavigationFragment()) {
return;
}
// We need to hide the current showing fragment (primary fragment)
final Fragment currentShowingFragment = getSupportFragmentManager().getPrimaryNavigationFragment();
final FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (currentShowingFragment != null) {
fragmentTransaction.hide(currentShowingFragment);
}
// We try to find that fragment if it was already added before
final Fragment alreadyAddedFragment = getSupportFragmentManager().findFragmentByTag(tag);
if (alreadyAddedFragment != null) {
// Since its already added before we just set it as primary navigation and show it again
fragmentTransaction.setPrimaryNavigationFragment(alreadyAddedFragment);
fragmentTransaction.show(alreadyAddedFragment);
} else {
// We add the new fragment and then show it
fragmentTransaction.add(containerId, fragment, tag);
fragmentTransaction.show(fragment);
// We set it as the primary navigation to support back stack and back navigation
fragmentTransaction.setPrimaryNavigationFragment(fragment);
}
fragmentTransaction.commit();
}
add a comment |
I have made an app like this (still not published on PlayStore) that has the same navigation, maybe its implementation is different from what Google does in their apps, but the functionality is the same.
the structure involves I have Main Activity that I switch the content of it by showing/hiding fragments using:
public void switchTo(final Fragment fragment, final String tag /*Each fragment should have a different Tag*/) {
// We compare if the current stack is the current fragment we try to show
if (fragment == getSupportFragmentManager().getPrimaryNavigationFragment()) {
return;
}
// We need to hide the current showing fragment (primary fragment)
final Fragment currentShowingFragment = getSupportFragmentManager().getPrimaryNavigationFragment();
final FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (currentShowingFragment != null) {
fragmentTransaction.hide(currentShowingFragment);
}
// We try to find that fragment if it was already added before
final Fragment alreadyAddedFragment = getSupportFragmentManager().findFragmentByTag(tag);
if (alreadyAddedFragment != null) {
// Since its already added before we just set it as primary navigation and show it again
fragmentTransaction.setPrimaryNavigationFragment(alreadyAddedFragment);
fragmentTransaction.show(alreadyAddedFragment);
} else {
// We add the new fragment and then show it
fragmentTransaction.add(containerId, fragment, tag);
fragmentTransaction.show(fragment);
// We set it as the primary navigation to support back stack and back navigation
fragmentTransaction.setPrimaryNavigationFragment(fragment);
}
fragmentTransaction.commit();
}
I have made an app like this (still not published on PlayStore) that has the same navigation, maybe its implementation is different from what Google does in their apps, but the functionality is the same.
the structure involves I have Main Activity that I switch the content of it by showing/hiding fragments using:
public void switchTo(final Fragment fragment, final String tag /*Each fragment should have a different Tag*/) {
// We compare if the current stack is the current fragment we try to show
if (fragment == getSupportFragmentManager().getPrimaryNavigationFragment()) {
return;
}
// We need to hide the current showing fragment (primary fragment)
final Fragment currentShowingFragment = getSupportFragmentManager().getPrimaryNavigationFragment();
final FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (currentShowingFragment != null) {
fragmentTransaction.hide(currentShowingFragment);
}
// We try to find that fragment if it was already added before
final Fragment alreadyAddedFragment = getSupportFragmentManager().findFragmentByTag(tag);
if (alreadyAddedFragment != null) {
// Since its already added before we just set it as primary navigation and show it again
fragmentTransaction.setPrimaryNavigationFragment(alreadyAddedFragment);
fragmentTransaction.show(alreadyAddedFragment);
} else {
// We add the new fragment and then show it
fragmentTransaction.add(containerId, fragment, tag);
fragmentTransaction.show(fragment);
// We set it as the primary navigation to support back stack and back navigation
fragmentTransaction.setPrimaryNavigationFragment(fragment);
}
fragmentTransaction.commit();
}
answered Jun 19 '18 at 12:48
Mohammad ErsanMohammad Ersan
10.3k73866
10.3k73866
add a comment |
add a comment |
If you have a bottomNavigationView with 3 items corresponding to 3 Fragments: FragmentA, FragmentB and FragmentC where FragmentA is the startDestination in your navigation graph, then when you're on FragmentB or FragmentC and you click back, you're going to be redirected to FragmentA, that's the behavior recommended by Google and that's implemented by default.
If however you wish to alter this behavior, you'll need to either use a ViewPager as was suggested by some of the other answers, or manually handle the fragments backStack and back transactions yourself -which in a way would undermine the use of the Navigation component altogether-.
3
But youtube, Instagram, Saavn has different behaviour
– Bincy Baby
Aug 29 '18 at 2:05
True, there's no right or wrong way of doing it, it's just about what google supports by default (and thus recommends) and what your needs are. If these two don't align you need to work around it.
– Husayn Hakeem
Aug 30 '18 at 18:26
But the problem is that if you are using the JetPack Navigation the backStack will be empty. Apparently JetPack is not adding nothing to the back stack when handling BottomNavigation clicks.
– Allan Veloso
Jan 30 at 23:12
add a comment |
If you have a bottomNavigationView with 3 items corresponding to 3 Fragments: FragmentA, FragmentB and FragmentC where FragmentA is the startDestination in your navigation graph, then when you're on FragmentB or FragmentC and you click back, you're going to be redirected to FragmentA, that's the behavior recommended by Google and that's implemented by default.
If however you wish to alter this behavior, you'll need to either use a ViewPager as was suggested by some of the other answers, or manually handle the fragments backStack and back transactions yourself -which in a way would undermine the use of the Navigation component altogether-.
3
But youtube, Instagram, Saavn has different behaviour
– Bincy Baby
Aug 29 '18 at 2:05
True, there's no right or wrong way of doing it, it's just about what google supports by default (and thus recommends) and what your needs are. If these two don't align you need to work around it.
– Husayn Hakeem
Aug 30 '18 at 18:26
But the problem is that if you are using the JetPack Navigation the backStack will be empty. Apparently JetPack is not adding nothing to the back stack when handling BottomNavigation clicks.
– Allan Veloso
Jan 30 at 23:12
add a comment |
If you have a bottomNavigationView with 3 items corresponding to 3 Fragments: FragmentA, FragmentB and FragmentC where FragmentA is the startDestination in your navigation graph, then when you're on FragmentB or FragmentC and you click back, you're going to be redirected to FragmentA, that's the behavior recommended by Google and that's implemented by default.
If however you wish to alter this behavior, you'll need to either use a ViewPager as was suggested by some of the other answers, or manually handle the fragments backStack and back transactions yourself -which in a way would undermine the use of the Navigation component altogether-.
If you have a bottomNavigationView with 3 items corresponding to 3 Fragments: FragmentA, FragmentB and FragmentC where FragmentA is the startDestination in your navigation graph, then when you're on FragmentB or FragmentC and you click back, you're going to be redirected to FragmentA, that's the behavior recommended by Google and that's implemented by default.
If however you wish to alter this behavior, you'll need to either use a ViewPager as was suggested by some of the other answers, or manually handle the fragments backStack and back transactions yourself -which in a way would undermine the use of the Navigation component altogether-.
answered Aug 28 '18 at 18:20
Husayn HakeemHusayn Hakeem
6261414
6261414
3
But youtube, Instagram, Saavn has different behaviour
– Bincy Baby
Aug 29 '18 at 2:05
True, there's no right or wrong way of doing it, it's just about what google supports by default (and thus recommends) and what your needs are. If these two don't align you need to work around it.
– Husayn Hakeem
Aug 30 '18 at 18:26
But the problem is that if you are using the JetPack Navigation the backStack will be empty. Apparently JetPack is not adding nothing to the back stack when handling BottomNavigation clicks.
– Allan Veloso
Jan 30 at 23:12
add a comment |
3
But youtube, Instagram, Saavn has different behaviour
– Bincy Baby
Aug 29 '18 at 2:05
True, there's no right or wrong way of doing it, it's just about what google supports by default (and thus recommends) and what your needs are. If these two don't align you need to work around it.
– Husayn Hakeem
Aug 30 '18 at 18:26
But the problem is that if you are using the JetPack Navigation the backStack will be empty. Apparently JetPack is not adding nothing to the back stack when handling BottomNavigation clicks.
– Allan Veloso
Jan 30 at 23:12
3
3
But youtube, Instagram, Saavn has different behaviour
– Bincy Baby
Aug 29 '18 at 2:05
But youtube, Instagram, Saavn has different behaviour
– Bincy Baby
Aug 29 '18 at 2:05
True, there's no right or wrong way of doing it, it's just about what google supports by default (and thus recommends) and what your needs are. If these two don't align you need to work around it.
– Husayn Hakeem
Aug 30 '18 at 18:26
True, there's no right or wrong way of doing it, it's just about what google supports by default (and thus recommends) and what your needs are. If these two don't align you need to work around it.
– Husayn Hakeem
Aug 30 '18 at 18:26
But the problem is that if you are using the JetPack Navigation the backStack will be empty. Apparently JetPack is not adding nothing to the back stack when handling BottomNavigation clicks.
– Allan Veloso
Jan 30 at 23:12
But the problem is that if you are using the JetPack Navigation the backStack will be empty. Apparently JetPack is not adding nothing to the back stack when handling BottomNavigation clicks.
– Allan Veloso
Jan 30 at 23:12
add a comment |
First, let me clarify how Youtube and Instagram handles fragment navigation.
- When the user is on a detail fragment, back or up pop the stack once, with the state properly restaured. A second click on the already selected bottom bar item pop all the stack to the root, refreshing it
- When the user is on a root fragment, back goes to the last menu selected on the bottom bar, displaying the last detail fragment, with the state properly restaured (JetPack doesn't)
- When the user is on the start destination fragment, back finishes activity
None of the other answers above solve all this problems using the jetpack navigation.
JetPack navigation has no standard way to do this, the way that I found more simple is to dividing the navigation xml graph into one for each bottom navigation item, handling the back stack between the navigation items myself using the activity FragmentManager and use the JetPack NavController to handle the internal navigation between root and detail fragments (its implementation uses the childFragmentManager stack).
Suppose you have in your navigation folder this 3 xmls:
res/navigation/
navigation_feed.xml
navigation_explore.xml
navigation_profile.xml
Have your destinationIds inside the navigation xmls the same of your bottomNavigationBar menu ids. Also, to each xml set the app:startDestination to the fragment that you want as the root of the navigation item.
Create a class BottomNavController.kt:
class BottomNavController(
val context: Context,
@IdRes val containerId: Int,
@IdRes val appStartDestinationId: Int
) {
private val navigationBackStack = BackStack.of(appStartDestinationId)
lateinit var activity: Activity
lateinit var fragmentManager: FragmentManager
private var listener: OnNavigationItemChanged? = null
private var navGraphProvider: NavGraphProvider? = null
interface OnNavigationItemChanged {
fun onItemChanged(itemId: Int)
}
interface NavGraphProvider {
@NavigationRes
fun getNavGraphId(itemId: Int): Int
}
init {
var ctx = context
while (ctx is ContextWrapper) {
if (ctx is Activity) {
activity = ctx
fragmentManager = (activity as FragmentActivity).supportFragmentManager
break
}
ctx = ctx.baseContext
}
}
fun setOnItemNavigationChanged(listener: (itemId: Int) -> Unit) {
this.listener = object : OnNavigationItemChanged {
override fun onItemChanged(itemId: Int) {
listener.invoke(itemId)
}
}
}
fun setNavGraphProvider(provider: NavGraphProvider) {
navGraphProvider = provider
}
fun onNavigationItemReselected(item: MenuItem) {
// If the user press a second time the navigation button, we pop the back stack to the root
activity.findNavController(containerId).popBackStack(item.itemId, false)
}
fun onNavigationItemSelected(itemId: Int = navigationBackStack.last()): Boolean {
// Replace fragment representing a navigation item
val fragment = fragmentManager.findFragmentByTag(itemId.toString())
?: NavHostFragment.create(navGraphProvider?.getNavGraphId(itemId)
?: throw RuntimeException("You need to set up a NavGraphProvider with " +
"BottomNavController#setNavGraphProvider")
)
fragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.nav_default_enter_anim,
R.anim.nav_default_exit_anim,
R.anim.nav_default_pop_enter_anim,
R.anim.nav_default_pop_exit_anim
)
.replace(containerId, fragment, itemId.toString())
.addToBackStack(null)
.commit()
// Add to back stack
navigationBackStack.moveLast(itemId)
listener?.onItemChanged(itemId)
return true
}
fun onBackPressed() {
val childFragmentManager = fragmentManager.findFragmentById(containerId)!!
.childFragmentManager
when {
// We should always try to go back on the child fragment manager stack before going to
// the navigation stack. It's important to use the child fragment manager instead of the
// NavController because if the user change tabs super fast commit of the
// supportFragmentManager may mess up with the NavController child fragment manager back
// stack
childFragmentManager.popBackStackImmediate() -> {
}
// Fragment back stack is empty so try to go back on the navigation stack
navigationBackStack.size > 1 -> {
// Remove last item from back stack
navigationBackStack.removeLast()
// Update the container with new fragment
onNavigationItemSelected()
}
// If the stack has only one and it's not the navigation home we should
// ensure that the application always leave from startDestination
navigationBackStack.last() != appStartDestinationId -> {
navigationBackStack.removeLast()
navigationBackStack.add(0, appStartDestinationId)
onNavigationItemSelected()
}
// Navigation stack is empty, so finish the activity
else -> activity.finish()
}
}
private class BackStack : ArrayList<Int>() {
companion object {
fun of(vararg elements: Int): BackStack {
val b = BackStack()
b.addAll(elements.toTypedArray())
return b
}
}
fun removeLast() = removeAt(size - 1)
fun moveLast(item: Int) {
remove(item)
add(item)
}
}
}
// Convenience extension to set up the navigation
fun BottomNavigationView.setUpNavigation(bottomNavController: BottomNavController, onReselect: ((menuItem: MenuItem) -> Unit)? = null) {
setOnNavigationItemSelectedListener {
bottomNavController.onNavigationItemSelected(it.itemId)
}
setOnNavigationItemReselectedListener {
bottomNavController.onNavigationItemReselected(it)
onReselect?.invoke(it)
}
bottomNavController.setOnItemNavigationChanged { itemId ->
menu.findItem(itemId).isChecked = true
}
}
Do your layout main.xml like this:
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottomNavigationView"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
Use on your activity like this:
class MainActivity : AppCompatActivity(),
BottomNavController.NavGraphProvider {
private val navController by lazy(LazyThreadSafetyMode.NONE) {
Navigation.findNavController(this, R.id.container)
}
private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) {
BottomNavController(this, R.id.container, R.id.navigation_feed)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
bottomNavController.setNavGraphProvider(this)
bottomNavigationView.setUpNavigation(bottomNavController)
if (savedInstanceState == null) bottomNavController
.onNavigationItemSelected()
// do your things...
}
override fun getNavGraphId(itemId: Int) = when (itemId) {
R.id.navigation_feed -> R.navigation.navigation_feed
R.id.navigation_explore -> R.navigation.navigation_explore
R.id.navigation_profile -> R.navigation.navigation_profile
else -> R.navigation.navigation_feed
}
override fun onSupportNavigateUp(): Boolean = navController
.navigateUp()
override fun onBackPressed() = bottomNavController.onBackPressed()
}
this solution looks good but there are some things i noticed :<FrameLayout />should be aNavHostFragment, every graph got it's own home default so doing thisif (savedInstanceState == null) bottomNavController .onNavigationItemSelected()will trigger the fragment two times , it doesn't hold states for the fragments.
– Sim
Mar 4 at 6:46
The idea of the saved instance state exactly avoided the fragment be created two times. I can't check this because I ended up extending theNavController, and creating a custom Navigator exclusively forNavHostFragments, adding it to this NavController (I calledNavHostFragmentNavController). Then I create a graph called navigation_main.xml with the <nav-fragment> elements that represent each a bottom navigation item. The new implementations are bigger but the usage quite simple. The code still has some small bugs that I did not finish yet. I will post it when I fix them.
– Allan Veloso
Mar 4 at 14:52
yes i guess for now extending theNavControlleris a sane solution, till Google release the sample.
– Sim
Mar 5 at 7:18
Shouldn't here a destination be passed as the last parameter instead ofR.id.navigation_feed?private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) { BottomNavController(this, R.id.container, R.id.navigation_feed) }
– WWJD
Mar 5 at 10:19
1
@WWJD, R.id.navigation_feed is a destination. I named the graph id with the same name as it's an initial destination, soR.navigation.navigation_feedhas aR.id.navigation_feeddestination.
– Allan Veloso
Mar 5 at 14:09
add a comment |
First, let me clarify how Youtube and Instagram handles fragment navigation.
- When the user is on a detail fragment, back or up pop the stack once, with the state properly restaured. A second click on the already selected bottom bar item pop all the stack to the root, refreshing it
- When the user is on a root fragment, back goes to the last menu selected on the bottom bar, displaying the last detail fragment, with the state properly restaured (JetPack doesn't)
- When the user is on the start destination fragment, back finishes activity
None of the other answers above solve all this problems using the jetpack navigation.
JetPack navigation has no standard way to do this, the way that I found more simple is to dividing the navigation xml graph into one for each bottom navigation item, handling the back stack between the navigation items myself using the activity FragmentManager and use the JetPack NavController to handle the internal navigation between root and detail fragments (its implementation uses the childFragmentManager stack).
Suppose you have in your navigation folder this 3 xmls:
res/navigation/
navigation_feed.xml
navigation_explore.xml
navigation_profile.xml
Have your destinationIds inside the navigation xmls the same of your bottomNavigationBar menu ids. Also, to each xml set the app:startDestination to the fragment that you want as the root of the navigation item.
Create a class BottomNavController.kt:
class BottomNavController(
val context: Context,
@IdRes val containerId: Int,
@IdRes val appStartDestinationId: Int
) {
private val navigationBackStack = BackStack.of(appStartDestinationId)
lateinit var activity: Activity
lateinit var fragmentManager: FragmentManager
private var listener: OnNavigationItemChanged? = null
private var navGraphProvider: NavGraphProvider? = null
interface OnNavigationItemChanged {
fun onItemChanged(itemId: Int)
}
interface NavGraphProvider {
@NavigationRes
fun getNavGraphId(itemId: Int): Int
}
init {
var ctx = context
while (ctx is ContextWrapper) {
if (ctx is Activity) {
activity = ctx
fragmentManager = (activity as FragmentActivity).supportFragmentManager
break
}
ctx = ctx.baseContext
}
}
fun setOnItemNavigationChanged(listener: (itemId: Int) -> Unit) {
this.listener = object : OnNavigationItemChanged {
override fun onItemChanged(itemId: Int) {
listener.invoke(itemId)
}
}
}
fun setNavGraphProvider(provider: NavGraphProvider) {
navGraphProvider = provider
}
fun onNavigationItemReselected(item: MenuItem) {
// If the user press a second time the navigation button, we pop the back stack to the root
activity.findNavController(containerId).popBackStack(item.itemId, false)
}
fun onNavigationItemSelected(itemId: Int = navigationBackStack.last()): Boolean {
// Replace fragment representing a navigation item
val fragment = fragmentManager.findFragmentByTag(itemId.toString())
?: NavHostFragment.create(navGraphProvider?.getNavGraphId(itemId)
?: throw RuntimeException("You need to set up a NavGraphProvider with " +
"BottomNavController#setNavGraphProvider")
)
fragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.nav_default_enter_anim,
R.anim.nav_default_exit_anim,
R.anim.nav_default_pop_enter_anim,
R.anim.nav_default_pop_exit_anim
)
.replace(containerId, fragment, itemId.toString())
.addToBackStack(null)
.commit()
// Add to back stack
navigationBackStack.moveLast(itemId)
listener?.onItemChanged(itemId)
return true
}
fun onBackPressed() {
val childFragmentManager = fragmentManager.findFragmentById(containerId)!!
.childFragmentManager
when {
// We should always try to go back on the child fragment manager stack before going to
// the navigation stack. It's important to use the child fragment manager instead of the
// NavController because if the user change tabs super fast commit of the
// supportFragmentManager may mess up with the NavController child fragment manager back
// stack
childFragmentManager.popBackStackImmediate() -> {
}
// Fragment back stack is empty so try to go back on the navigation stack
navigationBackStack.size > 1 -> {
// Remove last item from back stack
navigationBackStack.removeLast()
// Update the container with new fragment
onNavigationItemSelected()
}
// If the stack has only one and it's not the navigation home we should
// ensure that the application always leave from startDestination
navigationBackStack.last() != appStartDestinationId -> {
navigationBackStack.removeLast()
navigationBackStack.add(0, appStartDestinationId)
onNavigationItemSelected()
}
// Navigation stack is empty, so finish the activity
else -> activity.finish()
}
}
private class BackStack : ArrayList<Int>() {
companion object {
fun of(vararg elements: Int): BackStack {
val b = BackStack()
b.addAll(elements.toTypedArray())
return b
}
}
fun removeLast() = removeAt(size - 1)
fun moveLast(item: Int) {
remove(item)
add(item)
}
}
}
// Convenience extension to set up the navigation
fun BottomNavigationView.setUpNavigation(bottomNavController: BottomNavController, onReselect: ((menuItem: MenuItem) -> Unit)? = null) {
setOnNavigationItemSelectedListener {
bottomNavController.onNavigationItemSelected(it.itemId)
}
setOnNavigationItemReselectedListener {
bottomNavController.onNavigationItemReselected(it)
onReselect?.invoke(it)
}
bottomNavController.setOnItemNavigationChanged { itemId ->
menu.findItem(itemId).isChecked = true
}
}
Do your layout main.xml like this:
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottomNavigationView"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
Use on your activity like this:
class MainActivity : AppCompatActivity(),
BottomNavController.NavGraphProvider {
private val navController by lazy(LazyThreadSafetyMode.NONE) {
Navigation.findNavController(this, R.id.container)
}
private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) {
BottomNavController(this, R.id.container, R.id.navigation_feed)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
bottomNavController.setNavGraphProvider(this)
bottomNavigationView.setUpNavigation(bottomNavController)
if (savedInstanceState == null) bottomNavController
.onNavigationItemSelected()
// do your things...
}
override fun getNavGraphId(itemId: Int) = when (itemId) {
R.id.navigation_feed -> R.navigation.navigation_feed
R.id.navigation_explore -> R.navigation.navigation_explore
R.id.navigation_profile -> R.navigation.navigation_profile
else -> R.navigation.navigation_feed
}
override fun onSupportNavigateUp(): Boolean = navController
.navigateUp()
override fun onBackPressed() = bottomNavController.onBackPressed()
}
this solution looks good but there are some things i noticed :<FrameLayout />should be aNavHostFragment, every graph got it's own home default so doing thisif (savedInstanceState == null) bottomNavController .onNavigationItemSelected()will trigger the fragment two times , it doesn't hold states for the fragments.
– Sim
Mar 4 at 6:46
The idea of the saved instance state exactly avoided the fragment be created two times. I can't check this because I ended up extending theNavController, and creating a custom Navigator exclusively forNavHostFragments, adding it to this NavController (I calledNavHostFragmentNavController). Then I create a graph called navigation_main.xml with the <nav-fragment> elements that represent each a bottom navigation item. The new implementations are bigger but the usage quite simple. The code still has some small bugs that I did not finish yet. I will post it when I fix them.
– Allan Veloso
Mar 4 at 14:52
yes i guess for now extending theNavControlleris a sane solution, till Google release the sample.
– Sim
Mar 5 at 7:18
Shouldn't here a destination be passed as the last parameter instead ofR.id.navigation_feed?private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) { BottomNavController(this, R.id.container, R.id.navigation_feed) }
– WWJD
Mar 5 at 10:19
1
@WWJD, R.id.navigation_feed is a destination. I named the graph id with the same name as it's an initial destination, soR.navigation.navigation_feedhas aR.id.navigation_feeddestination.
– Allan Veloso
Mar 5 at 14:09
add a comment |
First, let me clarify how Youtube and Instagram handles fragment navigation.
- When the user is on a detail fragment, back or up pop the stack once, with the state properly restaured. A second click on the already selected bottom bar item pop all the stack to the root, refreshing it
- When the user is on a root fragment, back goes to the last menu selected on the bottom bar, displaying the last detail fragment, with the state properly restaured (JetPack doesn't)
- When the user is on the start destination fragment, back finishes activity
None of the other answers above solve all this problems using the jetpack navigation.
JetPack navigation has no standard way to do this, the way that I found more simple is to dividing the navigation xml graph into one for each bottom navigation item, handling the back stack between the navigation items myself using the activity FragmentManager and use the JetPack NavController to handle the internal navigation between root and detail fragments (its implementation uses the childFragmentManager stack).
Suppose you have in your navigation folder this 3 xmls:
res/navigation/
navigation_feed.xml
navigation_explore.xml
navigation_profile.xml
Have your destinationIds inside the navigation xmls the same of your bottomNavigationBar menu ids. Also, to each xml set the app:startDestination to the fragment that you want as the root of the navigation item.
Create a class BottomNavController.kt:
class BottomNavController(
val context: Context,
@IdRes val containerId: Int,
@IdRes val appStartDestinationId: Int
) {
private val navigationBackStack = BackStack.of(appStartDestinationId)
lateinit var activity: Activity
lateinit var fragmentManager: FragmentManager
private var listener: OnNavigationItemChanged? = null
private var navGraphProvider: NavGraphProvider? = null
interface OnNavigationItemChanged {
fun onItemChanged(itemId: Int)
}
interface NavGraphProvider {
@NavigationRes
fun getNavGraphId(itemId: Int): Int
}
init {
var ctx = context
while (ctx is ContextWrapper) {
if (ctx is Activity) {
activity = ctx
fragmentManager = (activity as FragmentActivity).supportFragmentManager
break
}
ctx = ctx.baseContext
}
}
fun setOnItemNavigationChanged(listener: (itemId: Int) -> Unit) {
this.listener = object : OnNavigationItemChanged {
override fun onItemChanged(itemId: Int) {
listener.invoke(itemId)
}
}
}
fun setNavGraphProvider(provider: NavGraphProvider) {
navGraphProvider = provider
}
fun onNavigationItemReselected(item: MenuItem) {
// If the user press a second time the navigation button, we pop the back stack to the root
activity.findNavController(containerId).popBackStack(item.itemId, false)
}
fun onNavigationItemSelected(itemId: Int = navigationBackStack.last()): Boolean {
// Replace fragment representing a navigation item
val fragment = fragmentManager.findFragmentByTag(itemId.toString())
?: NavHostFragment.create(navGraphProvider?.getNavGraphId(itemId)
?: throw RuntimeException("You need to set up a NavGraphProvider with " +
"BottomNavController#setNavGraphProvider")
)
fragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.nav_default_enter_anim,
R.anim.nav_default_exit_anim,
R.anim.nav_default_pop_enter_anim,
R.anim.nav_default_pop_exit_anim
)
.replace(containerId, fragment, itemId.toString())
.addToBackStack(null)
.commit()
// Add to back stack
navigationBackStack.moveLast(itemId)
listener?.onItemChanged(itemId)
return true
}
fun onBackPressed() {
val childFragmentManager = fragmentManager.findFragmentById(containerId)!!
.childFragmentManager
when {
// We should always try to go back on the child fragment manager stack before going to
// the navigation stack. It's important to use the child fragment manager instead of the
// NavController because if the user change tabs super fast commit of the
// supportFragmentManager may mess up with the NavController child fragment manager back
// stack
childFragmentManager.popBackStackImmediate() -> {
}
// Fragment back stack is empty so try to go back on the navigation stack
navigationBackStack.size > 1 -> {
// Remove last item from back stack
navigationBackStack.removeLast()
// Update the container with new fragment
onNavigationItemSelected()
}
// If the stack has only one and it's not the navigation home we should
// ensure that the application always leave from startDestination
navigationBackStack.last() != appStartDestinationId -> {
navigationBackStack.removeLast()
navigationBackStack.add(0, appStartDestinationId)
onNavigationItemSelected()
}
// Navigation stack is empty, so finish the activity
else -> activity.finish()
}
}
private class BackStack : ArrayList<Int>() {
companion object {
fun of(vararg elements: Int): BackStack {
val b = BackStack()
b.addAll(elements.toTypedArray())
return b
}
}
fun removeLast() = removeAt(size - 1)
fun moveLast(item: Int) {
remove(item)
add(item)
}
}
}
// Convenience extension to set up the navigation
fun BottomNavigationView.setUpNavigation(bottomNavController: BottomNavController, onReselect: ((menuItem: MenuItem) -> Unit)? = null) {
setOnNavigationItemSelectedListener {
bottomNavController.onNavigationItemSelected(it.itemId)
}
setOnNavigationItemReselectedListener {
bottomNavController.onNavigationItemReselected(it)
onReselect?.invoke(it)
}
bottomNavController.setOnItemNavigationChanged { itemId ->
menu.findItem(itemId).isChecked = true
}
}
Do your layout main.xml like this:
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottomNavigationView"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
Use on your activity like this:
class MainActivity : AppCompatActivity(),
BottomNavController.NavGraphProvider {
private val navController by lazy(LazyThreadSafetyMode.NONE) {
Navigation.findNavController(this, R.id.container)
}
private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) {
BottomNavController(this, R.id.container, R.id.navigation_feed)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
bottomNavController.setNavGraphProvider(this)
bottomNavigationView.setUpNavigation(bottomNavController)
if (savedInstanceState == null) bottomNavController
.onNavigationItemSelected()
// do your things...
}
override fun getNavGraphId(itemId: Int) = when (itemId) {
R.id.navigation_feed -> R.navigation.navigation_feed
R.id.navigation_explore -> R.navigation.navigation_explore
R.id.navigation_profile -> R.navigation.navigation_profile
else -> R.navigation.navigation_feed
}
override fun onSupportNavigateUp(): Boolean = navController
.navigateUp()
override fun onBackPressed() = bottomNavController.onBackPressed()
}
First, let me clarify how Youtube and Instagram handles fragment navigation.
- When the user is on a detail fragment, back or up pop the stack once, with the state properly restaured. A second click on the already selected bottom bar item pop all the stack to the root, refreshing it
- When the user is on a root fragment, back goes to the last menu selected on the bottom bar, displaying the last detail fragment, with the state properly restaured (JetPack doesn't)
- When the user is on the start destination fragment, back finishes activity
None of the other answers above solve all this problems using the jetpack navigation.
JetPack navigation has no standard way to do this, the way that I found more simple is to dividing the navigation xml graph into one for each bottom navigation item, handling the back stack between the navigation items myself using the activity FragmentManager and use the JetPack NavController to handle the internal navigation between root and detail fragments (its implementation uses the childFragmentManager stack).
Suppose you have in your navigation folder this 3 xmls:
res/navigation/
navigation_feed.xml
navigation_explore.xml
navigation_profile.xml
Have your destinationIds inside the navigation xmls the same of your bottomNavigationBar menu ids. Also, to each xml set the app:startDestination to the fragment that you want as the root of the navigation item.
Create a class BottomNavController.kt:
class BottomNavController(
val context: Context,
@IdRes val containerId: Int,
@IdRes val appStartDestinationId: Int
) {
private val navigationBackStack = BackStack.of(appStartDestinationId)
lateinit var activity: Activity
lateinit var fragmentManager: FragmentManager
private var listener: OnNavigationItemChanged? = null
private var navGraphProvider: NavGraphProvider? = null
interface OnNavigationItemChanged {
fun onItemChanged(itemId: Int)
}
interface NavGraphProvider {
@NavigationRes
fun getNavGraphId(itemId: Int): Int
}
init {
var ctx = context
while (ctx is ContextWrapper) {
if (ctx is Activity) {
activity = ctx
fragmentManager = (activity as FragmentActivity).supportFragmentManager
break
}
ctx = ctx.baseContext
}
}
fun setOnItemNavigationChanged(listener: (itemId: Int) -> Unit) {
this.listener = object : OnNavigationItemChanged {
override fun onItemChanged(itemId: Int) {
listener.invoke(itemId)
}
}
}
fun setNavGraphProvider(provider: NavGraphProvider) {
navGraphProvider = provider
}
fun onNavigationItemReselected(item: MenuItem) {
// If the user press a second time the navigation button, we pop the back stack to the root
activity.findNavController(containerId).popBackStack(item.itemId, false)
}
fun onNavigationItemSelected(itemId: Int = navigationBackStack.last()): Boolean {
// Replace fragment representing a navigation item
val fragment = fragmentManager.findFragmentByTag(itemId.toString())
?: NavHostFragment.create(navGraphProvider?.getNavGraphId(itemId)
?: throw RuntimeException("You need to set up a NavGraphProvider with " +
"BottomNavController#setNavGraphProvider")
)
fragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.nav_default_enter_anim,
R.anim.nav_default_exit_anim,
R.anim.nav_default_pop_enter_anim,
R.anim.nav_default_pop_exit_anim
)
.replace(containerId, fragment, itemId.toString())
.addToBackStack(null)
.commit()
// Add to back stack
navigationBackStack.moveLast(itemId)
listener?.onItemChanged(itemId)
return true
}
fun onBackPressed() {
val childFragmentManager = fragmentManager.findFragmentById(containerId)!!
.childFragmentManager
when {
// We should always try to go back on the child fragment manager stack before going to
// the navigation stack. It's important to use the child fragment manager instead of the
// NavController because if the user change tabs super fast commit of the
// supportFragmentManager may mess up with the NavController child fragment manager back
// stack
childFragmentManager.popBackStackImmediate() -> {
}
// Fragment back stack is empty so try to go back on the navigation stack
navigationBackStack.size > 1 -> {
// Remove last item from back stack
navigationBackStack.removeLast()
// Update the container with new fragment
onNavigationItemSelected()
}
// If the stack has only one and it's not the navigation home we should
// ensure that the application always leave from startDestination
navigationBackStack.last() != appStartDestinationId -> {
navigationBackStack.removeLast()
navigationBackStack.add(0, appStartDestinationId)
onNavigationItemSelected()
}
// Navigation stack is empty, so finish the activity
else -> activity.finish()
}
}
private class BackStack : ArrayList<Int>() {
companion object {
fun of(vararg elements: Int): BackStack {
val b = BackStack()
b.addAll(elements.toTypedArray())
return b
}
}
fun removeLast() = removeAt(size - 1)
fun moveLast(item: Int) {
remove(item)
add(item)
}
}
}
// Convenience extension to set up the navigation
fun BottomNavigationView.setUpNavigation(bottomNavController: BottomNavController, onReselect: ((menuItem: MenuItem) -> Unit)? = null) {
setOnNavigationItemSelectedListener {
bottomNavController.onNavigationItemSelected(it.itemId)
}
setOnNavigationItemReselectedListener {
bottomNavController.onNavigationItemReselected(it)
onReselect?.invoke(it)
}
bottomNavController.setOnItemNavigationChanged { itemId ->
menu.findItem(itemId).isChecked = true
}
}
Do your layout main.xml like this:
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottomNavigationView"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
Use on your activity like this:
class MainActivity : AppCompatActivity(),
BottomNavController.NavGraphProvider {
private val navController by lazy(LazyThreadSafetyMode.NONE) {
Navigation.findNavController(this, R.id.container)
}
private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) {
BottomNavController(this, R.id.container, R.id.navigation_feed)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
bottomNavController.setNavGraphProvider(this)
bottomNavigationView.setUpNavigation(bottomNavController)
if (savedInstanceState == null) bottomNavController
.onNavigationItemSelected()
// do your things...
}
override fun getNavGraphId(itemId: Int) = when (itemId) {
R.id.navigation_feed -> R.navigation.navigation_feed
R.id.navigation_explore -> R.navigation.navigation_explore
R.id.navigation_profile -> R.navigation.navigation_profile
else -> R.navigation.navigation_feed
}
override fun onSupportNavigateUp(): Boolean = navController
.navigateUp()
override fun onBackPressed() = bottomNavController.onBackPressed()
}
edited Feb 15 at 0:17
aksh1618
340214
340214
answered Feb 3 at 17:23
Allan VelosoAllan Veloso
954720
954720
this solution looks good but there are some things i noticed :<FrameLayout />should be aNavHostFragment, every graph got it's own home default so doing thisif (savedInstanceState == null) bottomNavController .onNavigationItemSelected()will trigger the fragment two times , it doesn't hold states for the fragments.
– Sim
Mar 4 at 6:46
The idea of the saved instance state exactly avoided the fragment be created two times. I can't check this because I ended up extending theNavController, and creating a custom Navigator exclusively forNavHostFragments, adding it to this NavController (I calledNavHostFragmentNavController). Then I create a graph called navigation_main.xml with the <nav-fragment> elements that represent each a bottom navigation item. The new implementations are bigger but the usage quite simple. The code still has some small bugs that I did not finish yet. I will post it when I fix them.
– Allan Veloso
Mar 4 at 14:52
yes i guess for now extending theNavControlleris a sane solution, till Google release the sample.
– Sim
Mar 5 at 7:18
Shouldn't here a destination be passed as the last parameter instead ofR.id.navigation_feed?private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) { BottomNavController(this, R.id.container, R.id.navigation_feed) }
– WWJD
Mar 5 at 10:19
1
@WWJD, R.id.navigation_feed is a destination. I named the graph id with the same name as it's an initial destination, soR.navigation.navigation_feedhas aR.id.navigation_feeddestination.
– Allan Veloso
Mar 5 at 14:09
add a comment |
this solution looks good but there are some things i noticed :<FrameLayout />should be aNavHostFragment, every graph got it's own home default so doing thisif (savedInstanceState == null) bottomNavController .onNavigationItemSelected()will trigger the fragment two times , it doesn't hold states for the fragments.
– Sim
Mar 4 at 6:46
The idea of the saved instance state exactly avoided the fragment be created two times. I can't check this because I ended up extending theNavController, and creating a custom Navigator exclusively forNavHostFragments, adding it to this NavController (I calledNavHostFragmentNavController). Then I create a graph called navigation_main.xml with the <nav-fragment> elements that represent each a bottom navigation item. The new implementations are bigger but the usage quite simple. The code still has some small bugs that I did not finish yet. I will post it when I fix them.
– Allan Veloso
Mar 4 at 14:52
yes i guess for now extending theNavControlleris a sane solution, till Google release the sample.
– Sim
Mar 5 at 7:18
Shouldn't here a destination be passed as the last parameter instead ofR.id.navigation_feed?private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) { BottomNavController(this, R.id.container, R.id.navigation_feed) }
– WWJD
Mar 5 at 10:19
1
@WWJD, R.id.navigation_feed is a destination. I named the graph id with the same name as it's an initial destination, soR.navigation.navigation_feedhas aR.id.navigation_feeddestination.
– Allan Veloso
Mar 5 at 14:09
this solution looks good but there are some things i noticed :
<FrameLayout /> should be a NavHostFragment, every graph got it's own home default so doing this if (savedInstanceState == null) bottomNavController .onNavigationItemSelected() will trigger the fragment two times , it doesn't hold states for the fragments.– Sim
Mar 4 at 6:46
this solution looks good but there are some things i noticed :
<FrameLayout /> should be a NavHostFragment, every graph got it's own home default so doing this if (savedInstanceState == null) bottomNavController .onNavigationItemSelected() will trigger the fragment two times , it doesn't hold states for the fragments.– Sim
Mar 4 at 6:46
The idea of the saved instance state exactly avoided the fragment be created two times. I can't check this because I ended up extending the
NavController, and creating a custom Navigator exclusively for NavHostFragments, adding it to this NavController (I called NavHostFragmentNavController). Then I create a graph called navigation_main.xml with the <nav-fragment> elements that represent each a bottom navigation item. The new implementations are bigger but the usage quite simple. The code still has some small bugs that I did not finish yet. I will post it when I fix them.– Allan Veloso
Mar 4 at 14:52
The idea of the saved instance state exactly avoided the fragment be created two times. I can't check this because I ended up extending the
NavController, and creating a custom Navigator exclusively for NavHostFragments, adding it to this NavController (I called NavHostFragmentNavController). Then I create a graph called navigation_main.xml with the <nav-fragment> elements that represent each a bottom navigation item. The new implementations are bigger but the usage quite simple. The code still has some small bugs that I did not finish yet. I will post it when I fix them.– Allan Veloso
Mar 4 at 14:52
yes i guess for now extending the
NavController is a sane solution, till Google release the sample.– Sim
Mar 5 at 7:18
yes i guess for now extending the
NavController is a sane solution, till Google release the sample.– Sim
Mar 5 at 7:18
Shouldn't here a destination be passed as the last parameter instead of
R.id.navigation_feed? private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) { BottomNavController(this, R.id.container, R.id.navigation_feed) }– WWJD
Mar 5 at 10:19
Shouldn't here a destination be passed as the last parameter instead of
R.id.navigation_feed? private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) { BottomNavController(this, R.id.container, R.id.navigation_feed) }– WWJD
Mar 5 at 10:19
1
1
@WWJD, R.id.navigation_feed is a destination. I named the graph id with the same name as it's an initial destination, so
R.navigation.navigation_feed has a R.id.navigation_feed destination.– Allan Veloso
Mar 5 at 14:09
@WWJD, R.id.navigation_feed is a destination. I named the graph id with the same name as it's an initial destination, so
R.navigation.navigation_feed has a R.id.navigation_feed destination.– Allan Veloso
Mar 5 at 14:09
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f50577356%2fandroid-jetpack-navigation-bottomnavigationview-with-youtube-or-instagram-like%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Hi @BincyBaby i need same thing did you get any solutions?
– Ramki Anba
Aug 10 '18 at 4:28
not yet got answer
– Bincy Baby
Aug 12 '18 at 11:28
2
Commenting a bit late but upon some digging I found that the
popBackStackis called from theNavController.navigate()function whenNavOptionsare not null. My guess is that at the moment it is not possible to do it out of the box. A custom implementation of NavController is required that accesses themBackStackthrough reflection or something like that.– HawkPriest
Sep 6 '18 at 14:18
1
If you add a listener to the bottom nav you can override the navigation so that it will pop back stack if the stack already contains the new destination or otherwise perform the normal navigation if it doesn't.
if (!navHost.popBackStack(it.itemId, false)) navHost.navigate(it.itemId)– Marcus Hooper
Oct 18 '18 at 6:25
A workaround for the fragment recreation problem - stackoverflow.com/a/51684125/6024687
– jL4
Dec 6 '18 at 5:55