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()
Related
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.
I have a viewpager holding many fragments. When the activity is sent to background and when resources are needed, the OS will kill the app or at least some fragments. When I return to the activity it crashes because the activity tries to attach new instances of all fragments it held before the clean-up and now some fields are null. This of course could be fixed by properly implementing state saving and restoring using Bundles, but I don't want to do that.
Instead I want to prevent restoring the fragments. Is there a way to tell the OS that once it has sent the GC in and destroyed fragments, it shouldn't bother recreating them at all? Once the cleaning-up happens, I want the activity to be simply recreated on return, as if a user launched it by taping the icon. Any chance of doing that?
The proposed solution here https://stackoverflow.com/a/15683289/552735 does not work. It causes exceptions
java.lang.IllegalStateException: Fragement no longer exists for key f2: index 3
I had a problem just like this. Here's how I fixed it:
#Override
protected void onSaveInstanceState(Bundle savedInstanceState)
{
savedInstanceState.clear();
}
Note that this method will ensure that absolutely no UI state information will be stored when the Activity is killed by the system - this has the effect of also not restoring any Views when onRestoreInstanceState() gets called with the same Bundle after onStart().
You can't disable the save/restore instance state actions. In case you don't want to actually implement this logic, you have to reload the form especially if your form heavily loaded with fragments.
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
startActivity(new Intent(this,YOUR_ACTIVITY.class));
finish();
}
I started reading around about the activity life cycle callbacks and saving state and there are quite a few things I don't understand - I'm writing an android app but I want to ask more general questions than how to do it specifically for the few activities etc I have at the moment, I would like to have a better overall view of how this works!
There are two groups of methods I have seen being used (I have seen one or two others but don't want to confuse myself even further...)
onPause, onResume etc,
and then the onSaveInstanceState ones.
What is the difference between them and the circumstances we should be looking to use them? I have seen some questions where a poster is using one of the normal life cycle callbacks, and is told to use onSaveInstanceState instead, so when should we be implementing onPause rather than onSaveInstanceState and so on. Some posts mentioned about methods being used for transient state only, could someone expand on that?
I have seen state being used to mean slightly different things - UI/View state and Activity state, what is the difference between the two?
I am also a bit unsure with what they mean by state, when we are saving state what kind of things are we saving exactly, could anyone give some quick examples (I don't mean actual code)? The android developer guides say that the android system automatically takes care of some of this, so what should we be concerned with? Bundle objects used by onCreate and onSaveInstanceState only store simple values, so what about more complex objects and arrays.
Thanks
protected void onPause ()
protected void onSaveInstanceState (Bundle outState)
Just by looking at it, onSaveInstanceState has an Bundle you can put your things you need to save in it. And get it back in onCreate(Bundle) or onRestoreInstanceState(Bundle);
Some important lines in the document:
This method is called before an activity may be killed so that when it
comes back some time in the future it can restore its state. Do not
confuse this method with activity lifecycle callbacks such as
onPause(), which is always called when an activity is being placed in
the background or on its way to destruction, or onStop() which is
called before destruction.
Android can destroy your activity or even kill your process at any given time (not likely when it is visible to the user though :-)). When the user navigates back to the activity, the data/info that was shown on the screen before he or she left it should be shown again.
The onSaveInstanceState callback allows you to do this.
Most of the Views already do this for you automatically. E.g. the current text in an EditText, the current scroll position of a ListView, etc. are all automatically saved for you.
However, there are some things that are not automatically saved for you. E.g. the current text in a TextView, the (changed) background drawable of a particular View.
Say, you show an error message after a user action fails. The error message is then shown in a TextField and this TextField's background becomes red (i'm just making this up here :-)). When the user leaves the activity while this error is shown (e.g. presses Home button), the activity is destroyed, the error message and the red background won't be shown again when the user comes back to the activity.
This is where onSaveInstanceState comes to the rescue.
You can save a String in there that containts the error message. Then when the activity is re-created, the Bundle savedInstanceState of the onCreate is not null and you can query it for the error message. If this message is not null/empty, call setText on the TextView for the error message and make that TextView's background red.
try to use this code to save state
#Override
protected void onSaveInstanceState(Bundle outState) {
State s = new State(yourTextView.getText().toString());
outState.putSerializable(State.STATE, s);
super.onSaveInstanceState(outState);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
State s = (State) savedInstanceState.getSerializable(State.STATE);
yourTextView.setText(s.getYourTextViewText());
}
i've been constantly frustrated by this and i can't find a good answer, so hoping someone here can offer guidance.
i have a fragment that uses AsyncTask quite extensively. i'm constantly plagued by bugs where the fragment calls getActivity(), which returns null. i assume these are happening because some method in the fragment are invoked before the activity is attached, or after it is detached.
what's the correct way to handle this in my code? i don't want to have this idiom littered all over the place,
Activity activity = getActivity();
if (activity != null) { // do something }
looking at the docs for Fragment, i can come up with many possible hooks to solve this: isDetached(), onActivityCreated(), onAttach(), isResumed(), and so on. what is the right combination?
EDIT:
A few people have suggested canceling tasks when paused, but this implies that the standard idiom,
new AsyncTask<...>.execute();
cannot be used. It implies that every exec'd AsyncTask needs to be tracked to completion, or canceled. I have simply never seen that in example code from Google or elsewhere. Something like,
private final Set<AsyncTask<?>> tasks = new HashSet<>;
...
AsyncTask<?> t = new AsyncTask<...>() {
...
public void onPostExecute(...) {
tasks.remove(this);
...
}
}
tasks.add(t);
t.execute();
...
#Override
public void onPause() {
for (AsyncTask<?> t: tasks) {
t.cancel();
}
tasks.clear();
}
Try to cancel your AsyncTasks in the onPause or onStop methods. That will prevent the onPostExecute from being called when the Fragment is not active anymore (getActivity() returns null).
Or you could check if the Fragment is attached by calling this.isAdded() in your Fragment.
I could not find a good solution. In summary, either use a Loader, or check that getActivity() does not return null before using it. I looked into using Loaders, but the pattern makes a lot of assumptions about the structure of the app and the nature of data retrieval that didn't work for me.
In terms of coordinating lifecycles, I keep onActivityCreated as a mental benchmark - it marks the point at which the underlying activity has finished its own onCreate. Prior to that I do not believe there is an activity to getActivity() from.
That get activity is returning null sounds like either you're calling getActivity() too early (i.e. before it is created) or too late (i.e. when it stopped interacting with the fragment). Stopping your tasks in onPause() would prevent getActivity from returning null since it would cut off the task once the fragment stopped interacting with the underlying activity becuase the activity itself was paused. I think waiting for onStop() may be too late since, if the task were to still be running when the underlying activity paused it may still reutrn null.
I recently converted my Activities to Fragments.
Using something similar to Tab-Navigation the fragments are replaced when the user selects another tab.
After the fragment is populated I start at least one AsyncTask to get some information from the internet. However - if the user switches to another tab just as the doBackground-method from my AsyncTask is being executed - the fragment is replaced and thus I am getting a NullPointerException in the marked lines:
#Override
protected Object doInBackground(Object... params) {
...
String tempjson = helper.SendPost(getResources().getText(R.string.apiid)); //ERROR: Fragment not attached
...
}
protected onPostExecute(Object result) {
...
getActivity().getContentResolver() //NULLPOINTEREXCEPTION
getView().findViewById(R.id.button) //NULL
...
}
getActivity() and getResources() causes an error because my Fragment is replaced.
Things I've tried:
Calling cancel method on my AsyncTask (won't fix first error nor the second error if the fragment is replaced while onPostExecute() is executed)
checking if getActivity() is null or calling this.isDetached() (not a real fix and I'd need to check it whenever I call getActivity() and so on)
So my question is: what would be the best to get rid of these AsyncTask problems? I did not have these problems using Activities as they weren't "killed" / detached on tab change (which resulted in higher memory usage - the reason why I like to switch to Fragments)
Since AsyncTask is running in the background, your fragment may become detached from its parent activity by the time it finishes. As you've found out, you can use isDetached() to check. There's nothing wrong with that, and you don't have to check every time, just consider the fragment and activity life cycles.
Two other alternatives:
Use Loaders, they are designed to play nicer with fragments
Move your AsyncTask loading to the parent activity and use interfaces to decouple from the fragments. The activity would know whether a fragment is there or not, and act accordingly (by possibly discarding the result if the fragment is gone).
Today I've faced the same problem: when I changed the fragment being displayed if the AsyncTask has not finished yet, and it tries to access the viewto populate it with some more elements, it would return a NullPointerException.
I solved the problem overriding one method of the fragments lifecycle: onDetach(). This method is called in the moment before the fragment is detached from the activity.
What you need to do is to call the cancel() method on your AsyncTask. This will stop the task execution avoid the NullPointerExecption.
Here's a sample of onDetach():
#Override
public void onDetach() {
super.onDetach();
task.cancel(true);
}
Check this page to get more information about fragments lifecycle:
http://developer.android.com/reference/android/app/Fragment.html#Lifecycle
And this to view more about Cancelling a task:
http://developer.android.com/reference/android/os/AsyncTask.html
Have you try calling setRetainInstance(true); in the onCreate() function of your fragment class?