11

I have spent hours trying to fix an app crash and I think it deserves a question:

The Exception:

java.lang.IllegalStateException: View android.widget.LinearLayout{41a97eb8 V.E..... ......ID 0,0-540,105 #7f0b020d app:id/toast_layout_root} has already been added to the window manager.
   at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:223)
   at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
   at android.widget.Toast$TN.handleShow(Toast.java:402)
   at android.widget.Toast$TN$1.run(Toast.java:310)
   at android.os.Handler.handleCallback(Handler.java:730)
   at android.os.Handler.dispatchMessage(Handler.java:92)
   at android.os.Looper.loop(Looper.java:137)
   at android.app.ActivityThread.main(ActivityThread.java:5136)
   at java.lang.reflect.Method.invokeNative(Method.java)
   at java.lang.reflect.Method.invoke(Method.java:525)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
   at dalvik.system.NativeStart.main(NativeStart.java)

The Code:

I have a custom toast following this guide

I defined the custom toast as ToastMessageBar.java and the constructor looks like this:

public ToastMessageBar(Activity activity) {
    LayoutInflater inflater = activity.getLayoutInflater;
    mToastLayout = inflater.inflate(R.layout.toast_layout,
            (ViewGroup) activity.findViewById(R.id.toast_layout_root));

    mMessageView = (TextView) mToastLayout.findViewById(R.id.toast_message);
    mSubtitleView = (TextView) mToastLayout.findViewById(R.id.toast_subtitle);
}

and the way I show a toast message is following:

private void showMessage(MessageType type, String message, String subtitle) {
    int duration = Toast.LENGTH_SHORT;
    if (mToastLayout != null) {
        int colorId;
        switch (type) {
            case Warning:
                colorId = R.color.warning_bar_color;
                duration = Toast.LENGTH_SHORT;
                break;

            case Error:
                colorId = R.color.error_bar_color;
                break;

            default:
                colorId = R.color.info_bar_color;
                break;
        }

        mToastLayout.setBackgroundColor(
                MyApp.getContext().getResources().getColor(colorId));

        if (subtitle == null) {
            mMessageView.setVisibility(View.GONE);
            mSubtitleView.setText(message);
        } else {
            mMessageView.setVisibility(View.VISIBLE);
            mMessageView.setText(message);
            mSubtitleView.setText(subtitle);
        }
    }

    Utils.showToast(mToastLayout, message, duration);
}

public static void showToast(View layout, String message, int duration) {
    if (layout != null) {
        Toast toast = new Toast(MyApp.getContext());
        toast.setGravity(Gravity.TOP|Gravity.FILL_HORIZONTAL, 0, 0);
        toast.setDuration(duration);
        toast.setView(layout);
        toast.show();
        return;
    }

    Toast.makeText(MyApp.getContext(), message, Toast.LENGTH_LONG).show();
}

In MyBaseActivity.onCreate() I define the ToastMessageBar:

mMessageBar = new ToastMessageBar(this);

In this way, I can use showMessage() in all the activities that inherits MyBaseActivity.

It seems the exception happens when I call toast.show(); but it does not happen all the time (only rare cases) I don't know what caused the exception still.

xialin
  • 7,686
  • 9
  • 35
  • 66

3 Answers3

3

First off- when you're asking for help on an exception, always give us the full stack trace.

You're screwing up with your inflation and Toast usage. When you use the layout the first time, you're cool. The second time, it already has a parent (the Toast), so it will throw an error when you try to add it to the toast via setView. You either need to inflate a new copy each Toast, or remove it from the old Toast before adding it to the new one.

Gabe Sechan
  • 90,003
  • 9
  • 87
  • 127
  • if you check the code of Toast.java handleShow(), it has the checking of whether the view already has parent if (mView.getParent() != null) { if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); mWM.removeView(mView); } if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this); mWM.addView(mView, mParams); – xialin Mar 10 '15 at 06:23
  • 1
    and because this crash does not happen locally, I only get limited info from Crashlytics – xialin Mar 10 '15 at 06:24
  • You're counting on an implementation detail like that? Sure it does that in every version of Android ever released? Because the exception you have is exactly what I said- you try to add a view to a window for a second time. – Gabe Sechan Mar 10 '15 at 06:28
  • Ok, I get your point. so what should be the right way of handling that? – xialin Mar 10 '15 at 06:32
  • I checked the crash records, all of them are from Android 4.3 – xialin Mar 10 '15 at 06:36
1

You'd better set a new view every time you want to show the Toast.

In Toast.java handleHide() function. It says:

            // note: checking parent() just to make sure the view has
            // been added...  i have seen cases where we get here when
            // the view isn't yet added, so let's try not to crash.
            if (mView.getParent() != null) {
                if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                mWM.removeView(mView);
            }

And in Toast.java handleShow() function

            if (mView.getParent() != null) {
                if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                mWM.removeView(mView);
            }
            if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
            mWM.addView(mView, mParams);

May be when the last view been added but don't have a parent yet. A new view comes. Then it throw IllegalStateException

OverLook
  • 21
  • 2
0

I think problem at this line

   mToastLayout = inflater.inflate(R.layout.toast_layout,
            (ViewGroup) activity.findViewById(R.id.toast_layout_root));

Instead try:

mToastLayout = inflater.inflate(R.layout.toast_layout,null);
Pratik Popat
  • 2,891
  • 20
  • 31
  • check the link here http://developer.android.com/guide/topics/ui/notifiers/toasts.html I'm following this. – xialin Mar 10 '15 at 06:37