6

In my app I have a custom AlertDialog (handled by the system using showDialog()) which contains a tabhost with 2 tabs. In one of the tabs is a spinner. I can rotate my screen without problems as long as the spinner isn't open (spinner dialog displayed). If the spinner is open during rotation, I get this:

FATAL EXCEPTION: main
java.lang.IllegalArgumentException: View not attached to window manager
    at android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:355)
    at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:200)
    at android.view.Window$LocalWindowManager.removeView(Window.java:432)
    at android.app.Dialog.dismissDialog(Dialog.java:278)
    at android.app.Dialog.access$000(Dialog.java:71)
    at android.app.Dialog$1.run(Dialog.java:111)
    at android.app.Dialog.dismiss(Dialog.java:268)
    at android.widget.Spinner.onDetachedFromWindow(Spinner.java:89)
    at android.view.View.dispatchDetachedFromWindow(View.java:6173)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1164)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
    at android.view.ViewRoot.dispatchDetachedFromWindow(ViewRoot.java:1746)
    at android.view.ViewRoot.doDie(ViewRoot.java:2757)
    at android.view.ViewRoot.handleMessage(ViewRoot.java:1995)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:130)
    at android.app.ActivityThread.main(ActivityThread.java:3683)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:507)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
    at dalvik.system.NativeStart.main(Native Method)

So...

1 - Is it possible to programmatically close the spinner during onPause()?

2 - Is there a different method I should be using?

3 - If there isn't a proper solution, how do I catch this particular Exception? (Bad practice I know, but the crashing needs to be stopped, and since the activity reconstructs itself properly after being destroyed, it wouldn't matter anyway.

... And please for the love of all that is holy, do not suggest that I add android:configChanges="orientation" to my activity declaration. It amazes me how often that is accepted as the official fix-all for orientation issues.

Edit for additional info

Here is my dialog creation code for reference:

static final int ID_DIALOG_CHOOSER = 1;
int pref_location;
private Dialog dialog;

...

protected Dialog onCreateDialog(int id)
{
    switch(id)
    {
    case ID_DIALOG_CHOOSER:
        dialog = show(ID_DIALOG_CHOOSER);
        break;
    }
    return dialog;
}

...

showDialog(DialogView.ID_DIALOG_CHOOSER);

...

Dialog show(final int dialogType) 
{
    if (dialogType == ID_DIALOG_CHOOSER)
    {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);

        // inflate tabhost layout
        View tabHostLayout = (View) inflater.inflate(R.layout.tabhost_layout, null);
        FrameLayout tabContent = (FrameLayout) tabHostLayout.findViewById(android.R.id.tabcontent);

        // inflate tab content layouts and add to tabhost layout
        LinearLayout tab1 = (LinearLayout) inflater.inflate(R.layout.dialog_tab1, null);
        tabContent.addView(tab1);
        LinearLayout tab2 = (LinearLayout) inflater.inflate(R.layout.dialog_tab2, null);
        tabContent.addView(tab2);

        // tab setup
        TabHost tabHost = (TabHost) tabHostLayout.findViewById(R.id.TabHost_Dialog_Tabs);
        tabHost.setup();

        TabHost.TabSpec tab_1 = tabHost.newTabSpec("Category 1");
        tab_1.setContent(R.id.LinearLayout_Dialog_Tab1_Content);
        tab_1.setIndicator(this.getResources().getString(R.string.dialog_tab1), this.getResources().getDrawable(R.drawable.tabhost_icon_selector));
        tabHost.addTab(tab_1);

        TabHost.TabSpec tab_2 = tabHost.newTabSpec("Category 2");
        tab_2.setContent(R.id.LinearLayout_Dialog_Tab2_Content);
        tab_2.setIndicator(this.getResources().getString(R.string.dialog_tab2), this.getResources().getDrawable(R.drawable.tabhost_icon_selector));
        tabHost.addTab(tab_2);

        // spinner setup
        final Spinner spinner_location = (Spinner) tab1.findViewById(R.id.Spinner_Dialog_Location);
        String[] locationArrayVals = null;
        ArrayAdapter<CharSequence> adapter_location = null;
        locationArrayVals = this.getResources().getStringArray(R.array.location_array_vals);
        adapter_location = ArrayAdapter.createFromResource(this, R.array.location_array_listdisplay, android.R.layout.simple_spinner_item);
        adapter_location.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner_location.setAdapter(adapter_location);

        // ok button
        Button button_ok = (Button) tab1.findViewById(R.id.Button_Dialog_OK);
        button_ok.setOnClickListener(new View.OnClickListener() 
        {
            public void onClick(View view) 
            {
                pref_location = spinner_location.getSelectedItemPosition();
                dialog.dismiss();
            }
        });

        // create dialog
        builder.setTitle("Location")
        .setView(tabHost)
        .setCancelable(true);
        dialog = builder.create();
    }
    return dialog;
}

Edit with temporary workaround

For anybody interested I managed to at least stop the crashing by subclassing spinner and overriding onDetachedFromWindow() like so:

public static class CatchingSpinner extends Spinner
{
    public CatchingSpinner(Context context, AttributeSet attrs) 
    {
        super(context, attrs);
    }

    @Override
    protected void onDetachedFromWindow()
    {
        try
        {
            super.onDetachedFromWindow();
        }
        catch (IllegalArgumentException e)
        {
            // do whatever
        }
    } 
}

Like I said, a workaround. Still working on a solution. :/

wirbly
  • 2,183
  • 1
  • 24
  • 24
  • Did you try this method: showDialog(DIALOG_PAUSED_ID); mentioned in dialog tutorial: http://developer.android.com/guide/topics/ui/dialogs.html – ania Aug 02 '11 at 02:49
  • Yes - my dialog is managed by the activity. – wirbly Aug 02 '11 at 02:54
  • Can provide code you use for showing and dismissing dialog? If you say that you use dialog.dismiss(), than you are not using yourActivity.showDialog(DIALOG_NAME) method but dialog.show(). – ania Aug 02 '11 at 03:07
  • I apologize - I was using dialog.dismiss for something else, so disregard that part. I have edited my question to include the dialog creation code. (I left out the code for the other tab as it is just text) – wirbly Aug 02 '11 at 19:42
  • For the record this is still an issue a year later, even with the "new and improved" method of displaying dialogs via DialogFragment. I ended up using your workaround of catching the exception. – stevesw Aug 02 '12 at 15:42

3 Answers3

3

I found a better solution. It is actually not so hard to replace Spinner with a button that looks and behaves like Spinner (except the crash, fortunately).

<Button 
     android:background="@android:drawable/btn_dropdown" 
     android:gravity="left|center_vertical"
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" />

public void showSpinner() {
    AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
    builder.setTitle(_spinnerPrompt);
    builder.setSingleChoiceItems(_spinnerOptions, _spinnerSelection,
      new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int item) {
            _spinnerSelection = item;
            _pseudoSpinner.setText(_spinnerOptions[item]);
            _restoreSpinnerOnRestart = false;
            dialog.dismiss();
        }
    });
    builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
        @Override
        public void onCancel(DialogInterface dialog) {
            _restoreSpinnerOnRestart = false;
        }
    });
    AlertDialog alert = builder.create();
    _restoreSpinnerOnRestart = true;
    alert.show();
    }

@Override
public Bundle onSaveInstanceState() {
    Bundle state = super.onSaveInstanceState();
    state.putBoolean(STATE_SPINNER_RESTORE, _restoreSpinnerOnRestart);
    state.putInt(STATE_SPINNER_SELECTION, _spinnerSelection);
    return state;
};

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    _spinnerSelection = savedInstanceState.getInt(STATE_SPINNER_SELECTION, -1);
    _restoreSpinnerOnRestart = savedInstanceState.getBoolean(STATE_SPINNER_RESTORE);
    _pseudoSpinner.setText(_spinnerOptions[_spinnerSelection]);

    if (_restoreSpinnerOnRestart) {
        showSpinner();
    }
};
Juozas Kontvainis
  • 9,461
  • 6
  • 55
  • 66
1

Ok, I think I've found your problem: dialog.dismiss();
You are doing really strange thing - you are creating dialog through activity but say dismiss to it not to activity to dismiss it - those are two different approaches which you can't mix. You should choose one method: dialog.show and dialog.dismiss or activity.showDialog() and activity.dismissDialog() <- this is better because handles orientation change.
What you should do is just remove dialog from class variables, use activity.showDialog everywhere. Probably your problem was in dismissing dialog in onClick. Just use YourActivityName.this.dismissDialog(DIALOG_NUMBER) and it should work.
Read more about dialogs form developers site - I had also such problems and taken long time to learn how it works but after all - dialogs are not so complicated ;)

ania
  • 2,352
  • 1
  • 20
  • 20
  • This makes sense, but when I change my code to use `dismissDialog()` I get `java.lang.IllegalArgumentException: no dialog with id 1 was ever shown via Activity#showDialog`. ID 1 is the correct ID, but why doesn't it think the dialog was ever shown? (BTW this is before any orientation change - I'm just bringing up the dialog and dismissing it with the OK button to test this) – wirbly Aug 03 '11 at 09:07
  • When I try to @Override showDialog(), eclipse flags it as `Cannot override the final method from Activity`. Where should I put it? – wirbly Aug 03 '11 at 18:06
  • Check Activity documentation. It depends on your android version. If API Level is 8 or higher you should use showDialog(int id, Bundle args) and if you use fragments then FragmentDialog should be used. Tutorials are not always updated but documentation is :) – ania Aug 04 '11 at 00:44
1

I had a similar a crash. I worked around by disabling orientation changes when dialog is displayed.

@Override
public void onDismiss(DialogInterface dialog) {
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}

@Override
public void onShow(DialogInterface dialog) {
    int loadedOrientation = getResources().getConfiguration().orientation;
    int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
    if (loadedOrientation == Configuration.ORIENTATION_LANDSCAPE) {
        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
    } else if (loadedOrientation == Configuration.ORIENTATION_PORTRAIT) {
        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
    }
    setRequestedOrientation(requestedOrientation);
}

NOTE: I actually found that there is no reliable way to lock screen orientation.

Community
  • 1
  • 1
Juozas Kontvainis
  • 9,461
  • 6
  • 55
  • 66
  • I want to support orientation changes at all times, but this is an interesting approach. Thanks for the example. – wirbly Aug 17 '11 at 17:58