Properly handle progress dialog in Android
By Jay Liang
Here is another thing in android that kept me scratching my head for a day before i finally figure out how to do it properly because of the lack of documentations and books for Android development at this time.
The android app that I am currently working on has a search feature which will fire a http request and shows the result in a list view. The search can be slow depending on the network speed so I wanted to put a loading dialog on the screen to indicate that the app is processing.
There's a ProgressDialog class in Android's SDK, so the idea is to just first build and show a progress dialog before the search starts, and then cancel the dialog on search completion:
ProgressDialog pd = ProgressDialog.show(this,The above snippet will not work because the makeHttpRequest() method is being executed on the UI thread (Android has a similar threading model as Swing) therefore the dialog will not show on the screen until makeHttpRequest() returns.
"Title",
"Message",
true, false);
makeHttpRequest();
pd.dismiss();
That's understandable, so how about putting the method in a background thread like this:
final ProgressDialog pd = ProgressDialog.show(this,This works as we would expect, the progress dialog is shown and then it will disappear when makeHttpRequest() is finished.
"Title",
"Message",
true, false);
new Thread(new Runnable(){
public void run(){
makeHttpRequest();
pd.dimiss();
}
}).start();
However, here's the difficult bit. The above code will cause your application to crash if the user change the phone's orientation while the progress dialog is not yet dismissed.
W/dalvikvm( 292): threadid=3: thread exiting with uncaught exception
(group=0x40010e28)
E/AndroidRuntime( 292): Uncaught handler: thread main exiting due to uncaught exception
E/AndroidRuntime( 292): java.lang.IllegalArgumentException: View not attached to window manager
E/AndroidRuntime( 292): at android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java: 331)
E/AndroidRuntime( 292): at
android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:200)
E/AndroidRuntime( 292): at android.view.Window$LocalWindowManager.removeView(Window.java:401)
E/AndroidRuntime( 292): at
android.app.Dialog.dismissDialog(Dialog.java:249)
E/AndroidRuntime( 292): at android.app.Dialog.access$000(Dialog.java:59)
E/AndroidRuntime( 292): at android.app.Dialog$1.run(Dialog.java:93)
E/AndroidRuntime( 292): at
android.app.Dialog.dismiss(Dialog.java:233)
E/AndroidRuntime( 292): at android.app.Dialog.cancel(Dialog.java:838)
E/AndroidRuntime( 292): at com.yellowbook.android2.SearchHelper$3.handleMessage(SearchHelper.java:97)
E/AndroidRuntime( 292): at android.os.Handler.dispatchMessage(Handler.java:88)
E/AndroidRuntime( 292): at android.os.Looper.loop(Looper.java:
123)
E/AndroidRuntime( 292): at android.app.ActivityThread.main(ActivityThread.java:3742)
E/AndroidRuntime( 292): at
java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime( 292): at
java.lang.reflect.Method.invoke(Method.java:515)
E/AndroidRuntime( 292): at com.android.internal.os.ZygoteInit
$MethodAndArgsCaller.run(ZygoteInit.java:739)
E/AndroidRuntime( 292): at
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
E/AndroidRuntime( 292): at
dalvik.system.NativeStart.main(Native Method)
That is because by default, activities in Android will be destroyed and recreated on orientation change. Since the progress dialog is no longer 'attached' to the view after the activity is re-created, it blows up once the activity was destroyed. The problem has to do with the internal UI managements of android which i won't attempt to explain here.
Long story short, I figured out a working solution to the above problem. Rather than creating and showing the dialog manually, you will need to override the onCreateDialog() method in your activity to let Android management the dialogs for you.
Example codes:
@Override
protected Dialog onCreateDialog(int id) {
if(id == ID_DIALOG_SEARCHING){
ProgressDialog loadingDialog = new ProgressDialog(this);
loadingDialog.setMessage("searching...");
loadingDialog.setIndeterminate(true);
loadingDialog.setCancelable(true);
return loadingDialog;
}
return super.onCreateDialog(id);
}
private void Search(){
showDialog(ID_DIALOG_SEARCHING);
new Thread(new Runnable(){
public void run(){
makeHttpRequest();
dismissDialog(AndroidSearch.ID_DIALOG_SEARCHING);
}
}).start();
}
In addition to the aboive, my app's makeHttpRequest() forwards the UI to another screen using startActivityForResult() method like so:
makeHttpRequest(){If you have similar code, then you will find that the progress dialog is still being shown when you click on the back button to go back to the search activity screen. After a few more hours of trial and errors, I found that if I override the onSaveInstanceState() as followed, the progress dialog will be gone when I go back to the previous screen using the phone's back button.
Result res = search();
Intent i = new Intent(activity, ResultActivity.class);
i.putExtra("result", res);
activity.startActivityForResult(i, 1);
}
//Do this at your own risk because I don't know whether that's the correct way of handling this.
@Override
protected void onSaveInstanceState(Bundle outState) {
try {
dismissDialog(ID_DIALOG_SEARCHING);
} catch (Exception e) {
//ignore error
}
super.onSaveInstanceState(outState);
}