Home > UI Handling > Handling progress dialogs and orientation changes

Handling progress dialogs and orientation changes

ProgressDialogs in Android are easy. At least, that was my initial thought. As long as you don’t change the screen orientation that is. Once you start moving your phone around after – or worse, during – an active progress dialog, you’ll soon find that out that your application isn’t behaving the way it should.
Luckily for us, there are several ways to tackle this.I’ve created a sample application, available on GitHub outlining the problem, and some solutions of dealing with it gracefully. The repository is located here.

When starting the application, you’ll get the following screen :

progress dialog sample app home screen

progress dialog sample app home screen

Each button launches an activity that populates a listview. We’ve simulated a long running operation by introducing a SystemClock.sleep(LONG_RUNNING_TIME). The goal of each Activity is to populate a list while informing the user that populating that list might take a while. This is accomplished by presenting the user with a progress dialog.

The app allows you to launch the following activities.

  • AllOnUIThread
    A sample where a progress dialog is created and dismissed on the UI thread. Added to illustrate that the progress dialog will never be shown.
  • AsyncTaskSimple
    Implementation using an AsyncTask, where the progress dialog will be shown, but causes problems during screen orientation.
  • AsyncTaskSimpleStateSaving
    Implementation based on the previous AsyncTask, but now with a simple state saving mechanism, again causing problems during screen orientation.
  • AsyncTaskSimpleConfigured
    Implementation based on android:configChanges=”keyboardHidden|orientation”, where the activity is not re-created after a screen orientation change in order to handle screen orientations gracefully.
  • AsyncTaskComplex
    A more complex implementation that handles screen orientations gracefully without resorting to the android:configChanges “fix”.

We’re going to see how these activities behave in general, and more specific when the screen changes orientation.

1. Doing everything on the main UI thread

The topic of dialogs is outlined in the Android Developer SDK Dev Guide. Although it mentions how to create different kind of dialogs, it doesn’t explicitly mention where these dialogs should be created, shown and dismissed. There is a brief topic on threads, but it doesn’t really stand out in the article.

So, the first time I tried to create a dialog, I did it like this :

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
    	showDialog(LOADING_DIALOG);
    	this.listItems = retrieveListLongRunning();
    	refreshListView();
    	dismissDialog(LOADING_DIALOG);
	}

To my surprise, no dialog was showing on the screen (as can be verified using the sample app). Although the dialog got created and dismissed, I never got to see it on my screen. The reason for this is is the fact that the code you’re seeing here is executed on the main UI thread. This thread is responsible for keeping the Activity alive, and allowing the user to interact with it. As a rule of thumb, you should never do long running tasks here, as they basically block up the UI, and can cause an application to go unresponsive. Android even has a built-in mechanism to warn the user that an application has gone unresponsive, by showing the following dialog :

android unresponsive dialog

android unresponsive dialog

Conclusion here is that want to avoid this dialog from popping up at all cost. The reason our progress dialog wasn’t showing up was due to the fact that we told it to show up while the main UI thread was busy doing other things.

2. Using an Async Task to do the heavy lifting.

Android has the concept of an AsyncTask, where background tasks can be executed in order to avoid the UI to become unresponsive. It does so by spawning a background thread where that background task is executed. Implementing an AsyncTask is fairly easy, and a common implementation will look like this :

	/**
	 * Our background task split up into 3 sections
	 *
	 * onPreExecute - showing the dialog in the UI thread (just before doInBackground is called).
	 * doInBackground - launches a seperate thread to refresh the list data
	 * onPostExecute - dismisses the dialog and refreshes the list control.
	 *
	 */
	private class ListRefresher extends AsyncTask<Uri, Void, Void> {

		/**
		 * This is executed in the UI thread. The only place where we can show the dialog.
		 */
		@Override
		protected void onPreExecute() {
			showDialog(LOADING_DIALOG);
		}

		/**
		 * This is executed in the background thread.
		 */
		@Override
		protected Void doInBackground(Uri... params) {
			refreshListLongRunning();
			return null;
		}

		/**
		 * This is executed in the UI thread. The only place where we can show the dialog.
		 */
	   @Override
       protected void onPostExecute(Void unused) {
    	   dismissDialog(LOADING_DIALOG);
    	   refreshListView();
       }
	}

As can be seen in the included JavaDoc, there are 3 main hooks that we get when using AsyncTasks

  • onPreExecute
  • doInBackground
  • onPostExecute

The first and last hook are executed on the main UI thread. This is the place where we’ll respectively show and dismiss the dialog. In between (doInBackground), we’ll do the heavy lifting in the background thread that is provided by the AsyncTask.

A good way to illustrate (and comprehend) is by using a debugger. Put a breakpoint in these 3 methods.

When the onPreExecute breakpoint is suspended, we can see that it was called on the main UI thread (identified by thread nr 3 – main).

onPreExecute breakpoint on main UI thread

onPreExecute breakpoint on main UI thread

When the breakpoint on the doInBackground is suspended in the debugger, we can see that it is not in main UI thread, but in a seperate thread provided to us by the AsyncHandler ((identified by thread nr 25 – AsyncTask #5)

doInBackground called in background thread

doInBackground called in background thread

Our main UI thread is basically sitting idle, leaving it with plenty of  room to show the progress dialog. This wasn’t the case in our first example, as the UI thread was also blocked as it was busy retrieving the items for the list.

Once the background processing is ended, our third hook will be called, onPostExecute, back on the main UI thread.

onPostExecute on main UI thread

onPostExecute on main UI thread

So far so good…. with this approach, we’re able to show the progress dialog before the background processing starts, and dismiss it when the background processing has finished.

BUT, there is one big catch though …. if we change the orientation of our phone after the data is initially shown, we’ll start to see the strange behavior I mentioned in the introduction.

What happens is that during the screen orientation our Activity is re-created, the onCreate method is called again, triggering the execution of the AsyncTask. After the screen orientation, our Activity shows an initial empty list, followed by our progress dialog. Our list gets refreshed in the background, but what we’re seeing is that the progress dialog remains on the screen. So even after dismissing it (during the onPostExecute), the dialog remains on the screen.

To make matters worse, when changing the orientation multiple times in a row, your application will eventually crash with the following exception :

java.lang.IllegalArgumentException: no dialog with id 0 was ever shown via Activity#showDialog
     at android.app.Activity.missingDialog(Activity.java:2521)
     at android.app.Activity.dismissDialog(Activity.java:2511)
     at com.ecs.android.listview.sample.AsyncTaskSimple$ListRefresher.onPostExecute(AsyncTaskSimple.java:92)
     at com.ecs.android.listview.sample.AsyncTaskSimple$ListRefresher.onPostExecute(AsyncTaskSimple.java:1)
     at android.os.AsyncTask.finish(AsyncTask.java:417)
     at android.os.AsyncTask.access$300(AsyncTask.java:127)
     at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:429)
     at android.os.Handler.dispatchMessage(Handler.java:99)
     at android.os.Looper.loop(Looper.java:123)
     at android.app.ActivityThread.main(ActivityThread.java:4363)
     at java.lang.reflect.Method.invokeNative(Native Method)
     at java.lang.reflect.Method.invoke(Method.java:521)
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
     at dalvik.system.NativeStart.main(Native Method)

By changing the orientation multiple times, a race condition occurs where a certain Activity will try and dismiss the dialog that was created by the previous Activity. This s a result of the fact that during screen orientation changes, the current Activity is destroyed, and a new Activity is created.

One option of dealing with this is to avoid refreshing the data after a screen orientation change by properly saving your activity state.

3. Using an Async Task with state saving.

So everytime the screen orientation changes, Android will re-creates the activity, meaning that the Activity will go through the entire lifecycle again. This means that the onCreate method will be executed again. Android provides a method called onRetainNonConfigurationInstance to help us deal with this situation.

There are 3 important things we need to know about this method :

  • Called between onStop() and onDestroy().
  • A new instance of the activity will always be immediately created after this one’s onDestroy() is called.
  • The object you return here will always be available from the getLastNonConfigurationInstance() method of the following activity instance as described there.
	/**
	 * Cache the already fetched listItems.
	 * This will be picked up in the onCreate method.
	 */
    @Override
    public Object onRetainNonConfigurationInstance() {
    	return listItems;
    }

During the onCreate, we can check for saved state, and use that instead of refreshing the values again. Basically, after changing the screen orientation, the retained object will contain our list items, so instead of launching the AsyncTask again, we can simply capture the previous list items from the getLastNonConfigurationInstance().

	/**
	 * Intelligent onCreate that takes into account the last known configuration instance.
	 * When an activity is destroyed (as a result of a screen rotate), the onRetainNonConfigurationInstance
	 * method is called, where we can return any state that we want to restore here.
	 */
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		Object retained = getLastNonConfigurationInstance();
		if (retained==null) {
			new ListRefresher().execute();
		} else {
			this.listItems = (List<Map<String, String>>) retained;
			refreshListView();
		}
	}

The code above is a simple state saving mechanism, that only saves our listItems. Again, problems will arise when changing the screen orientation.

  • Changing the screen orientation after the asynctask has completed, won’t cause any issues, as our listItems have already been retrieved and can be restored by the method above. The screen will change orientation and the same items will be shown. The progressdialog won’t get triggered because the AsyncTask completed.
  • When changing the screen orientation during the asynctask execution we’ll get the same result as the previous sample without state saving. Our progress dialog remains on the screen forever

4. Configuring the Activity to not re-create itself after screen orientation change

By now it should be clear that when the screen is oriented, the Activity is re-created, meaning that the old one is destroyed, and a new instance is created. As a consequence of this behavior, we need to ensure that our onCreate method is always implemented properly. Long running operations, and heavy initialization should not be done in the onCreate, as each time the user will change the screen orientation, this method will be executed. Usually, when the user changes his screen orientation, he does so because he might feel that a landscape / portrait mode is more suitable to view the current activity. He basically wants a new screen layout, but what he doesn’t necessarily want is that his Activity is re-created or re-initialized.
Android provides an option for this, by configuring the activity like this in the manifest file.

        <activity android:name=".AsyncTaskSimpleConfigured"
        		 android:configChanges="keyboardHidden|orientation"
                  android:label="AsyncTaskSimpleConfigured"/>

What this does is that it allows the developer to listen for these configChanges, and act on them appropriately by implementing the following method :

@Override
public void onConfigurationChanged(Configuration newConfig) {
  super.onConfigurationChanged(newConfig);
  setContentView(R.layout.myLayout);
}

When running the sample “Using AsyncTask Manifest Configured”, we see that the progress dialog is show properly, and that the Activity survives all screen orientations. The AsyncTask keeps running in the background, and informs the UI that it has finished it work, refreshing the listview and dismissing the dialog. The onCreate method isn’t called during the screen orientation change.

5. Implement a more intelligent AsyncTask dealing with screen orientation

There are several reasons why you wouldn’t want to implement the solution above. Many people seem to consider this more of a hack then anything else, and even Google is not in favor of this approach. An alternative is to implement more robust logic inside your Activity / AsyncTask to gracefully handle the situation where a screen rotate occurs.
This solution was discussed at length in the Android developers forum, and I’ve provided a working example in my github repository based on that solution in the sample application. The idea here is that we associate our Activity with our AsyncTask. In the Commonsguy github repository you’ll find a similar example with a progress bar.
In the AsyncTask implementations above, the AsyncTask is completely decoupled from the Activity, meaning that the AsyncTask has no reference to the Activity, and as such is unaware what Activity has launched it. Obviously, Android internally is aware of this, but this isn’t exposed to the developer. In this solution, we’ll create a reference to the Activity in the AsyncTask by passing the Activity in the constructor of the AsyncTask.

We’ll also expose a flag to the activity that indicates if the task has completed, and if the progress dialog is visible on the screen. These 3 items (the reference to the Activity from within the AsyncTask, and the 2 flags) allow us to gracefully handle a screen orientation.

Our new AsyncTask looks like this :

    /**
     * Our complex async task that holds a reference to the Activity that started it,
     * and a boolean to determine if the task completed.
     */
	private class ListRefresher extends AsyncTask<Uri, Void, Void> {

		 private AsyncTaskComplex activity;
         private boolean completed;

         private List<Map<String, String>> items = new ArrayList<Map<String, String>>();

         private ListRefresher(AsyncTaskComplex activity) {
                 this.activity = activity;
         }

         public List<Map<String, String>> getItems() {
			return items;
		}

        /**
         * Showing the dialog on the UI thread.
         */
		@Override
		protected void onPreExecute() {
			activity.showDialog(LOADING_DIALOG);
		}

        /**
         * Performing the heavy lifting in the background thread thread.
         */
		@Override
		protected Void doInBackground(Uri... params) {
			items = retrieveListLongRunning();
			return null;
		}

		/**
		 * When the task is completed, notifiy the Activity.
		 */
        @Override
        protected void onPostExecute(Void unused) {
                completed = true;
                notifyActivityTaskCompleted();
        }

        private void setActivity(AsyncTaskComplex activity) {
                this.activity = activity;
                if ( completed ) {
                	notifyActivityTaskCompleted();
                }
        }

        /**
         * Helper method to notify the activity that this task was completed.
         */
        private void notifyActivityTaskCompleted() {
                if ( null != activity ) {
                	activity.onTaskCompleted();
                }
        }
	}

Important to note is that

  • The AsyncTask has a reference to the Activity that created it.
  • The Activity is associated to the AsyncTask using the constructor when the activity is first created, and through a setter when the activity is re-created after a screen orientation change.
  • The AsyncTask has a flag indicating if it completed execution.
  • The AsyncTask can notify the Activity when the task is completed.

When performing a screen orientation change, we make sure that our AsyncTask is saved through the following method (in order for it to be picked up again in the onCreate method.
We also remove the association with the current activity at this point, as it is in the process of being destroyed.

	/**
	 * After a screen orientation change, this method is invoked.
	 * As we're going to state save the task, we can no longer associate
	 * it with the Activity that is going to be destroyed here.
	 */
    @Override
    public Object onRetainNonConfigurationInstance() {
            mTask.setActivity(null);
            return mTask;
    }

In our Activity we have the following onCreate method

    /**
     * After a screen orientation change, we associate the current ( = newly created) activity
     * with the restored asyncTask.
     * On a clean activity startup, we create a new task and associate the current activity.
     */
	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Object retained = getLastNonConfigurationInstance();
        if ( retained instanceof ListRefresher ) {
                Log.i(TAG, "Reclaiming previous background task.");
                mTask = (ListRefresher) retained;
                mTask.setActivity(this);
        } else {
                Log.i(TAG, "Creating new background task.");
                mTask = new ListRefresher(this);
                mTask.execute();
        }
    }

When the screen orientation changes, the getLastNonConfigurationInstance() method returns our AsyncTask. We now need to associate the current Activity with this task (as the previous Activity was destroyed due to the screen orientation change). Keep in mind that during the screen orientation, the AsyncTask keeps on processing (doInBackGround).

When the AsyncTask finishes its work, it will notify the activity by calling its onTaskCompleted() method. That method is implemented like this :

	/**
	 * When the aSyncTask has notified the activity that it has completed,
	 * we can refresh the list control, and attempt to dismiss the dialog.
	 */
    private void onTaskCompleted() {
        Log.i(TAG, "Activity " + this + " has been notified the task is complete.");
        this.listItems = mTask.getItems();
        refreshListView();
        //Check added because dismissDialog throws an exception if the current
        //activity hasn't shown it. This Happens if task finishes early enough
        //before an orientation change that the dialog is already gone when
        //the previous activity bundles up the dialogs to reshow.
        if ( mShownDialog ) {
                dismissDialog(LOADING_DIALOG);
                Toast.makeText(this, "Finished..", Toast.LENGTH_LONG).show();
        }
    }

Conclusion

It is possible to handle screen orientations gracefully, but it requires some plumbing code, especially when progress dialogs are involved. Although most people will opt for the quick fix by changing the manifest, and configuring their activity with android:configChanges=”keyboardHidden|orientation”, I think it’s important to realize that there are alternatives to this. Although it requires somewhat more code, it does give you better insight in how the overall system works.

References

Android Developer SDK

Code samples

Stackoverflow questions

  1. February 28th, 2011 at 08:25 | #1

    hi, new to the site, thanks.

  2. Nutter
    March 19th, 2011 at 10:33 | #2

    Excellent article. Helped me spot a leak in one of my AysncTasks.

    Gotta love how many other devs are willing to share their knowledge and experience with Android.

    Thanks :)

  3. admin
    March 20th, 2011 at 16:23 | #3

    @Nutter
    No worries … Glad to see you got something positive out of it….

  4. May 4th, 2011 at 12:32 | #4

    Where (and how) do you set mShownDialog?

    Because of ‘symmetry’ I usually dismiss dialog in onPostExecute (since it is created in onPreExecuty).

  5. June 16th, 2011 at 15:06 | #5

    This is a great article. Thanks for doing the work and the excellent write-up. It saved me a lot of time. Your examples related to orientation change were just what I needed for my next step.

  6. achildress
    July 5th, 2011 at 15:27 | #6

    I am having some unpredictable results with your code if my ListActivity is on a tab of a TabActivity. For some reason, onPostExecute is being called prematurely. If I change orientation while my background task is still running, onPostExecute is called and triggers the dismissal of the dialog. Your code works beautifully as long as my ListActivity is not inside a TabActivity…..any ideas why this is happening and a solution to the problem?

  7. achildress
    July 5th, 2011 at 18:42 | #7

    I think I’ve answered my own question…I specified android:configChanges=”orientation” on my TabActivity in my AdroidManifest and now the AsyncTask is working just fine in my ListActivity. I discovered that the onCreate method of the TabActivity was firing on each orientation change, and that basically disrupted the functioning of everything for the ListActivity. Thanks for the very elegant example you posted. Saved me a ton of time coding…

  8. July 15th, 2011 at 13:44 | #8

    Excellent article, thanks for sharing!

  9. Morkus
    November 10th, 2011 at 10:23 | #9

    Thanks a lot !
    It’s the best article I read about this issue.
    I have spent a lot of time and lost sweat and tears but with your help it’s now over !
    Just a sad comment, that works succesfully on Android 1.6+ but it’s not perfect on 1.5
    There is no more exception but the progress dialog never ended.

  10. Craig
    November 30th, 2011 at 20:52 | #10

    The graceful handling of an orientation change by preserving the AsyncTask is very cool and a nice approach. However, all this is making the assumption that the reason for the Activity being destroyed if due to an orientation change. What about the case where an activity is destroyed for other reasons. If this AsyncTask is doing anything more substantial than retrieving a small finite list, you are basically leaving a task running with no Activity to host is. In a sense, you’ve leaked the task.

    I realize that most of the time this wouldn’t be an issue. However, I have a situation where I’m downloading/deleting/installing a bunch of APKs and it would be great to handle orientation changes without a hiccup. It does raise the question though, if my activity is killed, do I want an AsyncTask continuing its downloading or deleting or installing?

    I’m not really looking for you to answer this for me. Rather I’m just raising a concern. Food for thought.

  11. Ramesh
    December 26th, 2011 at 15:06 | #11

    this is vary useful for me.
    thanks a lot.

  12. Klym
    January 26th, 2012 at 09:02 | #12

    Well, this is really nice, but if you have complicated activities with tons of dialogs and asynctasks, it just becomes pain in the ass, to be honest. Google should do something about this. There is just times where you cant use “android:configChanges” (using it you just asks for problems).

  13. Soma
    February 6th, 2012 at 22:26 | #13

    Might be a better option these days to work with DialogFragments as they are managed

  14. May 11th, 2012 at 03:02 | #14

    Hey excellent solution, and very well explained, thanks man!

  15. Reddy
    January 24th, 2013 at 02:44 | #15

    Hi,

    Can you tel me what the is code flow of screen orientation in android(frameworks), how it’s handled in frameworks……..pls…

  16. May 6th, 2013 at 13:16 | #16

    very nice tutorial you can also check this onehttp://pavanhd.blogspot.in/2013/04/android-progress-dialog-example.html>

  1. November 8th, 2011 at 12:00 | #1
  2. December 9th, 2011 at 01:37 | #2