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 and getChildFragmentManager(). When using getChildFragmentManager() and addToBackStack(String name), by pressing the back button the system does not run down the back stack to the previous fragment. On the other hand, when using getFragmentManager() and addToBackStack(String name), by pressing the back button the system returns to the previous fragment.
For me, this behaviour is unexpected. By pressing the back button on my device, I'm expecting that the last added fragment to the back stack will be popped, even if the fragment was added to the back stack in the children's fragment manager.
Is this behaviour correct? Is this behaviour a bug? Is there a work around for this issue?
sample code with getChildFragmentManager():
public class FragmentceptionActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
final FrameLayout wrapper1 = new FrameLayout(this);
wrapper1.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
wrapper1.setId(1);
final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
params.topMargin = 0;
final TextView text = new TextView(this);
text.setLayoutParams(params);
text.setText("fragment 1");
wrapper1.addView(text);
setContentView(wrapper1);
getSupportFragmentManager().beginTransaction().addToBackStack(null)
.add(1, new Fragment1()).commit();
}
public class Fragment1 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final FrameLayout wrapper2 = new FrameLayout(getActivity());
wrapper2.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
wrapper2.setId(2);
final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
params.topMargin = 100;
final TextView text = new TextView(getActivity());
text.setLayoutParams(params);
text.setText("fragment 2");
wrapper2.addView(text);
return wrapper2;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getFragmentManager().beginTransaction().addToBackStack(null)
.add(2, new Fragment2()).commit();
}
}
public class Fragment2 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final FrameLayout wrapper3 = new FrameLayout(getActivity());
wrapper3.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
wrapper3.setId(3);
final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
params.topMargin = 200;
final TextView text = new TextView(getActivity());
text.setLayoutParams(params);
text.setText("fragment 3");
wrapper3.addView(text);
return wrapper3;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getChildFragmentManager().beginTransaction().addToBackStack(null)
.add(3, new Fragment3()).commit();
}
}
public class Fragment3 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final FrameLayout wrapper4 = new FrameLayout(getActivity());
wrapper4.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
wrapper4.setId(4);
final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
params.topMargin = 300;
final TextView text = new TextView(getActivity());
text.setLayoutParams(params);
text.setText("fragment 4");
wrapper4.addView(text);
return wrapper4;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getChildFragmentManager().beginTransaction().addToBackStack(null)
.add(4, new Fragment4()).commit();
}
}
public class Fragment4 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final FrameLayout wrapper5 = new FrameLayout(getActivity());
wrapper5.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
wrapper5.setId(5);
final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
params.topMargin = 400;
final TextView text = new TextView(getActivity());
text.setLayoutParams(params);
text.setText("fragment 5");
wrapper5.addView(text);
return wrapper5;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
}
}
sample code with getFragmentManager():
public class FragmentceptionActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
final FrameLayout wrapper1 = new FrameLayout(this);
wrapper1.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
wrapper1.setId(1);
final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
params.topMargin = 0;
final TextView text = new TextView(this);
text.setLayoutParams(params);
text.setText("fragment 1");
wrapper1.addView(text);
setContentView(wrapper1);
getSupportFragmentManager().beginTransaction().addToBackStack(null)
.add(1, new Fragment1()).commit();
}
public class Fragment1 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final FrameLayout wrapper2 = new FrameLayout(getActivity());
wrapper2.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
wrapper2.setId(2);
final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
params.topMargin = 100;
final TextView text = new TextView(getActivity());
text.setLayoutParams(params);
text.setText("fragment 2");
wrapper2.addView(text);
return wrapper2;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getFragmentManager().beginTransaction().addToBackStack(null)
.add(2, new Fragment2()).commit();
}
}
public class Fragment2 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final FrameLayout wrapper3 = new FrameLayout(getActivity());
wrapper3.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
wrapper3.setId(3);
final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
params.topMargin = 200;
final TextView text = new TextView(getActivity());
text.setLayoutParams(params);
text.setText("fragment 3");
wrapper3.addView(text);
return wrapper3;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getFragmentManager().beginTransaction().addToBackStack(null)
.add(3, new Fragment3()).commit();
}
}
public class Fragment3 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final FrameLayout wrapper4 = new FrameLayout(getActivity());
wrapper4.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
wrapper4.setId(4);
final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
params.topMargin = 300;
final TextView text = new TextView(getActivity());
text.setLayoutParams(params);
text.setText("fragment 4");
wrapper4.addView(text);
return wrapper4;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getFragmentManager().beginTransaction().addToBackStack(null)
.add(4, new Fragment4()).commit();
}
}
public class Fragment4 extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final FrameLayout wrapper5 = new FrameLayout(getActivity());
wrapper5.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
wrapper5.setId(5);
final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
params.topMargin = 400;
final TextView text = new TextView(getActivity());
text.setLayoutParams(params);
text.setText("fragment 5");
wrapper5.addView(text);
return wrapper5;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
}
}
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.
Seems like a bug. Take a look at: http://code.google.com/p/android/issues/detail?id=40323
For a workaround I've used successfully (as suggested in comments):
@Override
public void onBackPressed() {
// If the fragment exists and has some back-stack entry
if (mActivityDirectFragment != null && mActivityDirectFragment.getChildFragmentManager().getBackStackEntryCount() > 0){
// Get the fragment fragment manager - and pop the backstack
mActivityDirectFragment.getChildFragmentManager().popBackStack();
}
// Else, nothing in the direct fragment back stack
else{
// Let super handle the back press
super.onBackPressed();
}
}
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!
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();
}
SupportFragmentManager
and instead just the Standard FragmentManager
?
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;
}
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();
}
I have implemented it correctly if anybody didnt found any answer till now
just add this method on your child nested fragment
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
// This callback will only be called when MyFragment is at least Started.
OnBackPressedCallback callback = new OnBackPressedCallback(true ) {
@Override
public void handleOnBackPressed() {
// Handle the back button event
FragmentManager fm= getFragmentManager();
if (fm != null) {
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
Log.e( "Frag","back" );
}
List<Fragment> fragList = fm.getFragments();
if (fragList != null && fragList.size() > 0) {
for (Fragment frag : fragList) {
if (frag == null) {
continue;
}
if (frag.isVisible()) {
Log.e( "Frag","Visible" );
}
}
}
}
}
};
requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
super.onCreate( savedInstanceState );
}
requireActivity().getOnBackPressedDispatcher().addCallback(this, callback)
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:
defining an interface OnBackKeyPressedListener with a single method void onBackKeyPressed()
implemented this interface in "top" fragments that ViewPager shows
overriding onKeyDown and detecting BACK press, and calling onBackKeyPressed in a currently active fragment in the view pager.
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.
if you are using fragment in a fragment then use getChildFragmentManager()
getChildFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();
if you are using child fragment and replace it use getParentFragmentManager()
getParentFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();
if both are not working for you try this getActivity().getSupportFragmentManager()
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();
i was able to handle fragment back stack by adding to the parent fragment this method at the onCreate View() method and passing the root view.
private void catchBackEvent(View v){
v.setFocusableInTouchMode(true);
v.requestFocus();
v.setOnKeyListener( new OnKeyListener()
{
@Override
public boolean onKey( View v, int keyCode, KeyEvent event )
{
if( keyCode == KeyEvent.KEYCODE_BACK )
{
if(isEnableFragmentBackStack()){
getChildFragmentManager().popBackStack();
setEnableFragmentBackStack(false);
return true;
}
else
return false;
}
return false;
}
} );
}
The method isEnableFragmentBackStack() is a boolean flag to know when i'm on the main fragment or the next one.
Make sure that when you commit the fragment that needs to be have stack, then you must add the addToBackstack Method.
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();
}
Thanks to everyone for their help, this (tweaked version) works for me:
@Override
public void onBackPressed() {
if (!recursivePopBackStack(getSupportFragmentManager())) {
super.onBackPressed();
}
}
/**
* Recursively look through nested fragments for a backstack entry to pop
* @return: true if a pop was performed
*/
public static boolean recursivePopBackStack(FragmentManager fragmentManager) {
if (fragmentManager.getFragments() != null) {
for (Fragment fragment : fragmentManager.getFragments()) {
if (fragment != null && fragment.isVisible()) {
boolean popped = recursivePopBackStack(fragment.getChildFragmentManager());
if (popped) {
return true;
}
}
}
}
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack();
return true;
}
return false;
}
NOTE: You will probably also want to set the background color of these nested fragments to the app theme's window background color, as by default they are transparent. Somewhat outside of the scope of this question, but it is accomplished by resolving the attribute android.R.attr.windowBackground, and setting the Fragment view's background to that resource ID.
More than 5 years and this issue is still relevant. If you do not want to use the fragmentManager.getFragments() due to its restriction. Extend and use the below classes:
NestedFragmentActivity.java
abstract public class NestedFragmentActivity extends AppCompatActivity {
private final Stack<Integer> mActiveFragmentIdStack = new Stack<>();
private final Stack<String> mActiveFragmentTagStack = new Stack<>();
@Override
public void onBackPressed() {
if (mActiveFragmentIdStack.size() > 0 && mActiveFragmentTagStack.size() > 0) {
// Find by id
int lastFragmentId = mActiveFragmentIdStack.lastElement();
NestedFragment nestedFragment = (NestedFragment) getSupportFragmentManager().findFragmentById(lastFragmentId);
// If cannot find by id, find by tag
if (nestedFragment == null) {
String lastFragmentTag = mActiveFragmentTagStack.lastElement();
nestedFragment = (NestedFragment) getSupportFragmentManager().findFragmentByTag(lastFragmentTag);
}
if (nestedFragment != null) {
nestedFragment.onBackPressed();
}
// If cannot find by tag, then simply pop
mActiveFragmentTagStack.pop();
mActiveFragmentIdStack.pop();
} else {
super.onBackPressed();
}
}
public void addToBackStack(int fragmentId, String fragmentTag) {
mActiveFragmentIdStack.add(fragmentId);
mActiveFragmentTagStack.add(fragmentTag);
}
}
NestedFragment.java
abstract public class NestedFragment extends Fragment {
private final Stack<Integer> mActiveFragmentIdStack = new Stack<>();
private final Stack<String> mActiveFragmentTagStack = new Stack<>();
private NestedFragmentActivity mParentActivity;
private NestedFragment mParentFragment;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (getParentFragment() == null) {
try {
mParentActivity = (NestedFragmentActivity) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " must implement " + NestedFragmentActivity.class.getName());
}
} else {
try {
mParentFragment = (NestedFragment) getParentFragment();
} catch (ClassCastException e) {
throw new ClassCastException(getParentFragment().getClass().toString()
+ " must implement " + NestedFragment.class.getName());
}
}
}
public void onBackPressed() {
if (mActiveFragmentIdStack.size() > 0 && mActiveFragmentTagStack.size() > 0) {
// Find by id
int lastFragmentId = mActiveFragmentIdStack.lastElement();
NestedFragment nestedFragment = (NestedFragment) getChildFragmentManager().findFragmentById(lastFragmentId);
// If cannot find by id, find by tag
if (nestedFragment == null) {
String lastFragmentTag = mActiveFragmentTagStack.lastElement();
nestedFragment = (NestedFragment) getChildFragmentManager().findFragmentByTag(lastFragmentTag);
}
if (nestedFragment != null) {
nestedFragment.onBackPressed();
}
// If cannot find by tag, then simply pop
mActiveFragmentIdStack.pop();
mActiveFragmentTagStack.pop();
} else {
getChildFragmentManager().popBackStack();
}
}
private void addToBackStack(int fragmentId, String fragmentTag) {
mActiveFragmentIdStack.add(fragmentId);
mActiveFragmentTagStack.add(fragmentTag);
}
public void addToParentBackStack() {
if (mParentFragment != null) {
mParentFragment.addToBackStack(getId(), getTag());
} else if (mParentActivity != null) {
mParentActivity.addToBackStack(getId(), getTag());
}
}
}
Explanation:
Each activity and fragment extended from the above classes manages their own back stack for each of their children, and children's children, and so on. The backstack is simply a record of "active fragment" tags/ids. So the caveat is to always provide a tag and/or id for your fragment.
When adding to the backstack in a childFragmentManager, you will need to also call "addToParentBackStack()". This ensures that the fragment's tag/id is added in the parent fragment/activity for later pops.
Example:
getChildFragmentManager().beginTransaction().replace(
R.id.fragment,
fragment,
fragment.getTag()
).addToBackStack(null).commit();
addToParentBackStack();
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
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).
Here is my solution. I have an activity with a child fragment. That child fragment can then recursively create more versions of itself onto its childFragmentManager. So here is my code for my activity's onBackPressed function.
override fun onBackPressed() {
// Get the child fragment manager of the displayed fragment
var childFragmentManager = supportFragmentManager.fragments.first().childFragmentManager
// If there are nested fragments, handle those first
if (childFragmentManager.backStackEntryCount > 0) {
// If there are more nested fragments, go deeper into those
var nestedChildFragmentManager = childFragmentManager.fragments.first().childFragmentManager
while (nestedChildFragmentManager.backStackEntryCount > 0) {
childFragmentManager = nestedChildFragmentManager
nestedChildFragmentManager = childFragmentManager.fragments.first().childFragmentManager
}
// Dismiss the innermost nested fragment
childFragmentManager.popBackStack()
} else if (supportFragmentManager.backStackEntryCount > 0) {
// If there are no nested fragments, dismiss the original fragment
supportFragmentManager.popBackStack()
} else {
// Otherwise, handle the back press as normal
super.onBackPressed()
}
}
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;
}
Dialog
(not DialogFragment
) and the listener you use is DialogInterface.OnKeyListener
- the code is working and complete (and in use). Why don't you provide your situation and perhaps I can help.
for ChildFragments this works..
@Override
public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack();
} else {
doExit(); //super.onBackPressed();
}
}
Success story sharing
getFragments
is now public.