Can not perform this action after onSaveInstanceState in android - android

How can I Add a check to prevent this error
I am getting the error in this code:
private fun clearFragmentsFromContainer() {
if(supportFragmentManager.backStackEntryCount>0) {
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
}
Error on the line:
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
Log:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2080)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2106)
at android.support.v4.app.FragmentManagerImpl.popBackStack(FragmentManager.java:832)
at com.caring2u.organizer.ui.activities.screen.ActSummaryEvent.clearFragmentsFromContainer(ActSummaryEvent.kt:524)
at com.caring2u.organizer.ui.activities.screen.ActSummaryEvent.onClickEventTabs(ActSummaryEvent.kt:466)
at com.caring2u.organizer.ui.activities.screen.ActSummaryEvent.dataEventsList(ActSummaryEvent.kt:162)
at com.caring2u.organizer.network.retrofit.retrofitTasks.RetroEventsSummary$initiate$1.onResponse(RetroEventsSummary.kt:62)

You are trying to change the fragment stack after onPause as can be seen from the log.
You can either use FragmentManger.commitAllowingStateLoss or be sure to not call this method after onPause

To remove all fragments in a container please use below code
for (Fragment fragment:getSupportFragmentManager().getFragments()) {
if (fragment instanceof NavigationDrawerFragment) {
continue;
}
else if (fragment!=null) {
getSupportFragmentManager().beginTransaction().remove(fragment).commit();
}
}

Probably your fragment transaction is committing after onSavedInstanceState() callback. That means that your activity is stopping and you're adding your fragment in a state that cannot be saved. Actually, during onSavedInstanceState() call Android takes a snapshot of your activity state, this means that if you commit a transaction after the state it's saved the transaction won't be remembered as it was never recorded. From the user point of view that will result in a UI state loss.
Instead of using commitAllowingStateLoss you should understand if you're calling your clearFragmentsFromContainer method from an asynchronous method, in that case probably you should simply move your transaction from the async method.
More about "commit state loss":
AndroidDesignPatterns.com
Elye on Medium
In order to understand if your activity has already called onSaveInstanceState() method, you might think to place a flag inside onSaveInstanceState callback, resetting the flag in the dual method onRestoreInstanceState, something like:
val saveInstanceStateCalled = false
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
saveInstanceStateCalled = true
}
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
saveInstanceStateCalled = false
}
Then you can check the flag before calling clearFragmentsFromContainer

I solved this using the code
if (!fragmentManager.isStateSaved()) {
if(supportFragmentManager.backStackEntryCount>0) {
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
}

Related

Caused by java.lang.IllegalStateException Can not perform this action after onSaveInstanceState

I have an activity and a fragment within that activity. The fragment is loaded within the activity onCreate().
if (!supportFragmentManager.isDestroyed) {
val fragmentTransaction = this.supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.containerLayout, fragment).commit()
}
Inside the fragment, I am performing an API call and when the result is received, the activity gets the callback and the result is passed to the fragment from the activity.
The issue is when I load this activity and when the API is still on call if I press the device recents button then the app crashes showing the below exception.
Caused by java.lang.IllegalStateException Can not perform this action after onSaveInstanceState
I understand that the problem is the fragment tries to commit after onSaveInstanceState is called. But how is that happening I am not clear. I went through the article too. It says three points as solution.
To commit the fragment within onCreate() which I am already doing.
Not to commit in onPostExecute() which is not applicable to me.
Use commitAllowingStateLoss() only as a last resort.
Should I need to change commit() to commitAllowingStateLoss()? As I went through the docs, I don't feel that safe too. Could someone suggest to me the right way?
I didn't use commitAllowingStateLoss(). I put the code as:
var isAnException: Boolean = false
try {
if (!supportFragmentManager.isDestroyed) {
val fragmentTransaction = this.supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.containerLayout, fragment).commit()
}
} catch (exception: IllegalStateException) {
isAnException = true
}
and in onResume() of my activity, I added the below code to make the fragment work when taken from the recents.
override fun onResume() {
if (isAnException) {
isAnException = false
//fragment load and set the views
}
super.onResume()
}

Ignoring navigate() call: FragmentManager has already saved its state

I'm using navigation in MainActivity, then I start SecondActivity (for result). After finish of SecondActivity I would like to continue with navigation in MainActivity, but FragmentManager has saved his state already.
On Navigation.findNavController(view).navigate(R.id.action_next, bundle) I receive log message:
Ignoring navigate() call: FragmentManager has already saved its state
How I can continue in navigation?
You must always call super.onActivityResult() in your Activity's onActivityResult. That is what:
Unlocks Fragments so they can do fragment transactions (i.e., avoid the state is already saved errors)
Dispatches onActivityResult callbacks to Fragments that called startActivityForResult.
Finally, I fix the issue by simple calling super.onPostResume() right before navigating to restore state.
I've solved this problem this way:
#Override
public void onActivityResult() { //inside my fragment that started activity for result
model.navigateToResults = true; //set flag, that navigation should be performed
}
and then
#Override
public void onResume() { //inside fragment that started activity for result
super.onResume();
if(model.navigateToResults){
model.navigateToResults = false;
navController.navigate(R.id.action_startFragment_to_resultsFragment);
}
}
not sure, if this is not a terrible hack, but it worked for me. FramgentManager state is restored at this point (onResume) and no problems with navigation occur.
I believe above solutions should work. But my problem was different. There was a third party sdk which was launching its activity using context provided by me and it was delivering the result on a listener which I had to implement.
So there was no option for me to work with onActivityResult :(
I used below hack to solve the issue:
private var runnable: Runnable? = null // Runnable object to contain the navigation code
override fun onResume() {
super.onResume()
// run any task waiting for this fragment to be resumed
runnable?.run()
}
override fun responseListener(response: Response) { // Function in which you are getting response
if (!isResumed) {
// add navigation to runnable as fragment is not resumed
runnable = Runnable {
navController.navigate(R.id.destination_to_navigate)
}
} else {
// navigate normally as fragment is already resumed
navController.navigate(R.id.destination_to_navigate)
}
}
Let me know if there is any better solution for this. Currently I found this very simple and easy to implement :)
call super.onPostResume() before navigation....It's working

Can not perform this action after onSaveInstanceState WHEN commit

I have got exception when ft.commit() and I don't know why.
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1448)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1466)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:634)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:613)
at MainActivity.attachFragment(MainActivity.java:242)
at MainActivity.attachFragment(MainActivity.java:225)
at MainActivity.showHome(MainActivity.java:171)
at MainActivity.onComplete(MainActivity.java:278)
at MDownloadManager.onDownloadComplete(MDownloadManager.java:83)
at DownloadRequestQueue$CallBackDelivery$2.run(DownloadRequestQueue.java:61)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:149)
at android.app.ActivityThread.main(ActivityThread.java:5257)
at java.lang.reflect.Method.invokeNative(Method.java)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:609)
at dalvik.system.NativeStart.main(NativeStart.java)
Here is my method where crash is comming.
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
if(addToBackStack) {
ft.addToBackStack(null);
ft.add(R.id.frame_container, fragment, tag);
} else {
ft.replace(R.id.frame_container, fragment, tag);
}
ft.commit();
Have you got any idea what is wrong?
I don't use onSaveInstanceState in my project.
Complete solution at Solution for IllegalStateException
Overriding onSaveInstanceSate is a hack which doesnt necessarily work for all the scenerios. Also using commitAllowingStateLoss() is dangerous and could lead to UI irregularities.
We need to understand that IllegalStateException is encountered when we try to commit a fragment after the Activity state is lost - Activity is not in foreground (to understand more about Activity states read this). Therefore to avoid (resolve) this exception we just delay our fragment transaction until the state is restored
Declare two private boolean variables
public class MainActivity extends AppCompatActivity {
//Boolean variable to mark if the transaction is safe
private boolean isTransactionSafe;
//Boolean variable to mark if there is any transaction pending
private boolean isTransactionPending;
Now in onPostResume() and onPause we set and unset our boolean variable isTransactionSafe. Idea is to mark trasnsaction safe only when the activity is in foreground so there is no chance of stateloss.
/*
onPostResume is called only when the activity's state is completely restored. In this we will
set our boolean variable to true. Indicating that transaction is safe now
*/
public void onPostResume(){
super.onPostResume();
isTransactionSafe=true;
}
/*
onPause is called just before the activity moves to background and also before onSaveInstanceState. In this
we will mark the transaction as unsafe
*/
public void onPause(){
super.onPause();
isTransactionSafe=false;
}
private void commitFragment(){
if(isTransactionSafe) {
MyFragment myFragment = new MyFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.frame, myFragment);
fragmentTransaction.commit();
}
}
What we have done so far will save from IllegalStateException but our transactions will be lost if they are done after the activity moves to background, kind of like commitAllowStateloss(). To help with that we have isTransactionPending boolean variable
public void onPostResume(){
super.onPostResume();
isTransactionSafe=true;
/* Here after the activity is restored we check if there is any transaction pending from
the last restoration
*/
if (isTransactionPending) {
commitFragment();
}
}
private void commitFragment(){
if(isTransactionSafe) {
MyFragment myFragment = new MyFragment();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.frame, myFragment);
fragmentTransaction.commit();
isTransactionPending=false;
}else {
/*
If any transaction is not done because the activity is in background. We set the
isTransactionPending variable to true so that we can pick this up when we come back to
foreground
*/
isTransactionPending=true;
}
}
The onSaveInstanceState method is part of the activity lifecycle. So, even if you don't call it explicitly, it is call at some point by your Activity.
So the question is where in the activity lifecycle did you use the code you show us ?
One workaround is to use commitAllowingStateLoss instead of commit for the fragment transaction.
(You should read the description in the link to see if it is ok for you to use this method)
I had the same issue but I was able to solve this by overriding onSaveInstanceState and comment the line of calling its super like this in fragment.
#Override
public void onSaveInstanceState(Bundle outState) {
// super.onSaveInstanceState(outState);
}
Hope that help.
Here is an updated solution using Kotlin. For full details you can check this article : Avoid Fragment IllegalStateException: Can not perform this action after onSaveInstanceState
class MainActivity : AppCompatActivity(){
private var isActivityResumed = false
private var lastCall: (() -> Unit)? = null
companion object {
private const val ROOT_FRAGMENT = "root"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Call some expensive async operation that will result in onRequestCallback below
myExpensiveAsyncOperation()
}
override fun onPause() {
super.onPause()
//Very important flag
isActivityResumed = false
}
override fun onResume() {
super.onResume()
isActivityResumed = true
//If we have some fragment to show do it now then clear the queue
if(lastCall != null){
updateView(lastCall!!)
lastCall = null
}
}
/**
* Fragment Management
*/
private val fragmentA : () -> Unit = {
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer_fl, FragmentA())
.addToBackStack(ROOT_FRAGMENT)
.commit()
}
private val fragmentB : () -> Unit = {
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer_fl, FragmentB())
.addToBackStack(ROOT_FRAGMENT)
.commit()
}
private val popToRoot : () -> Unit = { supportFragmentManager.popBackStack(ROOT_FRAGMENT,0) }
// The function responsible for all our transactions
private fun updateView(action: () -> Unit){
//If the activity is in background we register the transaction
if(!isActivityResumed){
lastCall = action
} else {
//Else we just invoke it
action.invoke()
}
}
// Just an example
private fun onRequestCallback() {
if(something) {
updateView(fragmentA)
else {
updateView(fragmentB)
}
}
It's pretty simple, you cannot commit fragment transactions in an activity after onSaveInstanceState(Bundle outState) has been called. When is your code being called?
onSavedInstanceState is called as part of the activity lifecycle when a configuration change occurs. You have no control over it.
Hope this will help
EDIT1: 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
If you are using coroutines in your project you can easily make sure that your code will runs when lifecycle state is at least Started and not destroyed.
lifecycleScope.launchWhenStarted{}

Can not perform this action after onSaveInstanceState (EditText focus change)

I have ActivityA attaching FragmentA. There's an EditText in FragmentA which, if focused, adds FragmentB (below). The stack trace starts with onDestroy in ActivityA, which triggers onFocusChange, which fires off popBackStack. The isRemovingOrPartOfRemovalChain() should be returning true at this point but it occasionally returns false causing the popBackStack, hence the exception. Is there a bug in that method?
editText.setOnFocusChangeListener(new OnFocusChangeListener(){
#Override
public void onFocusChange(View view, boolean hasFocus) {
if(hasFocus){
FragmentManager fragmentManager = getChildFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(FRAGMENT_B);
if(fragment == null){
FragmentB fragmentB = FragmentB.newInstance();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fragment_b, fragmentB, FRAGMENT_B);
fragmentExploreSearchListTransaction.addToBackStack(null);
fragmentExploreSearchListTransaction.commit();
}
else{
if(!isRemovingOrPartOfRemovalChain()){
getChildFragmentManager().popBackStack();
}
}
}
});
public boolean isRemovingOrPartOfRemovalChain(){
if(isRemoving()){
return true;
}
Fragment fragment = this.getParentFragment();
if(fragment != null){
if(((MainFragment) fragment).isRemovingOrPartOfRemovalChain()){
return true;
}
else{
return false;
}
}
else{
return(getActivity().isFinishing());
}
}
/**
* Return true if this fragment is currently being removed from its
* activity. This is <em>not</em> whether its activity is finishing, but
* rather whether it is in the process of being removed from its activity.
*/
final public boolean isRemoving() {
return mRemoving;
}
When you commit fragment after onSavedInstanceState(Bundle outState) callback (e.g. onDestroy()), the committable Fragment state will be lost (becase Fragment.onSaveInstanceState(Bundle) won't be called in this situation).
In this case, when Activity is recreated, the committed fragment will not be present in Activity's FragmentManager, and so will not be restored. This is considered a state loss.
This behaviour might break or corrupt user experience, and is considered unintentional and exceptional, so the Android framework warns you about that by throwing an exception :-) Better save than sorry, right?
In case one knows what one's doing (which is almost always not so :-)), one may stick with .commitAllowingStateLoss(), but I strongly advise against it, as it will bring a legal bughole into your application.
Just do not commit fragments after it has became known that your Activity is destroying: for example,
...
boolean fieldActivityIsDestroying;
....
public void onSaveInstanceState(Bundle out){
super.onSaveInstanceState(out);
fieldActivityIsDestroying = true;
}
and check for field value when commiting the fragment.
Also you might want to FragmentManager.executePendingTransactions() to perform any fragment transactions immediately after you commit them to manager (default commit is asynchronous).
your issue because of activity state lost.
try this
fragmentExploreSearchListTransaction.commit()
to
fragmentExploreSearchListTransaction.commitAllowingStateLoss()
but it is not good solution, so i refer you read this blog, this blog is about fragment Transaction after save Activity Instance, I hope my information will help you.
There is not enough code to provide with the complete solution, but in this case:
It's better use Fragment#isRemoving() to check if fragment removed from activity;
If focus changed expected from user interaction with the screen it's better set/remove listener at following methods:
onCreateView()/onDestroyView();
onResume()/onPause();
If there is any reason that this solutions not work feel free to clarify.
Good luck!
Why don't you just set the onFocusedChangeListener in OnResume() and remove it in OnPause()?
That should prevent it from being triggered when your activity is finishing.

How to determine fragment restored from backstack

Been searching for this issue for a while to no avail now:
How to determine fragment is being restored from backstack?
I'm using the compatibility library and a ListFragment inside a FragmentActivity. When an item inside ListFragment is selected, a new Fragment is started to replace the ListFragment.
I noticed that when the FragmentActivity gets paused, the Fragment's onSaveInstanceState is called. But when the Fragment is put into the back stack via FragmentTransaction, onSaveInstanceState doesn't get called, then the lifecycle methods onCreateView and onActivityCreated gets called with null savedInstanceState Bundle.
I'm asking this because I want to load some data when the Fragment is created or restored, but not so when user comes back via. backstack.
I've looked at How to check if Fragment was restored from a backstack?
but want to add more details in hopes this would incite an answer.
Edit:
just noticed http://developer.android.com/reference/android/app/Fragment.html#onSaveInstanceState(android.os.Bundle)
says
Note however: this method may be called at any time before onDestroy(). There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state.
So onSaveInstanceState is definitely out of the question...
I think that most simple way is do this for example in onViewCreated() method:
if (savedInstanceState == null && !mAlreadyLoaded) {
mAlreadyLoaded = true;
// Do this code only first time, not after rotation or reuse fragment from backstack
}
Because when android put fragment on backstack, it only destroy its view, but don't kill instance itself, so mAlreadyLoaded will be still true when fragment will be restored from backstack.
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
public void onBackStackChanged() {
Log.i(TAG, "back stack changed ");
int backCount = getSupportFragmentManager().getBackStackEntryCount();
if (backCount == 0){
// block where back has been pressed. since backstack is zero.
}
}
});
use this addOnBackStackChangedListener.
When a fragment goes to back-stack onDestroyView() called. Not onDestroy().
And when a fragment pops from back-stack onCreateView() called. Not onCreate().
So add a boolean mIsRestoredFromBackstack to fragment and follow as below:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mIsRestoredFromBackstack = false;
}
#Override
public void onResume()
{
super.onResume();
if(mIsRestoredFromBackstack)
{
// The fragment restored from backstack, do some work here!
}
}
#Override
public void onDestroyView()
{
super.onDestroyView();
mIsRestoredFromBackstack = true;
}
MAJOR EDIT: Oct 15 2013
The previous explanation (kept below for reference) fails when the application is put to the background and brought back to the foreground.
Instead, it is better to compare the current size of the backstack with the one when the fragment was created & put into the backstack.
Take a good look at Figure 2 in http://developer.android.com/guide/components/fragments.html#Creating
What this figure tells you is that when a fragment is restored from the backstack, its onCreate() is not called, while its onCreateView() is.
So, you may want to do something like this:
public class MyFragment extends Fragment {
int mBackStackSize = 0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBackStackSize = getFragmentManager().getBackStackEntryCount();
}
public boolean isRestoredFromBackstack() {
return mBackStackSize > getFragmentManager().getBackStackEntryCount();
}
}
If you added fragment to backstack, and after some manipulation you hide it using fragmentTransaction.hide(fragment) and then restore it from backstack like fragmentTransaction.show(fragmentManager.findFragmentByTag(fragment.getName())); you can override onHiddenChanged(boolean hidden)
#Override
public void onHiddenChanged(boolean hidden) {
// TODO Auto-generated method stub
super.onHiddenChanged(hidden);
if (!hidden) {
//fragment became visible
//your code here
}
}
In some cases you can use isVisible method to understand is it first showing of a fragment or is it restored from the backstack.

Categories

Resources