0

I can't for the life of me figure out how to manage dialogs without using configChanges to specify that you want to manually handle orientation changes. So lets say you have this AndroidManifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testandroid"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />    
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>    
</manifest>

Take this MainActivity.java:

package com.example.testandroid;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.View;

public class MainActivity extends Activity {
    private final static String TAG = "MainActivity";
    Dialog mDialog = null;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
        setContentView(R.layout.activity_main);
    }    
    public void doShowDialog(View b) {
        Log.d(TAG, "doShowDialog");
        showDialog(1);
    }

    private void tryDismiss() {
        Log.d(TAG, "tryDismiss");
        try {           
            dismissDialog(1);
            removeDialog(1);
            mDialog.dismiss();
        } catch(IllegalArgumentException ex) {
            Log.e(TAG, ex.getMessage());
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    @Override
    protected void onPause() {
        tryDismiss();
        super.onPause();
        Log.d(TAG, "onPause");

    }
    @Override
    protected Dialog onCreateDialog(int dialog) {
        Builder b = new AlertDialog.Builder(this);
        b.setTitle("Hello").setMessage("Waiting..");
        mDialog =  b.create();
        return mDialog;

    }    
}

and this layout (main.xml)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
 >
    <Button 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Open Dialog"
        android:onClick="doShowDialog"
        />

</LinearLayout>

It doesn't seem to matter if you call from onDestroy or onPause, the dialog shows back up after the orientation switches. But why? I told it to go away. If call removeDialog/dismissDialog it does nothing when called before the orientation changes. I can't figure out for the life of me why this is. The only way to get rid of this that I know of is to handle the orientation change yourself by using

android:configChanges="keyboardHidden|orientation"

I know the new way of working is to use the FragmentDialog stuff which I have not upgraded to yet and am not ready to rewrite my whole app for that. Just seems strange that this doesn't work.

This is just an example of a real world problem I'm having in my app where the user can request some data be pulled from a remote server (to update a spinner's data), and if they switch orientation the loading dialog will never go away and there seems to be no fix for this besides handling the orientation change with the android:configChanges option. Which I can do but it seems ridiculous to me to have to do that.

-- Update -- Removed the button to dismiss the dialog as it's not necessary and you can't click it anyways since the dialog is on top.

To reproduce just start the app, click the button that opens the dialog, and then rotate your phone.

Matt Wolfe
  • 8,924
  • 8
  • 60
  • 77
  • Please be clear about your steps to reproduce. You load it portrait. Click on button to show the dialog, then click to stop dialog, and then switch to landscape and it shows up? – Code Droid Jul 06 '12 at 20:03
  • Not seeing where you assigned the arguments to doDismiss(l)? How did you initialize this? what is this value? – Code Droid Jul 06 '12 at 20:05
  • Have you set breakpoints to be certain these methods are being called? Check if the arguments really match the dialog you have created etc. – Code Droid Jul 06 '12 at 20:06
  • Should it not be mDialog that gets passed to as the argument? – Code Droid Jul 06 '12 at 20:08
  • dismissDialog(1); removeDialog(1); do not look right to me. – Code Droid Jul 06 '12 at 20:09
  • A lot of posts on the net about this issue say to "use removeDialog instead of dismissDialog", but even that does not work in this scenario. Try taking one of them out, or both.. Doesn't matter, you can't dismiss a dialog once the activity is in the process of getting destroyed, and then the only way I can figure out to remove it is for the user to press the back button. – Matt Wolfe Jul 06 '12 at 21:20
  • You should try adding a `OnClickListener` to the dialog using `setPositiveButton(...)` add an anonymous class here by simple implement a `DialogInterface.OnClickListener` in place. This should dismiss your dialog no matter what. See: https://developmentality.wordpress.com/2009/10/31/android-dialog-box-tutorial/ – kneo Jul 09 '12 at 05:54
  • I should have used an example of a loading dialog box which is what my real world problem is using.. The point is that this dialog is there for some long running processes and I want to dismiss it and show it again if a rotation happens. – Matt Wolfe Jul 09 '12 at 05:59
  • In assumption you are using a `AsyncTask` or Thread or something you need to update the `Context` assigned since it changes if the device is rotated. For this I suggest a static field in the Thread's class. If the devices is rotated the `Activty.onResume` method updates your `Context` in the thread and shows the `ProgressDialog` again. It's just a principle I didn't test yet. If you do this expect WindowLeakExceptions and odd crashes! – kneo Jul 09 '12 at 06:08
  • I'd suggest to lock the device rotation as long as the process runs, and unlock it if it's done. Another alternative is just to restart the process in the `onCreate` method all over again. If you download files into an external directory for example you could synchronize the data fetched and not loaded and download only the missing files. This avoids having a buggy and crashing application... – kneo Jul 09 '12 at 06:16

4 Answers4

2

Your dialog is saved in onSaveInstanceState, so you might try dismissing it before it's launched:

@Override
protected void onSaveInstanceState(Bundle state)
{
  tryDismiss();
  super.onSaveInstanceState(state);
}

Also I don't really understand why do you use Activity's onCreateDialog to manage dialogs. The reason it was designed was to handle orientation changes automatically. If you want to handle it manually, why don't you just use dialog's functions? Instead of using showDialog(id) and onCreateDialog(id) just launch it directly, it won't reappear after rotating the screen.

    Builder b = new AlertDialog.Builder(this);
    b.setTitle("Hello").setMessage("Waiting..");
    Dialog mDialog =  b.create();
    mDialog.show(); // <-----
Sebastian Nowak
  • 5,607
  • 8
  • 67
  • 107
  • Yeah showDialog(id) and onCreateDialog(id) is making an issue when orientation changes. Better if we go @Sebastian Nowak answer – Dharmendra Jul 09 '12 at 14:21
  • Also create a global instance of the dialog and in onDestroy() method use the code like `if(dialog != null && dialog.isShowing()) {dialog.dismiss();}` – Dharmendra Jul 09 '12 at 14:23
  • I don't understand this thought though. After the orientation change calling dismissDialog or removeDialog does not work, so it appears android does a really crappy job of handling the dialog for you. – Matt Wolfe Jul 09 '12 at 15:21
  • I see now, if you call dismissDialog/removeDialog it won't work with the newly created activity if you do it from onCreate. You must use onPostCreate or from a delayed handler. Oddly, when I tested your code I figured I could call dismissDialog/removeDialog by overriding onRestoreInstanceState, calling super.onRestoreInstanceState and then dismissDialog, but that did not work. Calling it from onPostCreate after calling super worked (didn't try before calling super). – Matt Wolfe Jul 09 '12 at 17:48
  • Oh wait, but you said you would to dismiss the dialog BEFORE activity is destroyed, not in the activity that is created after the orientation change. That's why I told you to overwrite onSaveInstanceState and dismiss the dialog before calling super, so it won't be saved. I've tested it and it's working. What's the problem with my solution then? – Sebastian Nowak Jul 10 '12 at 12:07
  • @Sebastian Nowak, thanks! It helped me fix my problem (I used to dismiss dialog in onPause but if app is unloaded/killed then restarted dialog used to reappear again). – Dragan Marjanović Mar 07 '13 at 09:32
1

Sebastian got it but I'd like to explain a bit more about the situation and my findings on dialogs since it can be quite confusing:

  1. As sebastian put it, you must call dismissDialog/removeDialog from onSaveInstanceState if you want the dialog gone before the rotation.
  2. While you can create a dialog from onCreate, if you don't dismiss it before the orientation change you won't be able to dismiss in the onCreate method when the activity restarts. You must call dismissDialog from onPostCreate
  3. Calling dismissDialog didn't work in onRestoreInstanceState after orientation change as well. I tried both before and after calling super.onRestoreInstanceState and neither worked (thought it would since dimissing happens in onSaveInstanceState)

Even more important than this is that I learned if you are doing some Asynchronous task, such as an HTTP call and you have an inner class which contains a callback function which will run when the task is complete, you need to be aware that that inner class method will contain a reference to the original instance of the outer Activity class if the screen is rotated. This was not at all obvious to me as and I wasn't actually using AsyncTask as many others have had issues with (I was using an asynchronous http library).

Taking a small example similar to my real code:

     public class MyActivity extends Activity {
   private static MyActivity sThis;

   @Override
   public void onCreate(Bundle state) {
      super.onCreate(state);
      sThis = this;
      doAsyncWork();
   }

   @Override
   public void onDestroy() {
      super.onDestroy();
      sThis = null;
   }
   private void doAsyncWork() {
      showDialog(LOADING_DIALOG); 
      ExampleAsyncWorker.work(new AsyncWorkerCallback() {   
           @Override
           public void onWorkComplete() {
             dismissDialog(LOADING_DIALOG); //doesn't work if orientation change happened.
           }        
      });
   }
}

The above code, through the CategoryManager, connects to an external server, downloads a list of categories, and once it is complete calls onCategoriesObtained and then onFetchComplete (also there are some error handling callback function removed for brevity). If an orientation change happens between the fetchCategories call and onFetchComplete then the call to dismissDialog in onFetchComplete will never work. The reason is that this inner class has an implicit reference to the original instance of the Activity class which was created before the orientation change. Thus, when you call dismissDialog you are calling it on the original instance not the new one, which will cause the dismissDialog to fail with the message: "no dialog with id 1 was ever shown via Activity#showDialog". I did figure out a way around this but its a bit of hack:

In your Activity class, include a static reference to the this reference and set it in onCreate, and null it in onDestroy, and use that reference from your inner class like so:

public class MyActivity extends Activity {
   private static MyActivity sThis;

   @Override
   public void onCreate(Bundle state) {
      super.onCreate(state);
      sThis = this;
      doAsyncWork();
   }

   @Override
   public void onDestroy() {
      super.onDestroy();
      sThis = null;
   }
   private void doAsyncWork() {
      showDialog(LOADING_DIALOG); 
      ExampleAsyncWorker.work(new AsyncWorkerCallback() {   
           @Override
           public void onWorkComplete() {
              sThis.dismissDialog(LOADING_DIALOG);
           }        
      });
   }
}

Note that I'm not sure this is a great practice but it worked for me. I know there can be problems with inner classes in activities that refer to the outer class (leaking the context) so there may be better ways of solving this problem.

Matt Wolfe
  • 8,924
  • 8
  • 60
  • 77
0

AFAIK , This window leaked can be handled in two ways.

@Override
public void onDestroy(){
    super.onDestroy();
    if ( Dialog!=null && Dialog.isShowing() ){
        Dialog.dismiss();
    }
}

Or

if(getActivity()!= null && !getActivity().isFinishing()){
            Dialog.show();
} 

Here Dailog is your progress /alert dailog

King of Masses
  • 18,405
  • 4
  • 60
  • 77
-1

for creating your app without the savedinstace you can use super.onCreate(null);

ChampS
  • 19
  • 1