What are the effects of commiting a dialogfragment transaction with state loss in android: Since its just a simple error dialog im showing with an ok button to close it i dont think i need to worry about state loss.
in my DialogFragment subclass i've over rided the show class so that it commits to include state loss so that i dont get illegalstateException...
#Override
public void show(FragmentManager manager, String tag) {
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
//its just dialogs so can we allow state loss to not trigger illegalStateExceptions
ft.commitAllowingStateLoss();
}
According to this Article
Originally you are trying to avoid this error (Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState).
This error stems from the fact that these Bundle objects of the onSaveInstanceState() represent a snapshot of an Activity at the moment onSaveInstanceState() was called, and nothing more. That means when you call FragmentTransaction#commit() after onSaveInstanceState() is called, the transaction won't be remembered because it was never recorded as part of the Activity's state in the first place
You tried to work around that by using commitAllowingStateLoss(), let's discuss
the difference between calling commit() and commitAllowingStateLoss() is that the latter will not throw an exception if state loss occurs. Usually you don't want to use this method because it implies that there is a possibility that state loss could happen. The better solution, of course, is to write your application so that commit() is guaranteed to be called before the activity's state has been saved, as this will result in a better user experience. Unless the possibility of state loss can't be avoided, commitAllowingStateLoss() should not be used.
Surprised no one ever gave a simple correct answer. The transaction will be lost. Since in this case you are adding a dialog fragment, if the fragments have to be restored, the dialog will be gone.
Test this by going into the developer settings and check 'never keep activities' or something that sounds a lot like that. Then go to your app, get the dialog open and press Home. Then open the app again.
Related
I have done ample research on this, and there is not one clear solution on the problem.
In the life-cycle, particularly in the Fragment life-cycle, following Exception comes any moment after onPause().
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
My logic says, that to continue with the current fragment, after it reaches this state, I have to restart the activity and again point back to the intended fragment using Intent.
I want to be clear on what is happening and what should be real solution to deal with it.
I need to know the pros and cons of this mechanism; its importance in Fragment or Activity life-cycle.
Also, if I am changing the Windows Feature in onCreate to not to go to sleep, unless if the user has manually pressed the home button, will still the activity will go to this state?
This exception happens when you're trying to add/remove/replace/interact in any other way with a Fragment inside the Activity when it's paused.
Which means Activity will not be able to restore it's state (restore the state of a Fragment which has been changed) if it will be destroyed right away.
Best solution here, is to check that Activity is NOT paused during the interaction with a Fragment.
Another option is to use commitAllowingStateLoss() to interact with Fragment transaction, with a risk of losing it's state.
See:
https://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss()
In a perfect world you should analyze each crash carefully and add checks to verify that you interact with fragments only when Activity is up and running.
A better explanation is presented in a new Android developer reference and guide documents for using JetPack Life Cycle Listener.
https://developer.android.com/topic/libraries/architecture/lifecycle#kotlin
The library makes the components Activity Life Cycle aware. That means you do not require an abstract baseActivity class which overrides every life cycle callback, and record that state in a boolean variable. LifeCycle listener will do it for you.
All you have to do is stop introducing a new fragment or stop any Loader that updates the UI when its response returns. The right time to do this is before onStop or onSavedInstance state is called, and your components will be made aware of it.
It clearly states that after the onSavedInstancState or onStop is called the UI becomes immutable till the onStart of the Activity is called again. Sometimes you have to call restart the same activity using NEW TASK and CLEAR TASK flags using intent, when this state occurs and there is no chance that otherwise onStart is going to be called.
Happy Coding :-)
In my application I have an Activity that holds 3 Fragments. The very first time the Activity is created, Fragment 1 is displayed. Next, all fragment transactions will be executed after a network operation. For example: Fragment 1 has a button to make a request to the server and when the result is ready, Fragment 1 uses a listener to call a method defined inside the parent activity, to replace fragment 1 with fragment 2.
This works fine, except when the parent activity receives the callback after its state has been saved by onSaveInstanceState(). An IllegalStateException is thrown.
I've read some answers about this problem, for example this post and I understood why this exception happens thanks to this blog.
I also take an example that I found here to try to solve the problem. This post suggests to always check if the activity is running before call commit(). So I declared a Boolean variable in the parent activity and I put its value to false in onPause() and to true in onResume().
The parent activity callback called after network operations has been completed is something like this piece of Kotlin code, where next is the number of the replacing fragment:
private fun changeFragment(next:Int){
// get the instance of the next fragment
val currentFragment = createFragment(next)
// do other stuff here
if(isRunning){
// prepare a replace fragment transaction and then commit
ft.commit()
}else{
// store this transaction to be executed when the activity state become running
}
}
This code is working fine and now I'm not getting the Exception anymore, but my question is: it's possible that onSaveInstanceState() is called after I check if(isRunning) and before I call ft.commit(), so that the commit() happens after the activity state has been saved causing IllegalStateException again?
I'm not sure if onSaveInstanceState() could interrupt my changeFragment() method at any point in time. Is it possible?
If the possibility exists and my code may be interrupted between if(isRunning) and ft.commit(), what I can do?
It could be solved adding a try{}catch(){} block like this?:
if(isRunning){
try{
ft.commit()
}catch(ie:IllegalStateException){
// store the transaction and execute it when the activity become running
}
}else{
// store the transaction and execute it when the activity become running
}
Its a bit late but as of API 26+ we can use following to check if we need to do a normal commit or commitAllowingStateLoss().
getSupportFragmentManager().isStateSaved();
Are you storing anything when you're changing states?
If not, then you can try commitAllowingStateLoss().
onSaveInstanceState() would not be able to interrupt your method if your method is being called on the main (UI) thread.
Another approach that tends to make your life easier is to not use callbacks, but rather adopt a reactive pattern like MVVM. In that pattern, your Activity or Fragment subscribe to an observable when they are interested in e.g. network responses and unsubscribe typically in the onStop or onPause lifecycle callbacks so that your methods never get called after onSaveInstanceState. For a good starting place, check the official LiveData overview.
I am sending an HTTP request to some server(in a background thread) inside onCreateView() method within my fragment class.
When the request received i check some stuff and according to that i add a new fragment(through fragment transaction in the UI thread).
I use the support library
But, for example, if the user press the android home button, while the request hasn't received yet, the fragment is going to pause or stop state and then the request received and of course the exception is thrown(just after trying to commit).
I searched the web and found some very good articles and answers which are relevant to this issue and all the things related to the 'state loss' after onSaveInstanceState() has been called, etc...
For example i read this excellent article of Alex Lockwood: http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
and also those stackoverflow questions/answers: getting exception "IllegalStateException: Can not perform this action after onSaveInstanceState"
and: "Can not perform this action after onSaveInstanceState" - why am I getting this exception from my activity's onResume method? and more.
But i couldn't came to a conclusion of what to do in my case.
The only thing that came to mind is to use commitAllowingStateLoss() instead of commit() but this fills hacky and not correct as some of the answers that i read conclude about it.
Also there was a suggestion to commit the transaction in onCreate() because it safe, but of course it can't work in my situation, all other suggestions doesn't work for me as well.
Maybe i can use some boolean flag to check if i can't make a transaction and then wait for the fragment to resume and if the flag is true then do the transaction.
This fills to much work and also presents the problem in which i need to know if the transaction can be done(don't even know how to check it).
I need to show a DialogFragment every time the user enter the Activity, so the best callback method would be onResume(). Thing is I can't just call DialogFragment#show() cause it will throw some IllegalStateException, so I did this:
#Override
protected void onResume() {
super.onResume();
if(!dialog.isVisible()) {
dialog.show(getSupportFragmentManager(), "login-dialog");
}
}
#Override
protected void onPause() {
super.onPause();
if(dialog.isVisible()) {
dialog.dismiss();
}
}
I'm getting:
java.lang.IllegalStateException: Fragment already added: LoginDialog{41fac3e0 #0 login-dialog}
I had exactly the same exception and message while trying to keep a DialogFragment visible and working between screen rotations (orientation change). Are your fragments from the support library?
In my case, I was using the support library and the call to dialog.show() was in the activity's onCreate(). What seems to have solved the problem was the workaround presented here:
https://stackoverflow.com/a/14016339/3577211
Which basically is putting setRetainInstance(true) in your DialogFragment's onCreate(). The second part, that is, overriding its onDestroy, was the only way I have managed to make the DialogFragment not go away during screen rotations (and come back when rotating again), even though they say the latest support library versions took care of that (perhaps I have a mess with the jars in here).
You did not state whether you get the exception always or it works only the first time the activity is created. But what I guess is happening is that dialog.show() is actually a wrapper for a getFragmentManager().add() call, which probably checks whether setRetainInstance is true for the dialog and if it is false, the DialogFragment instance is added again to the same FragmentManager instance, which throws that exception.
Yet another suggestion would be not to use onResume() for that; instead use onCreate() and onSaveInstanceState(Bundle outState) with some kind of flag, since that way you can save data across activity cycles (and unless your dialog is just a popup constant message, which is very annoying to have every time the user hits onResume(), you probably have some data to interact with the user that could be lost in unexpected situations).
change
!dialog.isVisible()
to
!dialog.isAdded()
I'm developing an android tablet application and i need to implement screen orientation (landscape and portrait). when screen orientation changes the fragment asyntactask restarting again. (sometimes application crashing) I need to stop restarting the asynctask and request data again. want to load the existing json data in to the new screen.
any help appreciate.
approch: android:configChanges="keyboardHidden|orientation|screenSize" is not working because of im using a fragment class.
You may find some hack (including muting the configuration changes or retaining the instance) to somehow get it work, but really AsyncTasks are evil and you should stay as far as you can from it most of the time.
If your current AsyncTask's job is to load some data from the network before showing it inside your Fragment, I'd recommend to use an AsyncTaskLoader. The examples in the javadoc should give you a good idea of how to implement it.
This will allow the network request to keep going while you rotate, to notify the UI (i.e. the LoaderManager.LoaderCallbacks) with the result only when it is ready to process it (so not while rotating for example), and will also cache the result so that you don't re-issue the network request each time you need the data.
There are a bunch of 3rd-party libraries trying to address this very common problem too, and Loaders also have their intricacies, but if I understand your use case correctly it should be just what you need.
Don't forget that whatever solution you choose it will have to account for the rotation (activity destroy / recreate) happening before, during and after the load of the data.
It seems that this approach is the less recommended. If you're initializing and executing your AsyncTask within your onCreate() method, I'd suggest using this approach. Basically it consists on saving the data you don't need to restart on a runtime configuration change, so you can handle them afterwards in the onCreate() method and decide which information you want to keep from the previous landscape and which should be restarted.
You can easily avoid the "re-creation" of a fragment, just using the setRetainInstance method on it.
public void setRetainInstance (boolean retain)
Control whether a fragment instance is retained across Activity
re-creation (such as from a configuration change). This can only be
used with fragments not in the back stack. If set, the fragment
lifecycle will be slightly different when an activity is recreated:
•
onDestroy() will not be called (but onDetach() still will be, because
the fragment is being detached from its current activity).
•
onCreate(Bundle) will not be called since the fragment is not being
re-created.
• onAttach(Activity) and onActivityCreated(Bundle) will
still be called.
Reference link
You can use this for example in you onCreateView method, like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
setRetainInstance(true);
// ... more of your code
}
While the Activity that contains the fragment still re-creating, the fragment instance will be the same, and will be reattached to the new activity.
Keep in mind that even with setRetainInstance the onCreateView method will be called always (in orientation change) because the fragment need to inflate the view according to the new space/dimensions, so if you are executing your asynctask from that method, you should consider moving it to the onCreate method (which will only be executed once) to avoid the "re-launching" every orientation change.
Hope it helps.