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
After observing some solutions presented here, I found that to allow flexibility & control for the parent fragment as to when popping the stack or when the back action should be ignored by it, I rather use such implementation:
Defining a "ParentFragment" interface:
interface ParentFragment {
/**
* Fragments that host child fragments and want to control their BackStack behaviour when the back button is pressed should implement this
*
* @return true if back press was handled, false otherwise
*/
fun onBackPressed(): Boolean
}
Overriding the "onBackPressed" in the parent activity (or in the BaseActivity):
override fun onBackPressed() {
val fm: FragmentManager = supportFragmentManager
for (frag in fm.fragments) {
if (frag.isVisible && frag is ParentFragment && frag.onBackPressed()) {
return
}
}
super.onBackPressed()
}
And then allow the parent fragment to handle as it wishes, for example:
override fun onBackPressed(): Boolean {
val childFragment = childFragmentManager.findFragmentByTag(SomeChildFragment::class.java.simpleName)
if (childFragment != null && childFragment.isVisible) {
// Only for that case, pop the BackStack (perhaps when other child fragments are visible don't)
childFragmentManager.popBackStack()
return true
}
return false
}
This allows to avoid thinking that there is some legitimate child fragment to remove when using a view pager for example (and back stack entry count > 0).
This solution may be better version of @Sean answer:
@Override
public void onBackPressed() {
// 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
FragmentManager fm = getSupportFragmentManager();
for (Fragment frag : fm.getFragments()) {
if (frag.isVisible()) {
FragmentManager childFm = frag.getChildFragmentManager();
if (childFm.getBackStackEntryCount() > 0) {
childFm.popBackStack();
return;
}
}
}
super.onBackPressed();
}
Again, I prepared this solution based on @Sean answer above.
As @AZ13 said, this solution is only feasible in one level child fragments situations. In multiple level fragments case, works become a little complex, so I recommend that try this solution only the feasible case I have said. =)
Note: Since getFragments
method is now a private method, this solution will not work. You can check comments for a link which suggests a solution about this situation.
This code will navigate the tree of fragment managers and return the last one that was added that has any fragments it can pop off the stack:
private FragmentManager getLastFragmentManagerWithBack(FragmentManager fm)
{
FragmentManager fmLast = fm;
List<Fragment> fragments = fm.getFragments();
for (Fragment f : fragments)
{
if ((f.getChildFragmentManager() != null) && (f.getChildFragmentManager().getBackStackEntryCount() > 0))
{
fmLast = f.getFragmentManager();
FragmentManager fmChild = getLastFragmentManagerWithBack(f.getChildFragmentManager());
if (fmChild != fmLast)
fmLast = fmChild;
}
}
return fmLast;
}
Call the method:
@Override
public void onBackPressed()
{
FragmentManager fm = getLastFragmentManagerWithBack(getSupportFragmentManager());
if (fm.getBackStackEntryCount() > 0)
{
fm.popBackStack();
return;
}
super.onBackPressed();
}
With this answer it will handle recursive back checking and give each fragment the chance to override the default behaviour. This means you can have a fragment that hosts a ViewPager do something special like scroll to the page that as a back-stack, or scroll to the home page and then on the next back press exit.
Add this to your Activity that extends AppCompatActivity.
@Override
public void onBackPressed()
{
if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
super.onBackPressed();
}
}
Add this to your BaseFragment or the class you can have all your fragments inherit from.
public static boolean handleBackPressed(FragmentManager fm)
{
if(fm.getFragments() != null){
for(Fragment frag : fm.getFragments()){
if(frag != null && frag.isVisible() && frag instanceof BaseFragment){
if(((BaseFragment)frag).onBackPressed()){
return true;
}
}
}
}
return false;
}
protected boolean onBackPressed()
{
FragmentManager fm = getChildFragmentManager();
if(handleBackPressed(fm)){
return true;
}
else if(getUserVisibleHint() && fm.getBackStackEntryCount() > 0){
fm.popBackStack();
return true;
}
return false;
}
The reason is that your Activity derives from FragmentActivity, which handles the BACK key press (see line 173 of FragmentActivity.
In our application, I'm using a ViewPager (with fragments) and each fragment can have nested fragments. The way I've handled this is by:
void onBackKeyPressed()
Also note, that I'm using getChildFragmentManager()
in the fragments to properly nest fragments. You can see a discussion and an explanation in this android-developers post.