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.
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
.
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
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) {
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With