I want to implement in my app navigation logic like in Youtube app. (BottomNavigationView + Fragment management). I want this, because these fragments are heavy, so I want them to be lazy initialized and then stored in backstack, I feel like YouTube is doing it this way. I have implemented BottomNagivationView but I have problems with Fragment Management.
My code:
bottomNavigationView.setOnTabSelectedListener { position, _ ->
setFragment(OnlinePageFragment.Page.values()[position])
}
where Pages is enum
enum class Page(index: Int, val klass: Class<*>) {
ONE(0, OnePageFragment::class.java),
TWO(1, TwoPageFragment::class.java),
THREE(2, ThreePageFragment::class.java)
}
and here is my setFragment function
fun setFragment(page: OnlinePageFragment.Page) {
var fragment: Fragment? = supportFragmentManager.findFragmentByTag(page.klass.name)
val tag = page.klass.name
if (fragment == null)
fragment = OnlinePageFragment.newInstance(page, null)
val ft = supportFragmentManager.beginTransaction()
with(ft) {
replace(R.id.fragmentContainer, fragment, tag)
addToBackStack(tag)
commit()
}
}
override fun onBackPressed() {
if (supportFragmentManager.backStackEntryCount == 1) finish()
else super.onBackPressed()
}
And it's working, but not as good as YouTube app. YouTube app has some magic behaviour i.e. it keeps only one transaction per each fragment, while my app allows to create "infinite" backstack of transactions. Do you have any ideas how it works in YouTube app ?
Without using view pager you can manage it.
I have implemented Please check this. https://github.com/sandeshsk/BackStackFragmentRedirectsToHome
Please update if there is any issue.
This is a method which assigns fragment
public void addFragment(FragmentManager fragmentManager,
Fragment fragment,
int containerId,boolean isFromHome){
fragmentManager.popBackStack(null,FragmentManager.POP_BACK_STACK_INCLUSIVE);
FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
if(isFromHome){
fragmentTransaction.replace(containerId,fragment);
}else{
fragmentTransaction.add(new HomeFragment(),"Home");
fragmentTransaction.addToBackStack("Home");
}
fragmentTransaction.replace(containerId,fragment).commit();
}
This is your navigation item listener
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
addFragment(getSupportFragmentManager(), new HomeFragment(), R.id.frame, true);
}else{
getSupportFragmentManager().popBackStack();
}
return true;
case R.id.navigation_dashboard:
addFragment(getSupportFragmentManager(),new DashboardFragment(),R.id.frame,false);
return true;
case R.id.navigation_notifications:
addFragment(getSupportFragmentManager(),new NotificationFragment(),R.id.frame,false);
return true;
case R.id.navigation_setting:
addFragment(getSupportFragmentManager(),new SettingFragment(),R.id.frame,false);
return true;
}
return false;
}
};
onBackPressed method
@Override
public void onBackPressed() {
if(getSupportFragmentManager().getBackStackEntryCount()>0){
navigation.setSelectedItemId(R.id.navigation_home);
}else {
super.onBackPressed();
}
}
I guess the best way here is to use ViewPager
to store root fragments. Navigation bar will change pages of ViewPager
.
Root fragments will store content fragments inside their ChildFragmentManagers
and implement logic of navigation between content fragments.
Also you will need to implement logic of delegation backpress events from activity to root fragments, to perform navigation in their backstack.
UPD #1
Make sure, that root fragments are not destroyed inside ViewPager, use setOffscreenPageLimit()
UPD #2
If you don't want to use ViewPager, you can use code from this answer to manage root fragments.
Since san's answer was showing fragments as expected, but has some bugs that call onCreateView on HomeFragment twice each time the fragment is changed. Additionally changing page is instantiating new fragment each time the page is changed.
So I took the code and modified my setPage
and it's working great.
Here is how:
private void setPage(Page page) {
FragmentManager fragmentManager = getSupportFragmentManager();
String tag = page.name();
FragmentTransaction transaction = fragmentManager.beginTransaction();
// detach everything
for (Fragment fragment : fragmentManager.getFragments()) {
transaction.detach(fragment);
}
// Retrieve fragment instance, if it was already created
Fragment fragment = fragmentManager.findFragmentByTag(tag);
if (fragment == null) { // If not, crate new instance and add it
try {
fragment = (Fragment) page.clazz.newInstance();
transaction.add(R.id.frame, fragment, tag);
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
} else { // otherwise just attach it
transaction.attach(fragment);
}
transaction.commit();
}
onBackPressed implementation
@Override
public void onBackPressed() {
if (!getSupportFragmentManager().findFragmentByTag(Page.HOME.name()).isDetached()) {
super.onBackPressed();
} else {
navigation.setSelectedItemId(R.id.navigation_home);
}
}