Is there a way to find out if a fragment is being permanently destroyed, i.e. that it won't be recreated in the future?
I need this to know when should I destroy a network task associated with the fragment.
Something that should work is to call getActivity().isChangingConfigurations() in the fragment's onDestroy().
This will all depend on how you have created and set up you fragment(s). An easy way to check to see if your fragment(s) is being permanently destroyed is to add a toast or some other sort of notification/flag that you can check in the onDestroy() method of the fragment. If you see the toast of if the flag is set then you know that the fragment was destroyed.
In your fragment, override onDestroy and cancel your AsyncTasks or other operations then
AsyncTask task;
#Override
public void onDestroy() {
if (task != null && !task.isCancelled()){
task.cancel(true);
task = null;
}
super.onDestroy();
}
If the task is associated with a fragment, it should be destroyed in the fragment's onDestroy method. The system assumes that all of the fragment's resources have been cleaned up once that method returns, so you should just go ahead and do the required clean-up.
If the fragment is re-created in the future, it will be a whole new fragment so will presumably require a whole new network task anyway.
EDIT:
If you want work to continue in the background when an activity is destroyed and re-created due to a configuration change (such as a screen rotation), you can set up your fragment to not be destroyed. You need to do two relatively simple things:
Define your fragment to have no UI (so that it has nothing that needs to be cleaned up when the activity is destroyed).
Call setRetainInstance(true) for the fragment to prevent the fragment from being destroyed along with the activity. When the activity is re-created after a configuration change, the retained fragment will be reattached. Meanwhile it can continue the background processing.
Related
I am making an Activity that logs a message when the activity is destroyed due to orientation change.
What I want to do is to call that Log.d() UPON the moment the activity is destroyed.
In other words, I don't want to call it by checking savedInstanceState==null after the activity is recreated.
Is there a way to know why the activity is destroyed before I reach onDestroy()? Thanks
You can use isChangingConfigurations() from the docs:
Check to see whether this activity is in the process of being destroyed in order to be recreated with a new configuration.
Docs available here
If I understood correctly you want to log something before destroying activity.
According to the lyfecyle activity diagram you should do that at this point.
In your activity maybe you have to overwrite onDestroy method and call log before the super call.
#Override
public void onDestroy() {
//log
Log.d()
super.onDestroy();
}
EDIT:
Juan is right, as the docs mentions:
"There are situations where the system will simply kill the activity's hosting process without calling this method (or any others) in it, so it should not be used to do things that are intended to remain around after the process goes away."
https://developer.android.com/reference/android/app/Activity.html#onDestroy%28%29
I have an Activity that creates a retained Fragment in order to persist a long running network operation in the event that the Activity goes through any configuration changes during the long running operation. So basically I'm trying to use the retained Fragment like a Singleton that lives until Activity's onDestroy() is called.
My goal is to have Activity's onCreate() trigger the long running network operation because I only want it to run when the activity is created not every time it starts again (otherwise I'd put it in onStart()).
To do this I first create a retained fragment in Activity's onCreate, then use FragmentManager to add the retained fragment then I kick off the network call in Activity's onCreate method and pass the networking object to the retained Fragment to hold onto.
This works, however I'm concerned because if I log what's going on I can see that the Activity first sets data on the retained Fragment and then the retained Fragment's onCreate() method is called. This looks wrong and seems out of order, however it works.
Is it bad practice to utilize the retained Fragment instance before the fragment has run its onCreate() method?
EDIT
After reading the responses and thinking a bit more about this, I'm realizing that initiating the network call from Activity onCreate() although convenient, is risky to do. As noted in the responses there's a chance that the long running operation returns very quickly and attempts to manipulate the Activity's view which may not have been initialized yet. So for my specific case I am resorting to initiating the long running operation from Activity's onStart() method and then using the retainedFragment to cache the response. This way even if onStart() is called multiple times and attempts to kick off the long running operation again, the result from the first attempt will be cached and can be returned.
I will admit that's unusual, but I can't think of a reason off the top of my head why it would be bad. The advantage of a retained Fragment is the reference to the Fragment is not destroyed so the references it holds on to are also retained. onCreate() and onDestroy() are called once throughout the lifetime of the Fragment (when the Fragment is added and removed respectively).
The danger might be that the asynchronous operation finishes before onCreate() is called. Likewise, the operation could finish after onDestroy() is called and when you expected the Fragment to be running. There are some methods like Fragment#setArguments() that can not be called during some parts of the Fragment's lifecycle. If you were to call these methods when you expected the Fragment to be running, then you will get an Exception thrown. So you end up having to put in a bunch of checks like if(isAttached()) { /* do this */ }. Putting the operation in onCreate() will ensure that it at least started before the operation finished.
But if you are not actually relying on any of the Fragment's functionality, then it should be fine. The lifecycles are only meant to tell you what's going on with it.
As long as you are not relying on Activity/Fragment views being available while Fragment's onCreate, you will be fine:
Note that this can be called while the fragment's activity is still in the process of being created. As such, you can not rely on things like the activity's content view hierarchy being initialized at this point.
(https://developer.android.com/reference/android/app/Fragment.html#onCreate(android.os.Bundle)
Still, you need to secure possible edge case that long-running operation may be finished before Activity and Fragment are fully created (if you depend on their views, this might be an issue).
Therefore, think about what you will do when long-running operation has finished and the results need to be presented somewhere.
Fragments are recreated automatically when their parent Activity or Fragment is recreated. If the child fragments were related to state that is not retained between instances of their parent, when should I remove them?
In the parent fragment's onDestroy(): unreliable since onDestroy() might not be called.
In the parent fragment's onCreate(): presumably the children have not yet been created at this point.
Some other lifecycle method that is guaranteed to be called after the children have been recreated and added. Is onViewStateRestored(...) the right place for this?
In case my question isn't clear, here's an example:
An Activity has a Fragment which contains an asynchronous operation. The fragment would normally cancel this task in onDestroy(). But if the fragment is destroyed without onDestroy() being called, it may later find itself recreated with the background task uninitialized. In that case, it should remove its old progress dialog. When should it test for this condition?
Edit: When the user swipes the app out of recents, all its components are destroyed without calls to onDestroy(). But in that case, the fragment hierarchy is apparently obliterated along with the rest of the app. When the app is restarted, the fragment is not automatically recreated, so I don't have to worry about removing it.
When the fragment is destroyed because its host activity is put in the background and "don't keep activities" is turned on, the fragment is automatically recreated. But in that case, it seems I can count on onDestroy() being called.
My concern is what happens when the app is killed to free memory. Hopefully it will behave like swiping from recents, where the fragment hierarchy is not restored. That would render my whole question moot. Can anyone confirm what happens in that case?
The docs seem to indicate onpause will always be called
https://developer.android.com/guide/components/fragments.html
or if you know the activity is going to be destroyed onDetach() must be called since if the activity is destroyed the fragment can't be attached.
I feel like this should have been answered by the documentation in the Activity class, but I'm still not positive -- when is it actually safe to update the UI of an Activity (or a Fragment)? Only when the activity is resumed, or any point between being started and stopped?
For example, the Activity docs state:
The visible lifetime of an activity happens between a call to onStart() until a corresponding call to onStop(). During this time the user can see the activity on-screen, though it may not be in the foreground and interacting with the user. Between these two methods you can maintain resources that are needed to show the activity to the user. For example, you can register a BroadcastReceiver in onStart() to monitor for changes that impact your UI, and unregister it in onStop() when the user no longer sees what you are displaying. The onStart() and onStop() methods can be called multiple times, as the activity becomes visible and hidden to the user.
From that reading, I would assume that even if a foreground dialog is displaying, I can safely update UI elements behind it.
EDIT
To clarify: the reason I ask is having been bitten by errors when firing off an AsyncTask, and attempting to update the UI in the onPostExecute method. Even though it runs on the UI thread, the user has navigated away from that view and I would receive an exception. I'm starting a new project now and trying to establish some guidelines around better AsyncTask idioms.
I guess this comes down to what you mean by "safe" to update the UI. For elements on the Activity screen, they can be updated whenever, even if your Activity is not in the foreground (but make sure to update from the UI Thread).
However, the issue that will trip you up is saving state: onSaveInstanceState.
As you may know, the Activity that is in the background may be destroyed by the OS to free up memory. It will then be re-created when you come back to it. During this process, the onSaveInstanceState method will be called. If the OS does destroy the Activity, any changes you made to the UI State after the call to onSaveInstanceState will not be persisted.
For Fragments, you will actually get an IllegalStateException if you try to commit a FragmentTransaction after onSaveInstanceState. More info on that.
To summarize, you can update the UI of your activity at any point and try to gracefully handle the Fragment issues, but you may lose these updates when the Activity is restored.
So, you could say that it is only truly safe to update the Activity while it is in the foreground, or rather, before the call to onSaveInstanceState.
Edit in regards to Async Task onPostExectue
This is likely related to the issue I am referring to above with Fragment State Loss. From the blog post I linked to:
Avoid performing transactions inside asynchronous callback methods. This includes commonly used methods such as AsyncTask#onPostExecute() and LoaderManager.LoaderCallbacks#onLoadFinished(). The problem with performing transactions in these methods is that they have no knowledge of the current state of the Activity lifecycle when they are called. For example, consider the following sequence of events:
An activity executes an AsyncTask.
The user presses the "Home" key, causing the activity's onSaveInstanceState() and onStop() methods to be called.
The AsyncTask completes and onPostExecute() is called, unaware that the Activity has since been stopped.
A FragmentTransaction is committed inside the onPostExecute() method, causing an exception to be thrown.
I want to use setRetainInstance(true) on my FragmentActivity so that onCreate() will not be called every time the screen rotates. I just want to adjust the layout to the screen adjusting without reestablishing my location services connection and notifying the user. How should this be done?
Start by reading the Fragment documentation about what setRetainInstance does (And does not).
In summary:
public void setRetainInstance (boolean retain)
Added in API level 11 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.
With that in mind, make sure your "location services connection" and such are located in a place where they will not die (they probably shouldn't be in either Activity or Fragment anyway).
This paragraph is taken from a book I'm currently reading "Professional Android 4 Application Development"
If an Activity is destroyed and restarted to handle a hardware
configuration change, such as the screen orientation changing, you can
request that your Fragment instance be retained. By calling
setRetainInstance within a Fragment’s onCreate handler, you specify
that Fragment’s instance should not be killed and restarted when its
associated Activity is re-created.
I think that's clear enough.
Regards!