With Android 4.2, the support library got support for nested fragments see here. I\'ve played around with it and found an interesting behaviour / bug regarding back stack an
The real answer to this question is in the Fragment Transaction's function called setPrimaryNavigationFragment.
/**
* Set a currently active fragment in this FragmentManager as the primary navigation fragment.
*
* <p>The primary navigation fragment's
* {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
* to process delegated navigation actions such as {@link FragmentManager#popBackStack()}
* if no ID or transaction name is provided to pop to. Navigation operations outside of the
* fragment system may choose to delegate those actions to the primary navigation fragment
* as returned by {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
*
* <p>The fragment provided must currently be added to the FragmentManager to be set as
* a primary navigation fragment, or previously added as part of this transaction.</p>
*
* @param fragment the fragment to set as the primary navigation fragment
* @return the same FragmentTransaction instance
*/
public abstract FragmentTransaction setPrimaryNavigationFragment(Fragment fragment);
You have to set this function on the initial parent fragment when the activity is adding it. I have a replaceFragment function inside of my activity that looks like this:
public void replaceFragment(int containerId, BaseFragment fragment, boolean addToBackstack) {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setPrimaryNavigationFragment(fragment);
if (addToBackstack) {
fragmentTransaction.addToBackStack(fragment.TAG);
}
fragmentTransaction.replace(containerId, fragment).commit();
}
This gives you the same behavior as if your clicking back from regular Fragment B back to Fragment A, except now it is on the child fragments as well!
I solved this issue by keeping currently opened fragment in property of activity. Then I override method
// INSIDE ACTIVITY override fun onBackPressed() { val fragment = currentFragment if(fragment != null && fragment.childFragmentManager.fragments.any()){ fragment.childFragmentManager.popBackStack() } else { super.onBackPressed() } }
This is how I add child fragment into currently opened fragment from within itself.
// INSIDE FRAGMENT menu_fragment_view.setBackgroundColor(-((Math.random() * 10000 ).toInt() % 30000)) // to see change add_fragment_button.setOnClickListener { val transaction = childFragmentManager.beginTransaction() transaction.add(R.id.menu_fragment_fragment_holder, MenuFragment()) transaction.addToBackStack(null) transaction.commit() }
This is xml layout of parent fragment and added fragment
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/menu_fragment_view"> <Button app:layout_constraintTop_toTopOf="parent" app:layout_constraintLeft_toLeftOf="parent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/add_fragment_button" android:text="Just add fragment"/> <FrameLayout android:id="@+id/menu_fragment_fragment_holder" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintLeft_toLeftOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout>
If you have a DialogFragment
which in turn has nested fragments, the 'workaround' is a bit different. Instead of setting an onKeyListener
to the rootView
, you'll need to do that with the Dialog
. Also you will be setting up a DialogInterface.OnKeyListener
and not the View
one. Of course, remember addToBackStack
!
Btw, having 1 fragment on the backstack for delegating the call back to the activity is my personal use case. Typical scenarios might be for the count to be 0.
Here's what you gotta do within the onCreateDialog
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK){
FragmentManager cfm = getChildFragmentManager();
if(cfm.getBackStackEntryCount()>1){
cfm.popBackStack();
return true;
}
}
return false;
}
});
return dialog;
}
This solution may be better version of @ismailarilik answer:
Nested Fragment version
private boolean onBackPressed(FragmentManager fm) {
if (fm != null) {
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
return true;
}
List<Fragment> fragList = fm.getFragments();
if (fragList != null && fragList.size() > 0) {
for (Fragment frag : fragList) {
if (frag == null) {
continue;
}
if (frag.isVisible()) {
if (onBackPressed(frag.getChildFragmentManager())) {
return true;
}
}
}
}
}
return false;
}
@Override
public void onBackPressed() {
FragmentManager fm = getSupportFragmentManager();
if (onBackPressed(fm)) {
return;
}
super.onBackPressed();
}
This solution may be better, bacause it checks all the levels of nested fragments:
/**
* This method will go check all the back stacks of the added fragments and their nested fragments
* to the the {@code FragmentManager} passed as parameter.
* If there is a fragment and the back stack of this fragment is not empty,
* then emulate 'onBackPressed' behaviour, because in default, it is not working.
*
* @param fm the fragment manager to which we will try to dispatch the back pressed event.
* @return {@code true} if the onBackPressed event was consumed by a child fragment, otherwise {@code false}.
*/
public static boolean dispatchOnBackPressedToFragments(FragmentManager fm) {
List<Fragment> fragments = fm.getFragments();
boolean result;
if (fragments != null && !fragments.isEmpty()) {
for (Fragment frag : fragments) {
if (frag != null && frag.isAdded() && frag.getChildFragmentManager() != null) {
// go to the next level of child fragments.
result = dispatchOnBackPressedToFragments(frag.getChildFragmentManager());
if (result) return true;
}
}
}
// if the back stack is not empty then we pop the last transaction.
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
fm.executePendingTransactions();
return true;
}
return false;
}
in your activity onBackPressed
you can simply call it this way:
FragmentManager fm = getSupportFragmentManager();
// if there is a fragment and the back stack of this fragment is not empty,
// then emulate 'onBackPressed' behaviour, because in default, it is not working
if (!dispatchOnBackPressedToFragments(fm)) {
// if no child fragment consumed the onBackPressed event,
// we execute the default behaviour.
super.onBackPressed();
}
for ChildFragments this works..
@Override
public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack();
} else {
doExit(); //super.onBackPressed();
}
}