Question

I couldn't find any solution to this problem that satisfies all my requirements.

In my application I use AsyncTasks to perform some operations like saving data to a memory or reading data from a database. I create a progress dialog in onPreExecute, update a progress value in onProgressUpdate and dismiss the dialog in onPostExecute.

Recently I switched to the Fragment API (I use the Support Library to target older versions of Android), meaning that my activities subclass FragmentActivity and dialogs subclass DialogFragment.

Switching to the Fragment API caused a well-known problem - sometimes I get the following exception:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

This happens, for instance, when a user starts a background operation (a progress dialog appears), uses the home button to minimize the application and the operation finishes when the activity is in background. The application then tries to dismiss the dialog and this fails since the state of the activity was saved.

I understand the issue. It can be fixed by ensuring that changes to the UI are postponed until the activity is resumed as described in this post: How to handle Handler messages when activity/fragment is paused.

However, this solution leads to another problem. What if the operation finishes when the activity is in background and later the activity is killed by Android? When a user navigates back to the application, it restores its state that was saved in onSaveInstanceState. Therefore, the progress dialog is still visible with the same progress value as when the activity was put into background. A message that should dismiss it, was never processed and it was lost when the activity was killed.

What is the correct solution that handles all the described issues properly? How to allow for changing the UI when the activity is in background or, at least, allow for postponing UI changes and ensuring they won't be lost in case the activity is killed by Android? The solution must allow for tracking progress of a background task.

Was it helpful?

Solution

The only sure-fire way to avoid the situation you've described is to persist the results to some sort of storage. You could start a service to do the long-running task, and have it write the results to a database, and also send a local broadcast when complete.

If the activity happens to be in the foreground, it will receive the broadcast and update its state to know that the process has finished, and could remove the information about the task from whatever persistent storage mechanism you use.

If the activity is in the background, the service will finish, send the broadcast, and persist the results. The next time the activity comes to the foreground, it should check this persistent storage and see if there's an unresolved task status, and update its state to match.

So, an example rundown might be (let's use "Uploading a Photo" as the task):

  1. User clicks a button to start an image upload

  2. Activity starts a service to upload the image to a remote service

  3. Activity registers a LocalBroadcastManager for some broadcast event that you define (e.g. com.mypackage.ACTION_PHOTO_UPlOADED)

  4. Service persists some information about the task (e.g. SharedPreferences.putBoolean("TASK_PHOTO_UPLOADED", false)

  5. Activity shows some sort of loading UI while the service processes the upload task

  6. User presses home and sends the app into stopped state

  7. Activity unregisters the local broadcast manager

  8. The service finishes uploading the image

  9. The service sends a local broadcast (com.mypackage.ACTION_PHOTO_UPlOADED) which is unused because the Activity is no longer listening

  10. The service updates the persisted information (SharedPreferences.putBoolean("TASK_PHOTO_UPLOADED", true))

  11. Android kills off the Activity, saving its state

  12. User later returns to the Activity

  13. Activity checks in onResume() with the service to see if there's an unhandled task result (if (SharedPreferences.has("TASK_PHOTO_UPLOADED"))), then checks to see if it should updated the state.

  14. If the photo upload key is there, and is true, Activity removes the dialog fragment and clears the TASK_PHOTO_UPLOADED key from SharedPreferences.

    If the photo upload key is there, and is false, Activity registers a local broadcast manager to wait for the event to complete

OTHER TIPS

  • Your Question was lengthy without any code sample, ill try to come up with my best answer, I have made a project just for you on how you should use onSaveinstance state in android which will solve all the possible problems

  • I have written an explanation essay below that will clear all your doubts


In android Life-Cycle and dealing with Fragments

  • Activity has the container for the fragments

  • Mount the first fragment from the oncreate of Activity

  • When we are using fragments to add replace etc. we need to use android fragment reference instead of classreference

  • Keep the fragments in the backstack every time we mount the fragment to the container

  • In the project we can observe that we are collecting all the states of widgets on onpause() and stored in a local variable and using this local variable, then pass this local variable into onSaveInstance() event access these tags in onActivityCreatd() are set to the view objects. We use this process because localvariables are holded in the class but view objects are null in onSaveInstance. This perticular observation is very important observed in Activity-FragmentOne-FragmentTwo-FragmentOne-OrientationChange-OrientationChange

  • Android static objects are able to retain thae state onOrientation change but dynamic Objects has to be resetted onOrientation change with the value using localvariables as explained above

  • If we are using the dynamic fragments we have the create the dynamic objects in OnActivityCreated() event before using the saveInstanceState to restore the dynamic objects

  • The fragment is added to the backstack on the onPause event

  • If the screen navigation is Activity-FragmentOne-FragmentTwo-FragmentOne-FragmentTwo then on press of the back button navigation works as FragmentTwo-FragmentOne-FragmentTwo-FragmentOne-Activity, thus we can clearly observe our path is being kept in track by the android backstack

  • If the path is Activity-FragmentOne and change the orientation for the first time then the events fired are as follows MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume

  • If the path is Activity-FragmentOne-orientationchange and change the orientation for the first time then the events fired are as follows

MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume

  • If the path is Activity-FragmentOne-orientationchange-orientationchange and change the orientation for the first time then the events fired are as follows

MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume

  • If the path is Activity-FragmentOne-orientationchange-orientationchange-FragmentTwo and change the orientation for the first time then the events fired are as follows

MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onStop- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentTwo-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume

  • If the path is Activity-FragmentOne-orientationchange-orientationchange-FragmentTwo-orientationchange and change the orientation for the first time then the events fired are as follows

MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onStop- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentTwo-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentTwo-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume


Summarising::

  • Now remember onPause Event is always is fired just before closing a fragment store the values that you want to preserve onOrientation, when phonecall comes, any other scenarios

  • Next onSaveInstanceState will be executed so use those local variables to set here to set data in bundle

  • Next the activity will be pushed to backstack your data will not be lost on clicking home button

  • So when onActivityCreated get the data from bundle' and store it back to yourviews`

  • Also remember android always completes the full lifecycle when destroying the fragment, if it is sent to backstack fragment wont be destroyed(when you press home button) android destroys only when it needs more space from backstack and it happens automatically


Let me know if you need any more information

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top