I have a viewpager2 with multiple fragments in FragmentStateAdapter. Whenever I try to open a new fragment and then go back to my current one with viewpager2, I get an exception:
Expected the adapter to be 'fresh' while restoring state.
It seems FragmentStateAdapter is unable to properly restore its state as it is expecting it to be empty.
What could I do to fix this ?
it can be fixed by viewPager2.isSaveEnabled = false
So my problem was that I was creating my FragmentStateAdapter inside my Fragment class field where it was only created once. So when my onCreateView got called a second time I got this issue. If I recreate adapter on every onCreateView call, it seems to work.
I encountered the same problem with ViewPager2. After a lot of efforts on testing different methods this worked for me:
public void onExitOfYourFragment() {
viewPager2.setAdapter(null);
}
When you come back to the fragment again:
public void onResumeOfYourFragment() {
viewPager2.setAdapter(yourAdapter);
}
This adapter/view is useful as a replacement for FragmentStatePagerAdapter.
If what you seek is to preserve the Fragments on re-entrance from the Backstack that would be extremely difficult to achieve with this adapter.
The team placed to many breaks in place to prevent this, only god knows why...
They could have used a self detaching lifeCycle observer, which ability was already foresaw in its code, but nowhere in the android architecture makes use of that ability....
They should have used this unfinished component to listen to the global Fragments lifecycle instead of its viewLifeCycle, from here on, one can scale the listening from the Fragment to the viewLifeCycle. (attach/detach viewLifeCycle observer ON_START/ON_STOP)
Second... even if this is done, the fact that the viewPager itself is built on top of a recyclerView makes it extremely difficult to handle what you would expect from a Fragment's behavior, which is an state of preservation, a one time instantiation, and a well defined lifecycle (controllable/expected destruction).
This adapter is contradictory in its functionality, it checks if the viewPager has already been fed with Fragments, while still requiring a "fresh" adapter on reentrance.
It preserves Fragments on exit to the backStack, while expecting to recreate all of them on reentrance.
The breaks on place to prevent a field instantiated adapter, assuming all other variables are already accounted for a proper viewLifeCycle handling (registering/unregistering & setting and resetting of parameters) are:
#Override
public final void restoreState(#NonNull Parcelable savedState) {
if (!mSavedStates.isEmpty() || !mFragments.isEmpty()) {
throw new IllegalStateException(
"Expected the adapter to be 'fresh' while restoring state.");
}
.....
}
Second break:
#CallSuper
#Override
public void onAttachedToRecyclerView(#NonNull RecyclerView recyclerView) {
checkArgument(mFragmentMaxLifecycleEnforcer == null);
mFragmentMaxLifecycleEnforcer = new FragmentMaxLifecycleEnforcer();
mFragmentMaxLifecycleEnforcer.register(recyclerView);
}
where mFragmentMaxLifecycleEnforcer must be == null on reentrance or it throws an exception in the checkArgument().
Third:
A Fragment garbage collector put in place upon reentrance (to the view, from the backstack) that is postDelayed at 10 seconds that attempts to Destroy off screen Fragments, causing memory leaks on all offscreen pages because it kills their respective FragmentManagers that controls their LifeCycle.
private void scheduleGracePeriodEnd() {
final Handler handler = new Handler(Looper.getMainLooper());
final Runnable runnable = new Runnable() {
#Override
public void run() {
mIsInGracePeriod = false;
gcFragments(); // good opportunity to GC
}
};
mLifecycle.addObserver(new LifecycleEventObserver() {
#Override
public void onStateChanged(#NonNull LifecycleOwner source,
#NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
handler.removeCallbacks(runnable);
source.getLifecycle().removeObserver(this);
}
}
});
handler.postDelayed(runnable, GRACE_WINDOW_TIME_MS);
}
And all of them because of its main culprit: the constructor:
public FragmentStateAdapter(#NonNull FragmentManager fragmentManager,
#NonNull Lifecycle lifecycle) {
mFragmentManager = fragmentManager;
mLifecycle = lifecycle;
super.setHasStableIds(true);
}
I've faced the same issue.
After some researching I've came to that it was related to instance of Adapter. When it is created as a lazy property of Fragment it crashes with that error.
So creating Adapter in Fragment::onViewCreated resolves it.
I was also getting this java.lang.IllegalStateException: Expected the adapter to be 'fresh' while restoring state. when using ViewPager2 within a Fragment.
It seems the problem was because I was executing mViewPager2.setAdapter(mFragmentStateAdapter); in my onCreateView() method.
I fixed it by moving mViewPager2.setAdapter(mMyFragmentStateAdapter); to my onResume() method.
I solved this problem by testing if it is equal null
if(recyclerView.adapter == null) {recyclerView.adapter = myAdapter}
I've been struggling with this and none of the previous answers helped.
This may not work for every possible situation, but in my case fragments containing ViewPager2 were fixed and few, and I solved this by doing fragment switch with FragmentTransaction's show() and hide() methods, instead of replace() commonly recommended for this. Apply show() to the active fragment, and hide() to all others. This avoids operations like re-creating views, and restoring state that trigger the problem.
I got this problem after moving new SDK version. (Expected the adapter to be 'fresh' while restoring state)
android:saveEnabled="false" at ViewPager2 can be a quick fix but it may not be what you want.
<androidx.viewpager2.widget.ViewPager2
android:saveEnabled="false"
Because this simply means your viewPager2 will always come on the first tab when your activity is recreated due to the same reason you are getting this error ( config change and activity recreate).
I wanted users to stay wherever they were So I did not choose this solution.
So I looked in my code a bit. In my case, I found a residual code from early days when I was just learning to create android app.
There was a useless call to onRestoreInstanceState() in MainActivity.onCreate, I just removed that call and it fixed my problem.
In most cases, you should not need to override these methods.
If you want to override these , do not forget to call super.onSaveInstanceState / super.onRestoreInstanceState
Important Note from documentation
The default implementation takes care of most of the UI per-instance
state for you by calling View.onSaveInstanceState() on each view in
the hierarchy that has an id, and by saving the id of the currently
focused view (all of which is restored by the default implementation
of onRestoreInstanceState(Bundle)). If you override this method to
save additional information not captured by each individual view, you
will likely want to call through to the default implementation,
otherwise be prepared to save all of the state of each view yourself.
Check if the information you want to preview is part of a view that may have an ID. Only those with an ID will be preserved automatically.
If you want to Save the attribute of the state which is not being saved already. Then you override these methods and add your bit.
protected void onSaveInstanceState (Bundle outState)
protected void onRestoreInstanceState (Bundle savedInstanceState)
In latest SDK versions Bundle parameter is not null, so onRestoreInstanceState is called only when a savedStateIsAvailable.
However, OnCreate as well gets savedState Parameter. But it can be null first time, so you need to differentiate between first call and calls later on.
Change your fragmentStateAdapter code from
MyPagerAdapter(childFragmentManager: FragmentManager,
var fragments: MutableList<Fragment>,
lifecycle: Lifecycle
) : FragmentStateAdapter(childFragmentManager,lifecycle)
to
MyPagerAdapter(fragment: Fragment,
var fragments: MutableList<Fragment>
) : FragmentStateAdapter(fragment)
Note: Here we are removing lifecycle and fragmentManager dependency and fragment state gets restored on back press.
Related
I have a single Activity application with multiple Fragments that are being switched by using Navigation components. When I switch between two fragments their onCreate() and onDestroy() methods seem to overlap. Thus making it difficult for me to write initialization and clean up code for fragments when they access the same global objects.
Navigating from Framgent_A to Fragment_B has the following order of methods:
Fragment_B.onCreate()
Fragment_A.onDestroy()
In Fragment_A.onDestroy() I reverse the operations I do in Fragment_A.onCreate(). And in Fragment_B I expect things to be in a neutral state when onCreate() is called. However that is not the case since Fragment_A.onDestroy() has not yet been called.
Is the overlap normal on Android or did I configure something wrong in my Navigation components? Is there another way I could achieve what I am trying to do? I know I could couple both Fragments and make it work, but I don't want either Fragment to know about each other. To me it seems weird that Framgnet_A is still alive when Fragment_B is created, when Fragment_B is supposed to replace Fragment_A.
Any help is greatly appreciated!
Edit:
After groing through the source code while debugging I have found out that in FragmentNavigator.navigate() FragmentTransaction.setReorderingAllowed() is called, which allows reordering of operations, even allowing onCreate() of a new fragment to be called before onDestroy() of the previous. The question still remains, how can I solve my problem of correctly cleaning up global state in one Fragment before initializing the same global state in the next Fragment.
The Android Fragment life-cycle is not really an appropriate callback host for your needs. The navigation controller will replace the two fragments with animation, so both are somehow visible the same time and eventually even onPause() of the exiting fragment is called after onResume() of the entering one.
Solution 1: Use OnDestinationChangedListener
The onDestinationChanged() callback is called before any of the life-cycle events. As a very simplified approach (look out for leaks) you could do the following:
findNavController().addOnDestinationChangedListener { _, destination, _ ->
if(shouldCleanupFor(destination)) cleanup()
}
Solution 2: Abstract the global changes away
Instead of having single navigation points change the global state, have a single point of truth for it. This could be another fragment independent of the navigation hierarchy. This then observes the navigation as before:
findNavController(R.id.nav_graph).addOnDestinationChangedListener { _, destination, _ ->
resetAll()
when(distination.id) {
R.id.fragment_a -> prepareForA()
R.id.fragment_b -> prepareForB()
else -> prepareDefault()
}
}
As an additional advantage you could implement the state changes idempotently as well.
Since you have an activity that controls the inflation of your Fragments you can manually control the lifecycles of the fragment that are being inflated. By calling into below methods you can control which fragment is ready to use global data. You will at this point have to, some how pass data back to Mainactivity to establish which fragment is active since your asking about how to inflate 2 fragment simultaneously which will share an object. Better approach would be to have the MainActivity implement FragmentA and FragmentB-detail with specific classes to do Stuff this way you have to treat your app like Tablet and determine 2 pane mode and which point you can use appropriate classes out of those fragments controlled by your Activity. The included link matches what your trying to accomplish
private void addCenterFragments(Fragment fragment) {
try {
removeActiveCenterFragments();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.content_fragment, fragment);
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
activeCenterFragments.add(fragment);
fragmentTransaction.commit();
}catch (Exception e){
Crashlytics.logException(e);
}
}
private void removeActiveCenterFragments() {
if (activeCenterFragments.size() > 0) {
fragmentTransaction = fragmentManager.beginTransaction();
for (Fragment activeFragment : activeCenterFragments) {
fragmentTransaction.remove(activeFragment);
}
activeCenterFragments.clear();
fragmentTransaction.commit();
}
}
Perhaps you could move some the code related to initialization where you assume a neutral state to that fragments onStart() or onCreateView() method. According to the developer documentation this is where initialization should take place.
Another option available is using an Observer /Observable pattern, where you could notify your Activity once onDestroy() in Fragment A is completed. The Activity would then notify Fragment B that it is safe to assume a cleaned up state and begin initialization.
My case was a little bit different, and I would like to share it in case anyone faced the same issue.
I wanted to do an action in onPause() of the current fragment, but not execute that code when one navigates from a fragment to another. What I had to do was to call isRemoving() method to check if the current fragment is being removed or not. It is set to true when NavController.navigate(...) method is called.
override fun onPause() {
super.onPause()
if (!isRemoving()) {
// Write your code here
}
}
Per Google's Fragment.isRemoving() documentation:
Return true if this fragment is currently being removed from its activity. This is not whether its activity is finishing, but rather whether it is in the process of being removed from its activity.
I have a Live Android application, and from market i have received following stack trace and i have no idea why its happening as its not happening in application code but its getting caused by some or the other event from the application (assumption)
I am not using Fragments, still there is a reference of FragmentManager.
If any body can throw some light on some hidden facts to avoid this type of issue:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyDown(Activity.java:1962)
at android.view.KeyEvent.dispatch(KeyEvent.java:2482)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1720)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1258)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2851)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2824)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2011)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4025)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
at dalvik.system.NativeStart.main(Native Method)
This is the most stupid bug I have encountered so far. I had a Fragment application working perfectly for API < 11, and Force Closing on API > 11.
I really couldn't figure out what they changed inside the Activity lifecycle in the call to saveInstance, but I here is how I solved this :
#Override
protected void onSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}
I just do not make the call to .super() and everything works great. I hope this will save you some time.
EDIT: after some more research, this is a known bug in the support package.
If you need to save the instance, and add something to your outState Bundle you can use the following :
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
super.onSaveInstanceState(outState);
}
EDIT2: this may also occur if you are trying to perform a transaction after your Activity is gone in background. To avoid this you should use commitAllowingStateLoss()
EDIT3: The above solutions were fixing issues in the early support.v4 libraries from what I can remember. But if you still have issues with this you MUST also read #AlexLockwood 's blog : Fragment Transactions & Activity State Loss
Summary from the blog post (but I strongly recommend you to read it) :
NEVER commit() transactions after onPause() on pre-Honeycomb, and onStop() on post-Honeycomb
Be careful when committing transactions inside Activity lifecycle methods. Use onCreate(), onResumeFragments() and onPostResume()
Avoid performing transactions inside asynchronous callback methods
Use commitAllowingStateLoss() only as a last resort
Looking in Android source code on what causes this issue gives that flag mStateSaved in FragmentManagerImpl class (instance available in Activity) has value true. It is set to true when the back stack is saved (saveAllState) on call from Activity#onSaveInstanceState.
Afterwards the calls from ActivityThread don't reset this flag using available reset methods from FragmentManagerImpl#noteStateNotSaved() and dispatch().
The way I see it there are some available fixes, depending on what your app is doing and using:
Good ways
Before anything else: I would advertise Alex Lockwood article. Then, from what I've done so far:
For fragments and activities that don't need to keep any state information, call commitAllowStateLoss. Taken from documentation:
Allows the commit to be executed after an activity's state is saved. This is dangerous because the commit can be lost if the activity needs to later be restored from its state, so this should only be used for cases where it is okay for the UI state to change unexpectedly on the user`. I guess this is alright to use if the fragment is showing read-only information. Or even if they do show editable info, use the callbacks methods to retain the edited info.
Just after the transaction is commit (you just called commit()), make a call to FragmentManager.executePendingTransactions().
Not recommended ways:
As Ovidiu Latcu mentioned above, don't call super.onSaveInstanceState(). But this means you will lose the whole state of your activity along with fragments state.
Override onBackPressed and in there call only finish(). This should be OK if you application doesn't use Fragments API; as in super.onBackPressed there is a call to FragmentManager#popBackStackImmediate().
If you are using both Fragments API and the state of your activity is important/vital, then you could try to call using reflection API FragmentManagerImpl#noteStateNotSaved(). But this is a hack, or one could say it's a workaround. I don't like it, but in my case it's quite acceptable since I have a code from a legacy app that uses deprecated code (TabActivity and implicitly LocalActivityManager).
Below is the code that uses reflection:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
invokeFragmentManagerNoteStateNotSaved();
}
#SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeFragmentManagerNoteStateNotSaved() {
/**
* For post-Honeycomb devices
*/
if (Build.VERSION.SDK_INT < 11) {
return;
}
try {
Class cls = getClass();
do {
cls = cls.getSuperclass();
} while (!"Activity".equals(cls.getSimpleName()));
Field fragmentMgrField = cls.getDeclaredField("mFragments");
fragmentMgrField.setAccessible(true);
Object fragmentMgr = fragmentMgrField.get(this);
cls = fragmentMgr.getClass();
Method noteStateNotSavedMethod = cls.getDeclaredMethod("noteStateNotSaved", new Class[] {});
noteStateNotSavedMethod.invoke(fragmentMgr, new Object[] {});
Log.d("DLOutState", "Successful call for noteStateNotSaved!!!");
} catch (Exception ex) {
Log.e("DLOutState", "Exception on worka FM.noteStateNotSaved", ex);
}
}
Cheers!
Such an exception will occur if you try to perform a fragment transition after your fragment activity's onSaveInstanceState() gets called.
One reason this can happen, is if you leave an AsyncTask (or Thread) running when an activity gets stopped.
Any transitions after onSaveInstanceState() is called could potentially get lost if the system reclaims the activity for resources and recreates it later.
Simply call super.onPostResume() before showing your fragment or move your code in onPostResume() method after calling super.onPostResume(). This solve the problem!
This can also happen when calling dismiss() on a dialog fragment after the screen has been locked\blanked and the Activity + dialog's instance state has been saved. To get around this call:
dismissAllowingStateLoss()
Literally every single time I'm dismissing a dialog i don't care about it's state anymore anyway, so this is ok to do - you're not actually losing any state.
Short And working Solution :
Follow Simple Steps :
Step 1 : Override onSaveInstanceState state in respective fragment. And remove super method from it.
#Override
public void onSaveInstanceState(Bundle outState) {
};
Step 2 : Use CommitAllowingStateLoss(); instead of commit(); while fragment operations.
fragmentTransaction.commitAllowingStateLoss();
I think Lifecycle state can help to prevent such crash starting from Android support lib v26.1.0 you can have the following check:
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)){
// Do fragment's transaction commit
}
or you can try:
Fragment.isStateSaved()
more information here
https://developer.android.com/reference/android/support/v4/app/Fragment.html#isStateSaved()
this worked for me... found this out on my own... hope it helps you!
1) do NOT have a global "static" FragmentManager / FragmentTransaction.
2) onCreate, ALWAYS initialize the FragmentManager again!
sample below :-
public abstract class FragmentController extends AnotherActivity{
protected FragmentManager fragmentManager;
protected FragmentTransaction fragmentTransaction;
protected Bundle mSavedInstanceState;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSavedInstanceState = savedInstanceState;
setDefaultFragments();
}
protected void setDefaultFragments() {
fragmentManager = getSupportFragmentManager();
//check if on orientation change.. do not re-add fragments!
if(mSavedInstanceState == null) {
//instantiate the fragment manager
fragmentTransaction = fragmentManager.beginTransaction();
//the navigation fragments
NavigationFragment navFrag = new NavigationFragment();
ToolbarFragment toolFrag = new ToolbarFragment();
fragmentTransaction.add(R.id.NavLayout, navFrag, "NavFrag");
fragmentTransaction.add(R.id.ToolbarLayout, toolFrag, "ToolFrag");
fragmentTransaction.commitAllowingStateLoss();
//add own fragment to the nav (abstract method)
setOwnFragment();
}
}
I was always getting this when I tried to show fragment in onActivityForResult() method, so the problem was next:
My Activity is paused and stopped, which means, that onSaveInstanceState() was called already (for both pre-Honeycomb and post-Honeycomb devices).
In case of any result I made transaction to show/hide fragment, which causes this IllegalStateException.
What I made is next:
Added value for determining if action I want was done (e.g. taking photo from camere - isPhotoTaken) - it can be boolean or integer value depending how much different transactions you need.
In overriden onResumeFragments() method I checked for my value and after made fragment transactions I needed. In this case commit() was not done after onSaveInstanceState, as state was returned in onResumeFragments() method.
I solved the issue with onconfigurationchanged. The trick is that according to android activity life cycle, when you explicitly called an intent(camera intent, or any other one); the activity is paused and onsavedInstance is called in that case. When rotating the device to a different position other than the one during which the activity was active; doing fragment operations such as fragment commit causes Illegal state exception. There are lots of complains about it. It's something about android activity lifecycle management and proper method calls.
To solve it I did this:
1-Override the onsavedInstance method of your activity, and determine the current screen orientation(portrait or landscape) then set your screen orientation to it before your activity is paused. that way the activity you lock the screen rotation for your activity in case it has been rotated by another one.
2-then , override onresume method of activity, and set your orientation mode now to sensor so that after onsaved method is called it will call one more time onconfiguration to deal with the rotation properly.
You can copy/paste this code into your activity to deal with it:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Toast.makeText(this, "Activity OnResume(): Lock Screen Orientation ", Toast.LENGTH_LONG).show();
int orientation =this.getDisplayOrientation();
//Lock the screen orientation to the current display orientation : Landscape or Potrait
this.setRequestedOrientation(orientation);
}
//A method found in stackOverflow, don't remember the author, to determine the right screen orientation independently of the phone or tablet device
public int getDisplayOrientation() {
Display getOrient = getWindowManager().getDefaultDisplay();
int orientation = getOrient.getOrientation();
// Sometimes you may get undefined orientation Value is 0
// simple logic solves the problem compare the screen
// X,Y Co-ordinates and determine the Orientation in such cases
if (orientation == Configuration.ORIENTATION_UNDEFINED) {
Configuration config = getResources().getConfiguration();
orientation = config.orientation;
if (orientation == Configuration.ORIENTATION_UNDEFINED) {
// if height and widht of screen are equal then
// it is square orientation
if (getOrient.getWidth() == getOrient.getHeight()) {
orientation = Configuration.ORIENTATION_SQUARE;
} else { //if widht is less than height than it is portrait
if (getOrient.getWidth() < getOrient.getHeight()) {
orientation = Configuration.ORIENTATION_PORTRAIT;
} else { // if it is not any of the above it will defineitly be landscape
orientation = Configuration.ORIENTATION_LANDSCAPE;
}
}
}
}
return orientation; // return value 1 is portrait and 2 is Landscape Mode
}
#Override
public void onResume() {
super.onResume();
Toast.makeText(this, "Activity OnResume(): Unlock Screen Orientation ", Toast.LENGTH_LONG).show();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
I had the same problem, getting IllegalStateException, but replacing all my calls to commit() with commitAllowingStateLoss() did not help.
The culprit was a call to DialogFragment.show().
I surround it with
try {
dialog.show(transaction, "blah blah");
}
catch(IllegalStateException e) {
return;
}
and that did it. OK, I don't get to show the dialog, but in this case that was fine.
It was the only place in my app where I first called FragmentManager.beginTransaction() but never called commit() so I did not find it when I looked for "commit()".
The funny thing is, the user never leaves the app. Instead the killer was an AdMob interstitial ad showing up.
My solution for that problem was
In fragment add methods:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
guideMapFragment = (SupportMapFragment)a.getSupportFragmentManager().findFragmentById(R.id.guideMap);
guideMap = guideMapFragment.getMap();
...
}
#Override
public void onDestroyView() {
SherlockFragmentActivity a = getSherlockActivity();
if (a != null && guideMapFragment != null) {
try {
Log.i(LOGTAG, "Removing map fragment");
a.getSupportFragmentManager().beginTransaction().remove(guideMapFragment).commit();
guideMapFragment = null;
} catch(IllegalStateException e) {
Log.i(LOGTAG, "IllegalStateException on exit");
}
}
super.onDestroyView();
}
May be bad, but couldn't find anything better.
I got this issue.But I think this problem is not related to commit and commitAllowStateLoss.
The following stack trace and exception message is about commit().
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
But this exception was caused by onBackPressed()
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(Unknown Source)
at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(Unknown Source)
at android.support.v4.app.FragmentActivity.onBackPressed(Unknown Source)
They were all caused by checkStateLoss()
private void checkStateLoss() {
if (mStateSaved) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
if (mNoTransactionsBecause != null) {
throw new IllegalStateException(
"Can not perform this action inside of " + mNoTransactionsBecause);
}
mStateSaved will be true after onSaveInstanceState.
This problem rarely happens.I have never encountered this problem.I can not reoccurrence the problem.
I found issue 25517
It might have occurred in the following circumstances
Back key is called after onSaveInstanceState, but before the new activity is started.
use onStop() in code
I'm not sure what the root of the problem is.
So I used an ugly way.
#Override
public void onBackPressed() {
try{
super.onBackPressed();
}catch (IllegalStateException e){
// can output some information here
finish();
}
}
I have got the same issue in my App. I have been solved this issue just calling the super.onBackPressed(); on previous class and calling the commitAllowingStateLoss() on the current class with that fragment.
onSaveInstance will be called if a user rotates the screen so that it can load resources associated with the new orientation.
It's possible that this user rotated the screen followed by pressing the back button (because it's also possible that this user fumbled their phone while using your app)
Another lifecycle way to solve the issue is to use the latest released lifecycle-ktx with kotlin.
lifecycleScope.launchWhenResumed {
// your code with fragment or dialogfragment
}
The closure will be run after resume state, so even this method is called after
stop, it'll be safly excuted when the next resume come.
You can also choose like
lifecycleScope.launchWhenCreated
// or
lifecycleScope.launchWhenStarted
to fit your situation.
The code will be cancelled when destroy is come.
The Google document link:
https://developer.android.com/kotlin/ktx#lifecycle
Read
http://chris-alexander.co.uk/on-engineering/dev/android-fragments-within-fragments/
article.
fragment.isResumed() checking helps me in onDestroyView w/o using onSaveInstanceState method.
Same issue from me and after a day long analysis of all articles, blog and stackoverflow i've found a simple solution. Don't use savedInstanceState at all, this is the condition with one line of code. On the fragment code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
.....
This happens whenever you are trying to load a fragment but the activity has changed its state to onPause().This happens for example when you try to fetch data and load it to the activity but by the time the user has clicked some button and has moved to next activity.
You can solve this in two ways
You can use transaction.commitAllowingStateLoss() instead of transaction.commit() to load fragment but you may end up losing commit operation that is done.
or
Make sure that activity is in resume and not going to pause state when loading a fragment.
Create a boolean and check if activity is not going to onPause() state.
#Override
public void onResume() {
super.onResume();
mIsResumed = true;
}
#Override
public void onPause() {
mIsResumed = false;
super.onPause();
}
then while loading fragment check if activity is present and load only when activity is foreground.
if(mIsResumed){
//load the fragment
}
Thanks #gunar, but I think there is a better way.
According to doc :
* If you are committing a single transaction that does not modify the
* fragment back stack, strongly consider using
* {#link FragmentTransaction#commitNow()} instead. This can help avoid
* unwanted side effects when other code in your app has pending committed
* transactions that expect different timing.
*
* #return Returns true if there were any pending transactions to be
* executed.
*/
public abstract boolean executePendingTransactions();
So use commitNow to replace:
fragmentTransaction.commit();
FragmentManager.executePendingTransactions()
Well, after trying all the above solutions without success (because basically i dont have transactions).
On my case i was using AlertDialogs and ProgressDialog as fragments that, sometimes, on rotation, when asking for the FragmentManager, the error rises.
I found a workaround mixing some many similar posts:
Its a 3 step solution, all done on your FragmentActivity (in this case, its called GenericActivity):
private static WeakReference<GenericActivity> activity = null; //To avoid bug for fragments: Step 1 of 3
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
//To avoid bug for fragments: Step 2 of 3
activity = new WeakReference<GenericActivity>(this);
}
#Override
public FragmentManager getSupportFragmentManager(){
//To avoid bug for fragments: Step 3 of 3
if (this == activity.get()) {
return super.getSupportFragmentManager();
}
return activity.get().getSupportFragmentManager();
}
When i use startactivity in one fragment, i will get this exception;
When i change to use startactivityforresult, the exception is gone :)
So the easy way to fix it is use the startActivityForResult api :)
I was getting this exception when I was pressing back button to cancel intent chooser on my map fragment activity.
I resolved this by replacing the code of onResume()(where I was initializing the fragment and committing transaction) to onStart() and the app is working fine now.
Hope it helps.
This is fixed in Android 4.2 and also in the support library's source.[*]
For details of the cause (and work-arounds) refer to the the Google bug report:
http://code.google.com/p/android/issues/detail?id=19917
If you're using the support library then you shouldn't have to worry about this bug (for long)[*]. However, if you're using the API directly (i.e. Not using the support library's FragmentManager) and targeting an API below Android 4.2 then you will need to try one of the work-arounds.
[*] At the time of writing the Android SDK Manager is still distributing an old version that exhibits this bug.
Edit I'm going to add some clarification here because I've obviously somehow confused whoever down-voted this answer.
There are several different (but related) circumstances that can cause this exception to be thrown. My answer above is referring to the specific instance discussed in the question i.e. a bug in Android which has subsequently been fixed. If you're getting this exception for another reason it's because you're adding/removing fragments when you shouldn't be (after fragment states have been saved). If you're in such a situation then perhaps "Nested Fragments - IllegalStateException “Can not perform this action after onSaveInstanceState”" can be of use to you.
After researching a bit the solution to this problem is to do your fragment commits in the onresume.
Source: https://wenchaojames.wordpress.com/2013/01/12/illegalstateexception-from-onactivityresult/
My use case: I have used listener in fragment to notify activity that some thing happened. I did new fragment commit on callback method. This works perfectly fine on first time. But on orientation change the activity is recreated with saved instance state. In that case fragment is not created again implies that the fragment have the listener which is old destroyed activity. Any way the call back method will get triggered on action. It goes to destroyed activity which cause the issue. The solution is to reset the listener in fragment with current live activity. This solve the problem.
What I found is that if another app is dialog type and allows touches to be sent to background app then almost any background app will crash with this error.
I think we need to check every time a transaction is performed if the instance was saved or restored.
In my case, with the same error exception, i put the "onBackPressed()" in a runnable (you can use any of your view):
myView.post(new Runnable() {
#Override
public void run() {
onBackPressed()
}
});
I do not understand why, but it works!
You may be calling fragmentManager.popBackStackImmediate(); when activity is paused. Activity is not finished but is paused and not on foreground. You need to check whether activity is paused or not before popBackStackImmediate().
I noticed something very interesting. I have in my app the option to open the phone's gallery and the device asks what app to use, there I click on the gray area away from the dialog and saw this issue. I noticed how my activity goes from onPause, onSaveInstanceState back to onResume, it doesn't happen to visit onCreateView. I am doing transactions at onResume. So what I ended up doing is setting a flag being negated onPause, but being true onCreateView. if the flag is true onResume then do onCommit, otherwise commitAllowingStateLoss. I could go on and waste so much time but I wanted to check the lifecycle. I have a device which is sdkversion 23, and I don't get this issue, but I have another one which is 21, and there I see it.
I am really confused with the internal state of a Fragment.
I have an Activity holding only one Fragment at once and replaces it, if another Fragment should get shown. From the docs onSaveInstanceState is called ONLY if the Activitys onSaveInstanceState is getting called (which isn't called in my case).
If I stop my Fragment, I'll store its state myself inside a Singleton (yeah, I know I hate Singletons, too, but wasn't my idea to do so).
So I have to recreate the whole ViewHirarchy, create new Views (by using the keyword new), restore its state and return them in onCreateView.
I also have a Checkbox inside this View from which I explicitly do NOT want to store its state.
However the FragmentManager wants to be "intelligent" and calls onViewStateRestored with a Bundle I never created myself, and "restores" the state of the old CheckBox and applies it to my NEW CheckBox. This throws up so many questions:
Can I control the bundle from onViewStateRestored?
How does the FragmentManager take the state of a (probably garbage-collected) CheckBox and applies it to the new one?
Why does it only save the state of the Checkbox (Not of TextViews??)
So to sum it up: How does onViewStateRestored work?
Note I'm using Fragmentv4, so no API > 17 required for onViewStateRestored
Well, sometimes fragments can get a little confusing, but after a while you will get used to them, and learn that they are your friends after all.
If on the onCreate() method of your fragment, you do: setRetainInstance(true); The visible state of your views will be kept, otherwise it won't.
Suppose a fragment called "f" of class F, its lifecycle would go like this:
- When instantiating/attaching/showing it, those are the f's methods that are called, in this order:
F.newInstance();
F();
F.onCreate();
F.onCreateView();
F.onViewStateRestored;
F.onResume();
At this point, your fragment will be visible on the screen.
Assume, that the device is rotated, therefore, the fragment information must be preserved, this is the flow of events triggered by the rotation:
F.onSaveInstanceState(); //save your info, before the fragment is destroyed, HERE YOU CAN CONTROL THE SAVED BUNDLE, CHECK EXAMPLE BELLOW.
F.onDestroyView(); //destroy any extra allocations your have made
//here starts f's restore process
F.onCreateView(); //f's view will be recreated
F.onViewStateRestored(); //load your info and restore the state of f's view
F.onResume(); //this method is called when your fragment is restoring its focus, sometimes you will need to insert some code here.
//store the information using the correct types, according to your variables.
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable("foo", this.foo);
outState.putBoolean("bar", true);
}
#Override
public void onViewStateRestored(Bundle inState) {
super.onViewStateRestored(inState);
if(inState!=null) {
if (inState.getBoolean("bar", false)) {
this.foo = (ArrayList<HashMap<String, Double>>) inState.getSerializable("foo");
}
}
}
I'm getting this on some cases, in onResume(), of an activity which uses a FragmentStatePagerAdapter. When using device's back button. Not always. Not reproducible.
I'm using support package v4, last revision (8).
Already searched with google, no success finding a useful answer.
Looking in the source, it's thrown here: FragmentManager.java
#Override
public void putFragment(Bundle bundle, String key, Fragment fragment) {
if (fragment.mIndex < 0) {
throw new IllegalStateException("Fragment " + fragment
+ " is not currently in the FragmentManager");
}
bundle.putInt(key, fragment.mIndex);
}
But why is the index of fragment < 0 there?
The code instantiating the fragments:
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
switch(position) {
case 0:
fragment = MyFragment.newInstance(param1);
break;
case 1:
fragment = MyFragment2.newInstance(param2, param3);
break;
}
return fragment;
}
#Override
public int getCount() {
return 2;
}
If your ViewPager is layouted inside a fragment (not an activty) :
mViewPager.setAdapter(new MyFragmentStatePagerAdapter(getChildFragmentManager()));
I had the same error here, but for another reason.
In my case I had override getItemPosition in my FragmentStatePagerAdapter. My ideia was to return the position, if the item exists, or a POSITION_NONE, if it doesn't exists anymore.
Well, my problem was the fact that when my collection got empty I returned POSITION_NONE. And that broke everything.
My fix was to return POSITION_UNCHANGED when I had an empty collection.
Hope it helps someone else.
The two key things to understand the bug are:
It happens sometimes.
It happens in onResume().
Given this information, it's likely that the ViewPager is not retaining the state of your Fragments. If you are manipulating the Fragments directly from the Activity, it could be the case that the off-page Fragment is getting destroyed and your Activity is trying to manipulate a null fragment. To retain the Fragment's state even when it is not in the current screen, the fix is pretty simple:
private static final int NUM_ITEMS = 2;
ViewPager mPager = /** instantiate viewpager **/;
mPager.setOffscreenPageLimit(NUM_ITEMS-1);
You can read about it here:
ViewPager Fragments getting destroyed over time?
Got it, the reason was, that I'm intantiating the Adapter each time in onResume().
If I instantiate the adapter only once, in the life cycle of the activity, this does not happen anymore.
This exceptions means that you are trying to attach a fragment to an activity which is no longer correct state to attach the fragments. What it means is, whenever we try to attach fragments (especially through an asynchronous call), there is a small probability that someone has pressed the back button and activity is in merge of getting destroyed while you are attaching the fragment. This is not always reproducible as its just a race condition, and might not always occur..
There are two ways to fix this:
1) This happens when you the onSaveInstanceState of your activity has been called and post to that you are trying to attach the fragment, since android wont be able to save the state of your fragment now, it will throw an exception. To overcome this and if you are not saving the state of your fragment, try using
commitAllowingStateLoss(), while committing the transaction.
2) To be very safe, check whether your activity is in correct state or not before attaching the fragment, use the following code in onPause:
boolean isInCorrectState;
public void onCreate{
super.onCreate();
isInCorrectState = true;
}
public void onPause() {
super.onPause();
if(isFinishing()){
isInCorrectState = false;
}
}
Now use this flag to check if your activity is in correct state or not before attaching the fragment.. Meaning attach the fragment iff isInCorrectState == true.
For me the reason was something else.
In my Loader.onLoaderReset() I cleared the data from the adapter. When I was leaving the app, onDestroy() caused the loader to reset, which cleared the FragmentStatePagerAdapter. I think it caused the adapter to clear all references to it's Fragments, but somehow, the FragmentManager didn't notice and threw the Exception. Doesn't seem very logical to me.
Note that for me it happened Activity.onDestroy().
Hope it helps someone.
Fragments in the ViewPager are fixed, instead of trying to replace the fragments in the adapter, try to give a different set of fragments and notifyDataSet changed, or take the advantage of FrameLayout to show another fragment over the view pager tab's current fragment.
There is my solution that works:
Swipe Gesture applied at Fragment level along with ViewPager with it's default swipe disabled
I have a Live Android application, and from market i have received following stack trace and i have no idea why its happening as its not happening in application code but its getting caused by some or the other event from the application (assumption)
I am not using Fragments, still there is a reference of FragmentManager.
If any body can throw some light on some hidden facts to avoid this type of issue:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyDown(Activity.java:1962)
at android.view.KeyEvent.dispatch(KeyEvent.java:2482)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1720)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1258)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2851)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2824)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2011)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4025)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
at dalvik.system.NativeStart.main(Native Method)
This is the most stupid bug I have encountered so far. I had a Fragment application working perfectly for API < 11, and Force Closing on API > 11.
I really couldn't figure out what they changed inside the Activity lifecycle in the call to saveInstance, but I here is how I solved this :
#Override
protected void onSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}
I just do not make the call to .super() and everything works great. I hope this will save you some time.
EDIT: after some more research, this is a known bug in the support package.
If you need to save the instance, and add something to your outState Bundle you can use the following :
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
super.onSaveInstanceState(outState);
}
EDIT2: this may also occur if you are trying to perform a transaction after your Activity is gone in background. To avoid this you should use commitAllowingStateLoss()
EDIT3: The above solutions were fixing issues in the early support.v4 libraries from what I can remember. But if you still have issues with this you MUST also read #AlexLockwood 's blog : Fragment Transactions & Activity State Loss
Summary from the blog post (but I strongly recommend you to read it) :
NEVER commit() transactions after onPause() on pre-Honeycomb, and onStop() on post-Honeycomb
Be careful when committing transactions inside Activity lifecycle methods. Use onCreate(), onResumeFragments() and onPostResume()
Avoid performing transactions inside asynchronous callback methods
Use commitAllowingStateLoss() only as a last resort
Looking in Android source code on what causes this issue gives that flag mStateSaved in FragmentManagerImpl class (instance available in Activity) has value true. It is set to true when the back stack is saved (saveAllState) on call from Activity#onSaveInstanceState.
Afterwards the calls from ActivityThread don't reset this flag using available reset methods from FragmentManagerImpl#noteStateNotSaved() and dispatch().
The way I see it there are some available fixes, depending on what your app is doing and using:
Good ways
Before anything else: I would advertise Alex Lockwood article. Then, from what I've done so far:
For fragments and activities that don't need to keep any state information, call commitAllowStateLoss. Taken from documentation:
Allows the commit to be executed after an activity's state is saved. This is dangerous because the commit can be lost if the activity needs to later be restored from its state, so this should only be used for cases where it is okay for the UI state to change unexpectedly on the user`. I guess this is alright to use if the fragment is showing read-only information. Or even if they do show editable info, use the callbacks methods to retain the edited info.
Just after the transaction is commit (you just called commit()), make a call to FragmentManager.executePendingTransactions().
Not recommended ways:
As Ovidiu Latcu mentioned above, don't call super.onSaveInstanceState(). But this means you will lose the whole state of your activity along with fragments state.
Override onBackPressed and in there call only finish(). This should be OK if you application doesn't use Fragments API; as in super.onBackPressed there is a call to FragmentManager#popBackStackImmediate().
If you are using both Fragments API and the state of your activity is important/vital, then you could try to call using reflection API FragmentManagerImpl#noteStateNotSaved(). But this is a hack, or one could say it's a workaround. I don't like it, but in my case it's quite acceptable since I have a code from a legacy app that uses deprecated code (TabActivity and implicitly LocalActivityManager).
Below is the code that uses reflection:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
invokeFragmentManagerNoteStateNotSaved();
}
#SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeFragmentManagerNoteStateNotSaved() {
/**
* For post-Honeycomb devices
*/
if (Build.VERSION.SDK_INT < 11) {
return;
}
try {
Class cls = getClass();
do {
cls = cls.getSuperclass();
} while (!"Activity".equals(cls.getSimpleName()));
Field fragmentMgrField = cls.getDeclaredField("mFragments");
fragmentMgrField.setAccessible(true);
Object fragmentMgr = fragmentMgrField.get(this);
cls = fragmentMgr.getClass();
Method noteStateNotSavedMethod = cls.getDeclaredMethod("noteStateNotSaved", new Class[] {});
noteStateNotSavedMethod.invoke(fragmentMgr, new Object[] {});
Log.d("DLOutState", "Successful call for noteStateNotSaved!!!");
} catch (Exception ex) {
Log.e("DLOutState", "Exception on worka FM.noteStateNotSaved", ex);
}
}
Cheers!
Such an exception will occur if you try to perform a fragment transition after your fragment activity's onSaveInstanceState() gets called.
One reason this can happen, is if you leave an AsyncTask (or Thread) running when an activity gets stopped.
Any transitions after onSaveInstanceState() is called could potentially get lost if the system reclaims the activity for resources and recreates it later.
Simply call super.onPostResume() before showing your fragment or move your code in onPostResume() method after calling super.onPostResume(). This solve the problem!
This can also happen when calling dismiss() on a dialog fragment after the screen has been locked\blanked and the Activity + dialog's instance state has been saved. To get around this call:
dismissAllowingStateLoss()
Literally every single time I'm dismissing a dialog i don't care about it's state anymore anyway, so this is ok to do - you're not actually losing any state.
Short And working Solution :
Follow Simple Steps :
Step 1 : Override onSaveInstanceState state in respective fragment. And remove super method from it.
#Override
public void onSaveInstanceState(Bundle outState) {
};
Step 2 : Use CommitAllowingStateLoss(); instead of commit(); while fragment operations.
fragmentTransaction.commitAllowingStateLoss();
I think Lifecycle state can help to prevent such crash starting from Android support lib v26.1.0 you can have the following check:
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)){
// Do fragment's transaction commit
}
or you can try:
Fragment.isStateSaved()
more information here
https://developer.android.com/reference/android/support/v4/app/Fragment.html#isStateSaved()
this worked for me... found this out on my own... hope it helps you!
1) do NOT have a global "static" FragmentManager / FragmentTransaction.
2) onCreate, ALWAYS initialize the FragmentManager again!
sample below :-
public abstract class FragmentController extends AnotherActivity{
protected FragmentManager fragmentManager;
protected FragmentTransaction fragmentTransaction;
protected Bundle mSavedInstanceState;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSavedInstanceState = savedInstanceState;
setDefaultFragments();
}
protected void setDefaultFragments() {
fragmentManager = getSupportFragmentManager();
//check if on orientation change.. do not re-add fragments!
if(mSavedInstanceState == null) {
//instantiate the fragment manager
fragmentTransaction = fragmentManager.beginTransaction();
//the navigation fragments
NavigationFragment navFrag = new NavigationFragment();
ToolbarFragment toolFrag = new ToolbarFragment();
fragmentTransaction.add(R.id.NavLayout, navFrag, "NavFrag");
fragmentTransaction.add(R.id.ToolbarLayout, toolFrag, "ToolFrag");
fragmentTransaction.commitAllowingStateLoss();
//add own fragment to the nav (abstract method)
setOwnFragment();
}
}
I was always getting this when I tried to show fragment in onActivityForResult() method, so the problem was next:
My Activity is paused and stopped, which means, that onSaveInstanceState() was called already (for both pre-Honeycomb and post-Honeycomb devices).
In case of any result I made transaction to show/hide fragment, which causes this IllegalStateException.
What I made is next:
Added value for determining if action I want was done (e.g. taking photo from camere - isPhotoTaken) - it can be boolean or integer value depending how much different transactions you need.
In overriden onResumeFragments() method I checked for my value and after made fragment transactions I needed. In this case commit() was not done after onSaveInstanceState, as state was returned in onResumeFragments() method.
I solved the issue with onconfigurationchanged. The trick is that according to android activity life cycle, when you explicitly called an intent(camera intent, or any other one); the activity is paused and onsavedInstance is called in that case. When rotating the device to a different position other than the one during which the activity was active; doing fragment operations such as fragment commit causes Illegal state exception. There are lots of complains about it. It's something about android activity lifecycle management and proper method calls.
To solve it I did this:
1-Override the onsavedInstance method of your activity, and determine the current screen orientation(portrait or landscape) then set your screen orientation to it before your activity is paused. that way the activity you lock the screen rotation for your activity in case it has been rotated by another one.
2-then , override onresume method of activity, and set your orientation mode now to sensor so that after onsaved method is called it will call one more time onconfiguration to deal with the rotation properly.
You can copy/paste this code into your activity to deal with it:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Toast.makeText(this, "Activity OnResume(): Lock Screen Orientation ", Toast.LENGTH_LONG).show();
int orientation =this.getDisplayOrientation();
//Lock the screen orientation to the current display orientation : Landscape or Potrait
this.setRequestedOrientation(orientation);
}
//A method found in stackOverflow, don't remember the author, to determine the right screen orientation independently of the phone or tablet device
public int getDisplayOrientation() {
Display getOrient = getWindowManager().getDefaultDisplay();
int orientation = getOrient.getOrientation();
// Sometimes you may get undefined orientation Value is 0
// simple logic solves the problem compare the screen
// X,Y Co-ordinates and determine the Orientation in such cases
if (orientation == Configuration.ORIENTATION_UNDEFINED) {
Configuration config = getResources().getConfiguration();
orientation = config.orientation;
if (orientation == Configuration.ORIENTATION_UNDEFINED) {
// if height and widht of screen are equal then
// it is square orientation
if (getOrient.getWidth() == getOrient.getHeight()) {
orientation = Configuration.ORIENTATION_SQUARE;
} else { //if widht is less than height than it is portrait
if (getOrient.getWidth() < getOrient.getHeight()) {
orientation = Configuration.ORIENTATION_PORTRAIT;
} else { // if it is not any of the above it will defineitly be landscape
orientation = Configuration.ORIENTATION_LANDSCAPE;
}
}
}
}
return orientation; // return value 1 is portrait and 2 is Landscape Mode
}
#Override
public void onResume() {
super.onResume();
Toast.makeText(this, "Activity OnResume(): Unlock Screen Orientation ", Toast.LENGTH_LONG).show();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
I had the same problem, getting IllegalStateException, but replacing all my calls to commit() with commitAllowingStateLoss() did not help.
The culprit was a call to DialogFragment.show().
I surround it with
try {
dialog.show(transaction, "blah blah");
}
catch(IllegalStateException e) {
return;
}
and that did it. OK, I don't get to show the dialog, but in this case that was fine.
It was the only place in my app where I first called FragmentManager.beginTransaction() but never called commit() so I did not find it when I looked for "commit()".
The funny thing is, the user never leaves the app. Instead the killer was an AdMob interstitial ad showing up.
My solution for that problem was
In fragment add methods:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
guideMapFragment = (SupportMapFragment)a.getSupportFragmentManager().findFragmentById(R.id.guideMap);
guideMap = guideMapFragment.getMap();
...
}
#Override
public void onDestroyView() {
SherlockFragmentActivity a = getSherlockActivity();
if (a != null && guideMapFragment != null) {
try {
Log.i(LOGTAG, "Removing map fragment");
a.getSupportFragmentManager().beginTransaction().remove(guideMapFragment).commit();
guideMapFragment = null;
} catch(IllegalStateException e) {
Log.i(LOGTAG, "IllegalStateException on exit");
}
}
super.onDestroyView();
}
May be bad, but couldn't find anything better.
I got this issue.But I think this problem is not related to commit and commitAllowStateLoss.
The following stack trace and exception message is about commit().
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
But this exception was caused by onBackPressed()
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(Unknown Source)
at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(Unknown Source)
at android.support.v4.app.FragmentActivity.onBackPressed(Unknown Source)
They were all caused by checkStateLoss()
private void checkStateLoss() {
if (mStateSaved) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
if (mNoTransactionsBecause != null) {
throw new IllegalStateException(
"Can not perform this action inside of " + mNoTransactionsBecause);
}
mStateSaved will be true after onSaveInstanceState.
This problem rarely happens.I have never encountered this problem.I can not reoccurrence the problem.
I found issue 25517
It might have occurred in the following circumstances
Back key is called after onSaveInstanceState, but before the new activity is started.
use onStop() in code
I'm not sure what the root of the problem is.
So I used an ugly way.
#Override
public void onBackPressed() {
try{
super.onBackPressed();
}catch (IllegalStateException e){
// can output some information here
finish();
}
}
I have got the same issue in my App. I have been solved this issue just calling the super.onBackPressed(); on previous class and calling the commitAllowingStateLoss() on the current class with that fragment.
onSaveInstance will be called if a user rotates the screen so that it can load resources associated with the new orientation.
It's possible that this user rotated the screen followed by pressing the back button (because it's also possible that this user fumbled their phone while using your app)
Another lifecycle way to solve the issue is to use the latest released lifecycle-ktx with kotlin.
lifecycleScope.launchWhenResumed {
// your code with fragment or dialogfragment
}
The closure will be run after resume state, so even this method is called after
stop, it'll be safly excuted when the next resume come.
You can also choose like
lifecycleScope.launchWhenCreated
// or
lifecycleScope.launchWhenStarted
to fit your situation.
The code will be cancelled when destroy is come.
The Google document link:
https://developer.android.com/kotlin/ktx#lifecycle
Read
http://chris-alexander.co.uk/on-engineering/dev/android-fragments-within-fragments/
article.
fragment.isResumed() checking helps me in onDestroyView w/o using onSaveInstanceState method.
Same issue from me and after a day long analysis of all articles, blog and stackoverflow i've found a simple solution. Don't use savedInstanceState at all, this is the condition with one line of code. On the fragment code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
.....
This happens whenever you are trying to load a fragment but the activity has changed its state to onPause().This happens for example when you try to fetch data and load it to the activity but by the time the user has clicked some button and has moved to next activity.
You can solve this in two ways
You can use transaction.commitAllowingStateLoss() instead of transaction.commit() to load fragment but you may end up losing commit operation that is done.
or
Make sure that activity is in resume and not going to pause state when loading a fragment.
Create a boolean and check if activity is not going to onPause() state.
#Override
public void onResume() {
super.onResume();
mIsResumed = true;
}
#Override
public void onPause() {
mIsResumed = false;
super.onPause();
}
then while loading fragment check if activity is present and load only when activity is foreground.
if(mIsResumed){
//load the fragment
}
Thanks #gunar, but I think there is a better way.
According to doc :
* If you are committing a single transaction that does not modify the
* fragment back stack, strongly consider using
* {#link FragmentTransaction#commitNow()} instead. This can help avoid
* unwanted side effects when other code in your app has pending committed
* transactions that expect different timing.
*
* #return Returns true if there were any pending transactions to be
* executed.
*/
public abstract boolean executePendingTransactions();
So use commitNow to replace:
fragmentTransaction.commit();
FragmentManager.executePendingTransactions()
Well, after trying all the above solutions without success (because basically i dont have transactions).
On my case i was using AlertDialogs and ProgressDialog as fragments that, sometimes, on rotation, when asking for the FragmentManager, the error rises.
I found a workaround mixing some many similar posts:
Its a 3 step solution, all done on your FragmentActivity (in this case, its called GenericActivity):
private static WeakReference<GenericActivity> activity = null; //To avoid bug for fragments: Step 1 of 3
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
//To avoid bug for fragments: Step 2 of 3
activity = new WeakReference<GenericActivity>(this);
}
#Override
public FragmentManager getSupportFragmentManager(){
//To avoid bug for fragments: Step 3 of 3
if (this == activity.get()) {
return super.getSupportFragmentManager();
}
return activity.get().getSupportFragmentManager();
}
When i use startactivity in one fragment, i will get this exception;
When i change to use startactivityforresult, the exception is gone :)
So the easy way to fix it is use the startActivityForResult api :)
I was getting this exception when I was pressing back button to cancel intent chooser on my map fragment activity.
I resolved this by replacing the code of onResume()(where I was initializing the fragment and committing transaction) to onStart() and the app is working fine now.
Hope it helps.
This is fixed in Android 4.2 and also in the support library's source.[*]
For details of the cause (and work-arounds) refer to the the Google bug report:
http://code.google.com/p/android/issues/detail?id=19917
If you're using the support library then you shouldn't have to worry about this bug (for long)[*]. However, if you're using the API directly (i.e. Not using the support library's FragmentManager) and targeting an API below Android 4.2 then you will need to try one of the work-arounds.
[*] At the time of writing the Android SDK Manager is still distributing an old version that exhibits this bug.
Edit I'm going to add some clarification here because I've obviously somehow confused whoever down-voted this answer.
There are several different (but related) circumstances that can cause this exception to be thrown. My answer above is referring to the specific instance discussed in the question i.e. a bug in Android which has subsequently been fixed. If you're getting this exception for another reason it's because you're adding/removing fragments when you shouldn't be (after fragment states have been saved). If you're in such a situation then perhaps "Nested Fragments - IllegalStateException “Can not perform this action after onSaveInstanceState”" can be of use to you.
After researching a bit the solution to this problem is to do your fragment commits in the onresume.
Source: https://wenchaojames.wordpress.com/2013/01/12/illegalstateexception-from-onactivityresult/
My use case: I have used listener in fragment to notify activity that some thing happened. I did new fragment commit on callback method. This works perfectly fine on first time. But on orientation change the activity is recreated with saved instance state. In that case fragment is not created again implies that the fragment have the listener which is old destroyed activity. Any way the call back method will get triggered on action. It goes to destroyed activity which cause the issue. The solution is to reset the listener in fragment with current live activity. This solve the problem.
What I found is that if another app is dialog type and allows touches to be sent to background app then almost any background app will crash with this error.
I think we need to check every time a transaction is performed if the instance was saved or restored.
In my case, with the same error exception, i put the "onBackPressed()" in a runnable (you can use any of your view):
myView.post(new Runnable() {
#Override
public void run() {
onBackPressed()
}
});
I do not understand why, but it works!
You may be calling fragmentManager.popBackStackImmediate(); when activity is paused. Activity is not finished but is paused and not on foreground. You need to check whether activity is paused or not before popBackStackImmediate().
I noticed something very interesting. I have in my app the option to open the phone's gallery and the device asks what app to use, there I click on the gray area away from the dialog and saw this issue. I noticed how my activity goes from onPause, onSaveInstanceState back to onResume, it doesn't happen to visit onCreateView. I am doing transactions at onResume. So what I ended up doing is setting a flag being negated onPause, but being true onCreateView. if the flag is true onResume then do onCommit, otherwise commitAllowingStateLoss. I could go on and waste so much time but I wanted to check the lifecycle. I have a device which is sdkversion 23, and I don't get this issue, but I have another one which is 21, and there I see it.