I am using FragmentTransaction.replace() to swap fragments in and out.
The app starts up first time with no problem.
An IllegalStateException is thrown when rotating the device because of a conflict between the savedInstanceState
and commiting a new fragment transaction.
No AsyncTask is involved.
One StackOverflow question suggests to put the setContentView() call
in onResumeFragments(), but this seems to have no effect. Same with onPostResume().
Another StackOverflow question says to override onConfigurationChanged(). This works in that sense that it the exception doesn't occur
because the Activity is not restarted. However, this prevents fragments that have different portrait
and landscape layouts from switching between these layouts. Calling setContentView() in onConfigurationChanged()
causes a similar error (IllegalArgumentException: Binary XML file line #25: Duplicate id 0x12345678, tag null, or parent id with another fragment)
Using fragmentTransaction.commitAllowingStateLoss() instead of .commit() causes IllegalStateException: Activity has been destroyed.
How do I get this to work?
More exception info:
java.lang.RuntimeException: Unable to start
activity ComponentInfo{myapp/myap.MainActivity}:
android.view.InflateException: Binary XML file line #25: Error
inflating class fragment at
myapp.MainActivity.onResumeFragments(MainActivity.java:450)
Caused by: java.lang.IllegalStateException: Can not perform this action after
onSaveInstanceState at > android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1533)
at
myapp.fragments.FragmentChange.onFragmentChange(FragmentChange.java:128)
at
myapp.MainActivity.onNavigationDrawerItemSelected(MainActivity.java:490)
at
myapp.fragments.NavigationDrawerFragment.selectItem(NavigationDrawerFragment.java:197)
at
myapp.fragments.NavigationDrawerFragment.onCreate(NavigationDrawerFragment.java:78)
at myapp.MainActivity.onResumeFragments(MainActivity.java:450)
The sequence in the code upon rotating the device is:
MainActivity.onPause()
MainActivity.saveInstanceState()
NavigationDrawerFragment.onSaveInstanceState()
MainActivity.onStop()
MainActivity.onDestroy()
MainActivity.onCreate()
super.onCreate(savedInstanceState);
MainActivity.onResumeFragments()
setContentView()
NavigationDrawerFragment.onCreate()
MainActivity.onNavigationDrawerItemSelected()
fragmentTransaction.commit();
MainActivity:
public class MainActivity extends AppCompatActivity implements
NavigationDrawerFragment.NavigationDrawerCallbacks {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
#Override
public void onNavigationDrawerItemSelected(int position) {
...
FragmentChangeEvent fragmentChangeEvent = new FragmentChangeEvent(null);
FragmentChange fragmentChange = FragmentChange.getInstance( getSupportFragmentManager());
fragmentChange.onFragmentChange(fragmentChangeEvent);
...
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
super.onSaveInstanceState(outState);
}
#Override
public void onResumeFragments() {
super.onResumeFragments();
// causes onNavigationDrawerItemSelected() to be called, exception thrown
setContentView(myapp.R.layout.activity_main);
mNavigationDrawerFragment = (NavigationDrawerFragment)
getSupportFragmentManager().findFragmentById(myapp.R.id.navigation_drawer);
mNavigationDrawerFragment.setUp( // Set up the drawer
myapp.R.id.navigation_drawer,
(DrawerLayout) findViewById(myapp.R.id.drawer_layout));
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if current fragment is "Individual" { // pseudocode
setContentView(R.layout.activity_main); // causes IllegalArgumentException
}
}
}
NavigationDrawerFragment
public class NavigationDrawerFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);
if (savedInstanceState != null) {
mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
mFromSavedInstanceState = true;
}
// Select either the default item (0) or the last selected item.
selectItem(mCurrentSelectedPosition);
}
private void selectItem(int position) {
mCurrentSelectedPosition = position;
if (mDrawerListView != null) {
mDrawerListView.setItemChecked(position, true);
}
if (mDrawerLayout != null) {
mDrawerLayout.closeDrawer(mFragmentContainerView);
}
if (mCallbacks != null) {
// calls MainActivity.onNavigationDrawerItemSelected()
mCallbacks.onNavigationDrawerItemSelected(position);
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition);
}
}
FragmentChange
public class FragmentChange implements FragmentChangeListener {
public static FragmentChange getInstance(FragmentManager fragmentManager) {
if (instance == null) {
instance = new FragmentChange(fragmentManager);
}
return instance;
}
// constructor
private FragmentChange(FragmentManager fragmentManager) {
mFragmentManager = fragmentManager;
}
#Override
public void onFragmentChange(FragmentChangeEvent fragmentChangeEvent) {
...
mPosition = fragmentChangeEvent.getPosition();
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
Fragment fragment = EmployeesVerticalFragment.newInstance();
fragmentTransaction.replace(myapp.R.id.container, fragment);
fragmentTransaction.commit(); // IllegalState exception here
...
}
}
A greatly reduced form of the project on github which reproduces the IllegalStateException:
Some comments:I can see you have gone "above and beyond" trying to fix it (and have scanned stackoverflow for many tips).
Your code works the first time (even if you are in landscape b4 starting app).
The rotation fails to inflate the fragment (second time around) (in activity_main), and also reports the "commit after save" error (that's weird,how on Earth are we getting 2 fatal errors ? maybe on another thread, or ""save" is killing the inflator, but how can it carry on?).
NavigationDrawerFragment has a LOT of important code in it (that normally goes in the Activity).
Analysis:
Your MainActivity class extends AppCompatActivity:
public class MainActivity extends AppCompatActivity implements
NavigationDrawerFragment.NavigationDrawerCallbacks {
Normally you extend FragmentActivity:
public class MainActivity extends FragmentActivity implements
NavigationDrawerFragment.NavigationDrawerCallbacks {
This is because you inflate activity_main, which contains a fragment.
setContentView(com.example.replacefragments.R.layout.activity_main);
Inheritance:
Activity <- FragmentActivity <- AppCompatActivity <- ActionBarActivity
'<-' means inheritance here.
One reason why you would need to consider FragmentActivity specifically is if you want to use nested fragments (a fragment holding another fragment), as that was not supported in native fragments until API Level 17.
I think this is what you are doing here (in activity_main):
<fragment android:id="#+id/navigation_drawer
Here is a good template app that should make your life easier:
android-sliding-menu-using-navigation-drawer/
FIXING IT:
Replace the fragment in activity_main.xml with the ListView.
I have discarded NavigationDrawerFragment, and injected some code from the template above, then re-introduced bit-by-bit your code from NavigationDrawerFragment, until it falls over.
It now works with rotation, but not as you or I would like (preserving fragment state, NavigationDrawerFragment some functionality), so I'm still working on it.
protraitlandscape after rotation
Hot tip:
The .commit sounds final does it not ? Sounds as if it should synchronous? Neither are true.
The .commit puts the transaction in a pending queue, to truly execute it you need:
getSupportFragmentManager().executePendingTransactions();
Here's a link to the error log of the posted code on rotation:
link error log
Method 1. Avoid config changes. Add android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|keyboard" in you manifest activity.
<activity
android:name=".ui.accounts.YouActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|keyboard"
android:theme="#style/AppTheme.NoActionBar" />
Method 2.
a. First of all do not miss place code from default places like setContentView() in onConfigurationChanged().
b. Replace fragment with tag or by id in xml
c. Don't replace again if fragment found by id or tag.
d. Make sure you are dismissing any dailog inside fragment onPause.
The FragmentManager is an
Interface for interacting with Fragment objects inside of an Activity.
It strikes me as a particular bad idea to have save it in a static field and reuse and old FragmentManager for a new activity. This will necessarily lead to Activity has been destroyed, when the new activity interact with the manager from the old activity.
In your code, replace
FragmentChange.getInstance(getFragmentManager());
by
new FragmentChange(getFragmentManager());
Related
I was wondering, what is the Fragment lifecycle methods, I should commit FragmentTransaction to avoid famous
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
According to http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html, it gives great tip, on how to avoid such exception, by commit FragmentTransaction
FragmentActivity
onCreate()
onResumeFragments()
onPostResume()
Fragment
???
However, how about Fragment? What is the suitable Fragment lifecycle we should commit our fragment? For instance, under very rare situation, I will get exception from Google Play Console crash report, while trying to commit Fragment in another Fragment's onCreate.
public class BuyPortfolioFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final FragmentManager fm = this.getFragmentManager();
// Check to see if we have retained the worker fragment.
this.statusBarUpdaterFragment = (StatusBarUpdaterFragment)fm.findFragmentByTag(STATUS_BAR_UPDATER_FRAGMENT);
if (this.statusBarUpdaterFragment == null) {
this.statusBarUpdaterFragment = StatusBarUpdaterFragment.newInstance();
this.statusBarUpdaterFragment.setTargetFragment(this, 0);
// java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
fm.beginTransaction().add(statusBarUpdaterFragment, STATUS_BAR_UPDATER_FRAGMENT).commit();
} else {
statusBarUpdaterFragment.setTargetFragment(this, 0);
}
p/s I know I can avoid such exception by using commitAllowingStateLoss. I want to use it as last resource.
Fragment's lifecycle state not always matches Activity's. Fragment's method getFragmentManager() returns the FragmentManager of it's hosting Activity (unless it's a child Fragment, if so this method returns the child fragment manager of a hosting Fragment). You may never know in which state is Fragment's hosting Activity unless you make tracking code. So it's really possible that the transaction eventually may be committed after Activity onSaveInstanceState() was called.
I suggest using getChildFragmentManager() and deal with child fragments from fragments.
Or if your intention was really to control Activity Fragments, make accessors for controlling it's state, like
// Activity method
public void showSomeFragment() {
if (mFragmentTransactionsAllowed) {
// do transaction
}
}
// And track the boolean
#Override
protected void onCreate(Bundle b) {
super.onCreate(b);
// override on onCreate() in case if Activity object is reused and state was true
mFragmentTransactionsAllowed = true;
}
#Override
protected void onStart() {
super.onStart();
// override here so that if activity goes foreground but not yet destroyed
mFragmentTransactionsAllowed = true;
}
#Override
protected void onResume() {
super.onResume();
mFragmentTransactionsAllowed = true;
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mFragmentTransactionsAllowed = false;
}
I have implemented a simple activity with this code:
public class MainActivity extends AppCompatActivity implements Fragment_1.Operations{
Fragment_1 fragment_1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragmentactivity);
fragment_1=(Fragment_1)getSupportFragmentManager().findFragmentByTag("fragment_1");
}
//called on buttonclick, fired from a button existing in R.layout.fragmentactivity
public void createFragment(View view){
if (getSupportFragmentManager().findFragmentByTag("fragment_1")==null){
fragment_1=new Fragment_1();
FragmentTransaction transaction=getSupportFragmentManager().beginTransaction();
transaction.add(R.id.fragmentactt,fragment_1,"fragment_1");
transaction.commit();
getSupportFragmentManager().executePendingTransactions();
}
else{
fragment_1=(Fragment_1)getSupportFragmentManager().findFragmentByTag("fragment_1");
}
//Simply adding item to the listview contained in fragment_1.
fragment_1.add("Project #1");
fragment_1.add("Project #2");
fragment_1.add("Project #3");
}
//callback of interface "Operations"
#Override
public void buttonClicked() {
FragmentTransaction transaction=getSupportFragmentManager().beginTransaction();
if (fragment_1.isAdded()){
transaction.remove(fragment_1);
//transaction.addToBackStack(null);
transaction.commit();
getSupportFragmentManager().executePendingTransactions();
}
}
}
Well, the doubt came from the fact that no "onSaveInstanceState" needed to be implemented, everything got "saved" without any problems.
So, why should i use putfragment and getFragment? Do these methods need to be called in order to avoid that, when OS kills app process, they would be lost? This is the only reason i can imagine to force onSaveInstanceState and onRestoreInstanceState methods.
Any help is appreciated.
Activity and fragment lifecycles are linked so when any callback method such as onResume is called for the activity, it is called for the fragment too.
putFragment and getFragment help the activity to manage its fragment child's lifecycle. The activity also has to save instance state.
In order to be activity independant, a fragment can manage his own instance state.
The MainActivity of my Android 4+ app uses a ViewPager to switch between 5 different Fragments. It is necessary, that both the Activity and the Fragments knows which Fragment is currently selected/displayed. The MainActivity keeps track of this information using the ViewPagers OnPageChangeListener. The MainActivity also keeps references to the Fragments created the FragmentPagerAdapter to be able to notify a Fragment when it is selected.
The problem is, that the MainActivty (of course) looses the references to the Fragments when it is re-created, e.g. when rotating the device. This would not be big problem, if the re-created MainActivity would re-get the information from the FragmentPagerAdapter, but this is NOT the case.
It seems that the ViewPager automatically re-creates the Framgent(s) when super.onCreate(savedInstanceState)is called without asking the FragmentPagerAdapter. This is a problem because this way the MainActivty does not get any information about the created Fragment and cannot save a reference to it.
How can this be solved?
// MainActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
// When the activity is re-creates, e.g. on rotation, the viewPager
// Fragments are created here without knowledge of the MainActivity.
super.onCreate(savedInstanceState);
...
ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
viewPager.setAdapter(new SectionsPagerAdapter(getSupportFragmentManager()));
currentPageIndex = -1;
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
updateCurrentPageIndex(position);
}
});
#Override
protected void onResume () {
super.onResume();
updateCurrentPageIndex(currentPageIndex);
}
private void updateCurrentPageIndex(int index) {
currentPageIndex = position;
if (pageOneFragment != null && position == 0)
pageOneFragment.onSelectedInViewPager();
if (pageTwoFragment != null && position == 0)
pageTwoFragment.onSelectedInViewPager();
...
}
Fragment pageOnFragment;
Fragment pageTwoFragment;
...
private class SectionsPagerAdapter extends FragmentPagerAdapter {
...
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
if (position == 0) {
pageOneFragment = PageOneFragmet.newInstance();
fragment = pageOneFragment;
}
if (position == 1) {
pageTwoFragment= PageTwoFragmet.newInstance();
fragment = pageTwoFragment;
}
...
return fragment;
}
}
}
EDIT: Thanks for the answers, but (I think) saving the instance state does not solve the problem. Of course I am aware of the possibility to save Date to the instance state. Thus getting back the information about the current index is not a big problem. The problem is, that the MainActivity cannot notify the selected Fragment because it has not reference to it. The Fragment is not created in the Adapter but automatically by the ViewPager. Thus the Activity does not get any information about the Fragment and has not possibility to notify it about being the active one...
Create an interface
public interface FragmentListener {
void onFragmentActivated(Fragment f);
}
Implement this interface in your activity. Then in your fragment:
#Override
public void onResume() {
super.onResume();
((FragmentListener)getActivity()).onFragmentActivated(this);
}
Now, when this callback is being called in your activity, you know which fragment is activated
In onSaveInstanceState() you should "put" anything that you want to keep track of. In onCreate() you can "get" those items back out.
private static final String KEY_BUNDLE_CURRENT_PAGE_STATE = "current_page_state";
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_BUNDLE_CURRENT_PAGE_STATE, currentPageIndex);
}
#Override
public void onCreate(Bundle savedInstanceState){
// ...
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_BUNDLE_CURRENT_PAGE_STATE) {
currentPageIndex = savedInstanceState.getInt(KEY_BUNDLE_CURRENT_PAGE_STATE);
}
// ...
}
Try this
private static final String INDEX = "f_index";
private int mCurrentIndex ; // the current index will be updated thru position
// orientation change occurs
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current index state
savedInstanceState.putInt(INDEX, mCurrentIndex);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
...
// this is where we get the data back if we are recreated
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Always call the superclass first
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
mCurrentIndex= savedInstanceState.getInt(INDEX);
} else {
// Probably initialize members with default values for a new instance
}
...
}
A nice diagram of lifecycle here.
EDIT
If the above doesnt work for you I suggest trying this instead.
try using shared preferences
every change of index, save it to sharedpreferences
when orientation change handle runtime changes to get the index
there
hope it helps :)
I've a activity which basically is :
public class FragmentContainer extends FragmentActivityBase implements IRefreshListener {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().getExtras() == null
|| getIntent().getExtras().get("type") == null) {
showProductList();
}
else
{
if (getIntent().getExtras().get("type").equals("customer"))
showCustomerList();
}
#Override
public void showProductList() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
// load the product list
ProductList fragment = new ProductList();
fragmentTransaction.replace(R.id.fragment_container, fragment)
.addToBackStack(null);
fragmentTransaction.commit();
}
.....
}
in the fragment, I use onCreateView to get intent and then I create my view.
If I need to change the fragment, I get the reference to the parent Activity (taken from onAttach) and I call method referenced by the IRefreshListener.
like :
IRefreshListener mCallback;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception.
try {
mCallback = (IRefreshListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement IRefreshListener");
}
}
public void callCustomer() {
mCallback.showCustomerList();
}
It works but whne I change the orientation, even I use setRetainInstance(true) it will be reseted.
I have 2 questions :
Do I use the good pattern to manage my application. The big activity which contains one fragment become bigger with the time
How should I handle orientation change ?
Regards
I do not find this pattern is more perfect or best one, although it is or was a suggestion from Google. Because it could be a worse coding style if fragment knows particular activity or listeners, you might write more and more code, when you wanna to let your fragment know more its "container" or "parents". Will the fragment later be used for other activity which has not been implemented with IRefreshListener etc, you will code much more.
My introduce is using Otto-Bus or Event-Bus. You can just send message from one to one. Every one doesn't have to know each other.
Currently, I would like to retain an expensive data structure, during configuration changes. I choose not to use Bundle to handle it, as the expensive data structure is not parcelable.
Hence, I use a non-UI Fragment (Called it RetainInstanceFragment), with its setRetainInstance(true) to hold the data structure.
public class RetainInstanceFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Creating expensive data structure
expensiveDataStructure = CreateExpensiveDataStructure();
// Tell the framework to try to keep this fragment around
// during a configuration change.
setRetainInstance(true);
}
public ExpensiveDataStructure expensiveDataStructure = null;
}
An UI Fragment (Called it UIFragment) will get the expensive data structure from RetainInstanceFragment. Whenever there is configuration changes on UIFragment, UIFragment will always try to get the "cached" RetainInstanceFragment from FragmentManager, before it decides to create a new RetainInstanceFragment.
Example code is as follow.
public class UIFragment extends SherlockListFragment
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
FragmentManager fm = getFragmentManager();
// Check to see if we have retained the worker fragment.
retainInstanceFragment = (RetainInstanceFragment)fm.findFragmentByTag("data");
// If not retained (or first time running), we need to create it.
if (retainInstanceFragment == null) {
retainInstanceFragment = new RetainInstanceFragment();
fm.beginTransaction().add(watchlistArrayFragment, "data").commit();
} else {
// We can re-use retainInstanceFragment.expensiveDataStructure even
// after configuration change.
}
}
}
However, there's a problem. Whenever I destroy my old UIFragment, and replace it with new UIFragment, I expect old RetainInstanceFragment will be destroyed as well. Here is how I destroy and create new UIFragment
public class MyFragmentActivity extends SlidingFragmentActivity
// Being triggered when there is different menu item in sliding menu being
// selected.
public void selectActiveContent(Country country) {
Fragment fragment = new UIFragment(country);
getSupportFragmentManager().beginTransaction().replace(R.id.content, fragment).commitAllowingStateLoss();
}
But old RetainInstanceFragment is never destroyed.
My guess is, perhaps I forget to perform clean up in UIFragment. Hence, I add the following code
UIFragment
#Override
public void onDetach() {
super.onDetach();
// To differentiate whether this is a configuration changes, or we are
// removing away this fragment?
if (this.isRemoving()) {
FragmentManager fm = getFragmentManager();
fm.beginTransaction().remove(retainInstanceFragment).commit();
}
}
However, it doesn't work all the time. I perform several sliding menu clicks.
1. selectActiveContent() -> Create new UIFragment and new RetainInstanceFragment
2. selectActiveContent() -> Create new UIFragment, but re-use previous RetainInstanceFragment. (Wrong behavior)
3. selectActiveContent() -> Create new UIFragment, and new RetainInstanceFragment.
4. selectActiveContent() -> Create new UIFragment, but re-use previous RetainInstanceFragment. (Wrong behavior)
Any idea how I can properly remove retained instance Fragment?
As suggested by #Luksprog, the following method works. However, it still do not explain why the previous cleanup done through onDetach doesn't work. If anyone can explain why this solution works and previous doesn't, I would be very thankful. :)
UIFragment
#Override
public void onDetach() {
super.onDetach();
}
public void cleanupRetainInstanceFragment() {
FragmentManager fm = getFragmentManager();
fm.beginTransaction().remove(this.retainInstanceFragment).commit();
}
MyFragmentActivity
public class MyFragmentActivity extends SlidingFragmentActivity
// Being triggered when there is different menu item in sliding menu being
// selected.
public void selectActiveContent(Country country) {
// *******************************************
// Solution suggested by #Luksprog. It works!
// But I have no idea why it works and previous doesn't work...
// *******************************************
Fragment oldFragment = getSupportFragmentManager().findFragmentById(R.id.content);
if (oldFragment instanceof UIFragment) {
((UIFragment)oldFragment).cleanupRetainInstanceFragment();
}
Fragment fragment = new UIFragment(country);
getSupportFragmentManager().beginTransaction().replace(R.id.content, fragment).commitAllowingStateLoss();
}
(Edited) Useful comment by #Luksprog
The fragment transactions are not made right away. My assumption was
that doing that transaction in the onDetach() callback will not remove
the retain fragment instance before the UI fragment's replace
transaction finished and so your new UI fragment will still see the
retain fragment instance still available, so it will not create a new
one. Your previous method is not in the spirit of the fragments
framework where fragments are unaware of other fragments and the
activity manages all of them as it knows more about the overall
application state.
I think you can just remove the fragment from fragment transaction.
if (mWorkFragment != null) {
fm.beginTransaction().remove(mWorkFragment).commitAllowingStateLoss();
}