Android Fragments. Retaining an AsyncTask during screen rotation or configuration change
Fragments can actually make this a lot easier. Just use the method Fragment.setRetainInstance(boolean) to have your fragment instance retained across configuration changes. Note that this is the recommended replacement for Activity.onRetainnonConfigurationInstance() in the docs.
If for some reason you really don't want to use a retained fragment, there are other approaches you can take. Note that each fragment has a unique identifier returned by Fragment.getId(). You can also find out if a fragment is being torn down for a config change through Fragment.getActivity().isChangingConfigurations(). So, at the point where you would decide to stop your AsyncTask (in onStop() or onDestroy() most likely), you could for example check if the configuration is changing and if so stick it in a static SparseArray under the fragment's identifier, and then in your onCreate() or onStart() look to see if you have an AsyncTask in the sparse array available.
Best practice: AsyncTask during orientation change
Do NOT use android:configChanges
to address this issue. This is very bad practice.
Do NOT use Activity#onRetainNonConfigurationInstance()
either. This is less modular and not well-suited for Fragment
-based applications.
You can read my article describing how to handle configuration changes using retained Fragment
s. It solves the problem of retaining an AsyncTask
across a rotation change nicely. You basically need to host your AsyncTask
inside a Fragment
, call setRetainInstance(true)
on the Fragment
, and report the AsyncTask
's progress/results back to it's Activity
through the retained Fragment
.
How to handle an AsyncTask during Screen Rotation?
My first suggestion would be to make sure you actually need your activity to be reset on a screen rotation (the default behavior). Every time I've had issues with rotation I've added this attribute to my <activity>
tag in the AndroidManifest.xml, and been just fine.
android:configChanges="keyboardHidden|orientation"
It looks weird, but what it does it hand off to your onConfigurationChanged()
method, if you don't supply one it just does nothing other than re-measure the layout, which seems to be a perfectly adequate way of handling the rotate most of the time.
ASyncTask, hidden fragments, retaining instances, and screen orientation changes
There are a few issues here that you're experiencing (I think).
First of all, the reason your callbacks crash is because they're attached to an old Activity
that no longer "exists" after a screen orientation and/or Activity
push. If you use onAttach()
to attach a callback to your fragment, you must use onDetach()
to detach that callback when the Fragment is removed from the Activity
. Then, whenever you call the callback, check for a null so you don't send data to a dead object.
Basically, the logic you're trying to use here is:
Start Activity.
Check if your Fragment exists. If it does, grab it. Else, create it.
Retrieve the data if it exists. If not, wait for the callback.
Because of the nature of callbacks (depending on your implementation), you will not receive data until the event fires. However, if the Activity is gone and the event has already fired, the callback won't execute. Thus, you have to retrieve the data manually. When using setRetainInstance()
, it's helpful to think of it as this entity detatched from your Activity. It will exist as long as you don't pop the current Activity
or push a new Activity
. However, your current Activity
will be destroyed upon screen orientation changes while the Fragment
won't. As such, the Fragment
shouldn't rely on the existence of the Activity
.
A much more elegant solution to the problem that you may want to look in to is implementing the Android Loader API. Loaders are handy tools that are handled by the system that work is roughly the same way but are more in-tune with asynchronously retrieving data. They work effectively the same way. You simply start your loader and the system with either create one if it doesn't exist or re-use one that already exists. It will remain in the system by the LoaderManager
upon configuration changes.
EDIT:
To answer your edit, I guess I'll explain what's happening. It's convoluted, so just tell me if anything needs clarification.
Fragments aren't technically speaking part of your currently running Activity
. When you create an instance of the Fragment, you have to call beginTransation()
and commit()
on the FragmentManager. The FragmentManager is a singleton that exists within the realm of your application. The FragmentManager holds on to the instance of the Fragment
for you. The FragmentManager then attaches the Fragment to your Activity (see onAttach()
). The Fragment then exists within the FragmentManager which is why you never really have to hold a reference to it within your application. You can just call findFragmentByTag/Id()
to retrieve it.
Under normal circumstances, when your Activity is being destroyed, the FragmentManager will detach the instance of your Fragment (see onDetach()
) and just let it go. The Java garbage collect will detect that no reference to your Fragment exists and will clean it up.
When you call setRetainInstace()
, you're telling the FragmentManager to hold on to it. Thus, when your Activity is being destroyed on a configuration change, the FragmentManager will hold on to the reference of your Fragment. Thus when your Activity is rebuilt, you can call findFragmentByTag/Id()
to retrieve the last instance. So long as it didn't keep any context of the last Activity, there shouldn't be any problems.
Traditionally, one would use it to keep references to long standing data (as you are) or to keep connection sockets open so a phone flip doesn't delete it.
Your ViewPager has nothing to do with this. How it retrieves the Fragments is completely dependent on how you implement that Adapter that it's attached to. Usually, retained Fragments don't have Views themselves because Views hold Context data of the Activity they were created in. You would just basically want to make it a data bucket to hold on to the data for the Views to pull from when they're being inflated.
How prevent rotation until AsyncTask is not ended
Its not possible to pause screen rotation. You can only stop it entirely using configChanges in your activity manifest entry (but that is bad practice). What you should do is to put your async task in retained fragment. Until recently you could use Activity.getLastNonConfigurationInstance and Activity.onRetainNonConfigurationInstance to keep reference to AsyncTask between Activity being destroyed and recreated but now its deprecated. But you can still use it.
read here for more information: http://developer.android.com/reference/android/app/Activity.html#onRetainNonConfigurationInstance()
Also:
if I try to rotate the device during AsincTask the App crashes
this actually should not happen, it is possible that you keep reference to your Activity in AsyncTask and use it after it is destroyed. This is called reference leak. To avoid it keep reference to your Activity in WeakReference, also if your AsyncTask is an inner class, then make it static. If it is possible, destroy your asynctask in Activity.onDestroy - by cancelling it, in async task check if it is cancelled and stop processing. If you use it to download things then consider retained fragment or IntentService.
How to handle a running AsyncTask during orientation/configuration change?
According to this page : http://developer.android.com/guide/topics/manifest/activity-element.html#config, if your application targets API level 13 or higher, you should use android:configChanges="orientation|screenSize"
to catch the screen rotation into the onConfigurationChanged()
method.
By doing this, the onPause
and onResume
methods are not called, so your TextView will keep its value. And in my mind, it won't stop the running AsyncTask. If it does, try to recall it in the onConfigurationChanged
method.
avoid restart fragment asynctask on screen orientation change
You may find some hack (including muting the configuration changes or retaining the instance) to somehow get it work, but really AsyncTask
s 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 Loader
s 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.
How prevent rotation until AsyncTask is not ended
Its not possible to pause screen rotation. You can only stop it entirely using configChanges in your activity manifest entry (but that is bad practice). What you should do is to put your async task in retained fragment. Until recently you could use Activity.getLastNonConfigurationInstance and Activity.onRetainNonConfigurationInstance to keep reference to AsyncTask between Activity being destroyed and recreated but now its deprecated. But you can still use it.
read here for more information: http://developer.android.com/reference/android/app/Activity.html#onRetainNonConfigurationInstance()
Also:
if I try to rotate the device during AsincTask the App crashes
this actually should not happen, it is possible that you keep reference to your Activity in AsyncTask and use it after it is destroyed. This is called reference leak. To avoid it keep reference to your Activity in WeakReference, also if your AsyncTask is an inner class, then make it static. If it is possible, destroy your asynctask in Activity.onDestroy - by cancelling it, in async task check if it is cancelled and stop processing. If you use it to download things then consider retained fragment or IntentService.
Related Topics
Callback to a Fragment from a Dialogfragment
How to Set Opacity (Alpha) for View in Android
Android New Bottom Navigation Bar or Bottomnavigationview
How to Properly Highlight Selected Item on Recyclerview
How to Get the Current Location Latitude and Longitude in Android
Android: Keeping a Background Service Alive (Preventing Process Death)
Android Studio Does Not Show Layout Preview
Where Is Android.Os.Systemproperties
Where Is the Layout Preview in Android Studio
Android Facebook Sdk 4 in Eclipse
Calling Startactivity() from Outside of an Activity Context
Formula Px to Dp, Dp to Px Android
Android Studio - Local Path Doesn't Exist