Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SupportFragmentManager, popbackstack & screen rotation

I have a strange issue that is driving me mad!

Scenario:

I have Activity A as a launcher Activity and Activity B can be started through an Intent in Activity A

Activity A --> Activity B

Activity B has a initial Fragment call it Fragment C it is instantiated in Activity B like so:

if(getSupportFragmentManager().findFragmentByTag(FragmentC.TAG) == null) {
        getSupportFragmentManager()
                 .beginTransaction()
                 .add(R.id.container,
                  FragmentC.newInstance(null), // optional bundle
                  FragmentC.TAG)
                 .commit();
}

In Fragment C I have a RecyclerView with an OnClickListener on the ViewHolder this calls back to the hosting Activity B through Fragment C. At this point I start a FragmentTransaction (in Activity B) to replace the current Fragment C, call it Fragment D, and I add this transaction to the backstack:

getSupportFragmentManager()
          .beginTransaction()
          .setCustomAnimations(
                  R.anim.fragment_slide_enter,
                  R.anim.fragment_slide_exit,
                  R.anim.fragment_slide_enter_pop,
                  R.anim.fragment_slide_exit_pop)
          .replace(R.id.container,
                        FragmentD.newInstance(bundle),
                        FragmentD.TAG)
          .addToBackStack(null)
          .commit();

So at the point I have Activity B with Fragment D with the transaction from Fragment C to Fragment D in the back stack, handling a back press like so :

@Override
public boolean onSupportNavigateUp() {
    onBackPressed();
    return true;
}

@Override
public void onBackPressed() {
    super.onBackPressed();
}

Navigation works correctly going forwards and backwards, from start Actvity A, through to Activity B --> Fragment D and back.

Problem:

However, if I am in Fragment D and rotate the screen, then pop the backstack to go to Fragment C (undo the transaction) I am presented with a blank Fragment. What is more strange is that all of Fragment C's life cycle methods OnCreate(), OnCreateView(), OnActivityCreated() are called, and I have valid data/objects (checked through setting breakpoints).

Curiously however, if I do an add transaction when going from Fragment C --> Fragment D, obviously both are shown at once on top of each other, and I rotate the screen and press back and remove the transaction there is no issue, obviously not a solution, just an observation.

I have searched, yet everything I have read and tried does not work. I have implemented something similar before, which worked fine yet I'm struggling here - any help would be appreciated.

Edit

If the app goies into a pause state (left alone and screen goes off) and then woken up again, resumed, the Fragment appears as it should. I have no initialisation in onResume so I have no idea why this works.

like image 418
Mark Avatar asked Oct 15 '25 13:10

Mark


2 Answers

Problem Solved

Ok, thanks for the suggestions but I have fixed the problem myself.

The Solution? Very simple and I'm kicking myself!

When popping the backstack I never included getId() of the Fragment to "pop" :

The code before was relying on the FragmentActivity to pop the backstack itself in onBackPressed(). Indeed the documentation and method should take care of this:

/**
 * Take care of popping the fragment back stack or finishing the activity
 * as appropriate.
 */
@Override
public void onBackPressed() {
     if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
            super.onBackPressed();
      }
}

However it would seem that this does not work correctly when configuration changes have occurred and you want to pop the backstack to a previous transaction. The code solution was to manually take of "popping" the back stack myself :

Before:

@Override
public boolean onSupportNavigateUp() {
    onBackPressed();
    return true;
}

@Override
public void onBackPressed() {
    super.onBackPressed();
}

After (working solution):

@Override
public boolean onSupportNavigateUp() {
    onBackPressed();
    return true;
}

@Override
public void onBackPressed() {
    if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
            getSupportFragmentManager().popBackStack(getSupportFragmentManager()
                    .getBackStackEntryAt(0).getId(), 
                    FragmentManager.POP_BACK_STACK_INCLUSIVE));
    } else {
            super.onBackPressed();
    }
 }

The documentation for getId() states:

/**
 * Return the unique identifier for the entry.  This is the only
 * representation of the entry that will persist across activity
 * instances.
 */
public int getId();

So basically the only way to successfully "pop" the backstack, after configuration changes have occured (hosting Activity has been destroyed and recreated) is to reference the Fragments unique id.

like image 80
Mark Avatar answered Oct 18 '25 00:10

Mark


Your problem occured due to screen rotation.

According to documentation, Your activity will be destroyed and recreated each time the device configuration changes, like when the user rotates the screen. When the screen changes orientation, the system destroys and recreates the foreground activity because the screen configuration has changed and your activity might need to load alternative resources (such as the layout)

In this case Your Activity B launch intial fragnment C again and all lifecycle methods are execute once again

Solution

  1. You can restrict your activity in Portrait mode.
  2. You should handle onConfigurationChanged() Method in your fragment and defined setRetainInstance(true); in onCreateView()

public void setRetainInstance (boolean retain) Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:

 @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        // Checks the orientation of the screen for landscape and portrait
        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {

        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {

        }
    }
like image 42
Nishchal Sharma Avatar answered Oct 17 '25 23:10

Nishchal Sharma



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!