5

While trying to release my app for production, the pre-launch report notified me of an error on Pixel 2 Android P Preview device. The error is related to a custom toast message I have, saying that a View "has already been added to the window manager":

java.lang.IllegalStateException: View android.support.constraint.ConstraintLayout{efbeb21 V.E...... ......ID 0,0-788,1124 #7f0900db app:id/toast_correct_container} has already been added to the window manager.
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:328)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
at android.widget.Toast$TN.handleShow(Toast.java:499)
at android.widget.Toast$TN$1.handleMessage(Toast.java:403)
at android.os.Handler.dispatchMessage(Handler.java:106)
at androidx.test.espresso.base.Interrogator.a(Interrogator.java:19)
at androidx.test.espresso.base.UiControllerImpl.a(UiControllerImpl.java:142)
at androidx.test.espresso.base.UiControllerImpl.a(UiControllerImpl.java:134)
at androidx.test.espresso.base.UiControllerImpl.a(UiControllerImpl.java:34)
at androidx.test.espresso.action.MotionEvents.a(MotionEvents.java:74)
at androidx.test.espresso.action.MotionEvents.a(MotionEvents.java:52)
at androidx.test.espresso.action.Tap.c(Tap.java:9)
at androidx.test.espresso.action.Tap.a(Tap.java:19)
at androidx.test.espresso.action.Tap$1.b(Tap.java:2)
at androidx.test.espresso.action.GeneralClickAction.perform(GeneralClickAction.java:22)
at androidx.test.espresso.ViewInteraction$SingleExecutionViewAction.perform(ViewInteraction.java:9)
at androidx.test.espresso.ViewInteraction.a(ViewInteraction.java:78)
at androidx.test.espresso.ViewInteraction.a(ViewInteraction.java:94)
at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.java:3)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

In MainActivity OnCreate I call this method that inflates the toast View:

private void initToastObjects() {
    mToastCorrect = new Toast(this);
    mToastWrong = new Toast(this);

    // inflate view
    LayoutInflater myInflater = LayoutInflater.from(this);
    mLayoutCorrect = myInflater.inflate(R.layout.toast_correct, (ViewGroup) findViewById(R.id.toast_correct_container));
    mLayoutWrong = myInflater.inflate(R.layout.toast_wrong, (ViewGroup) findViewById(R.id.toast_wrong_container));
}

I later set dynamically different images to the toast according to user selection:

mToastCorrect.setView(mLayoutCorrect);

I show the toast messages every time the user click correct/wrong answers, and cancel the other toast if it's displayed:

    // cancel previous wrong answer toast and display correct answer toast
    try {
        if (mToastWrong.getView().isShown()) {
            mToastWrong.cancel();
        }
        mToastCorrect.show();
    } catch (Exception e) {
        e.printStackTrace();
    }

Any help is appreciated!

  1. How should I fix this issue?
  2. Why do I get this error only on Android P Preview device?
  3. If I inflate the View only once during MainActivity OnCreate, how come I get an error saying that the view "has already been added to the window manager"?
Oshri
  • 169
  • 1
  • 11
  • Have you managed to reproduce this on a real device or emulator? I'm suspicious this is only an issue on pre launch report – Mark Sep 26 '18 at 10:00
  • @Mark just on emulator. I don't have yet a real device at hand with Android P. – Oshri Oct 01 '18 at 18:07

2 Answers2

5

After few trials and errors, I have managed to fix it. I hope it would help others that encounter the same issue.

Apparently, toast messages handling was changed in Android P (API 28). In my app, toast messages are triggered by button clicks, so a toast message can be invoked before the previous toast message was done (note that the two toast messages are invoked from the same Toast object). On Android versions prior to P (API 28), there is no issue with starting a new toast before the previous one is done (even if it's the same Toast object) - the new toast just overrides the old toast and starts again. However, in Android P the same behavior may sometimes throw IllegalStateException.

I've already kept reference to the Toast object to reuse it, so I just had to cancel it in case it was shown. Since cancelling it resulted with unwanted behavior for API lower than 28 (e.g. toast message disappearing after a very short time), I have inserted a version check. This is the workaround code:

// cancel previous toast and display correct answer toast
try {
    if (mToastWrong.getView().isShown()) {
        mToastWrong.cancel();
    }
    // cancel same toast only on Android P and above, to avoid IllegalStateException on addView
    if (Build.VERSION.SDK_INT >= 28 && mToastCorrect.getView().isShown()) {
        mToastCorrect.cancel();
    }
    mToastCorrect.show();
} catch (Exception e) {
    e.printStackTrace();
}

What still puzzles me is why the try-catch code didn't catch the exception (the app crashed).

Oshri
  • 169
  • 1
  • 11
  • Shouldn't you have `VERSION.SDK_INT < 28 &&...` instead of `>=` and also for both toasts? – Mark Sep 26 '18 at 08:35
  • @Mark I have the same code for the second toast as well, just didn't put it in the reply. It should be >= 28 since I want to cancel the same toast only for Android P and above. For android 27 and below there is no need to cancel the same toast, it works fine without it. Moreover, if I cancel it for android 27 and below, it behaves abnormally so I don't want to just cancel it in all cases. – Oshri Oct 01 '18 at 18:06
  • 1
    The try-catch doesn't catch the exception because the exception is thrown from another stack trace which belongs to handling procedure of another UI message. the Toast.show() method only schedule a 'event', but the 'event' happens later and not in current stack trace anymore. – Liu Tao Dec 10 '18 at 09:44
  • Consider using Build.VERSION_CODES.P instead of 28. The code will be a bit more readable :) – LPVOID Aug 14 '19 at 20:30
  • @LiuTao is right! Try catch block is not work.. crash are still happening – Ucdemir Jul 30 '21 at 18:45
0

I have seen the same crash reports on Pie, firing the same custom Toast fast (triggered by hardware volume button). The difference to the OP's use case is that I have only one custom Toast instance with only its custom View being updated.

And besides this crash (that I could not reprod myself in the Pie emulator but seen in crash reports), I have another issue: when quickly calling Toast.show() on the same toast (say, 20 times), only at best the first 2 calls display the toast then it disappear. Cancelling the toast before showing it does not help.

Conclusion, toast are really broken on P...

b0b
  • 454
  • 3
  • 5
  • Are you using the same Toast object to show the messages, or are you using a new (perhaps unreferenced) Toast every time you show it? I use the same two Toast objects, which are class members. Perhaps that's the difference between us? – Oshri Aug 24 '18 at 16:28
  • I use a single Toast instance that is constructed once (like you except you have 2 instances), use a custom View (like you), and I call show() repeatedly with just the custom View being updated between calls to show(). My main problem, beside the crash of this post, is that several fast consecutive calls to show() do not work: only this first call (and eventually the second) do actually show the toast. the same code works perfectly before Pie. I'm going to try your method, using 2 toasts and cancelling the previous one when showing the next. – b0b Aug 24 '18 at 16:53
  • After more testing, Toast are definitely broken on Pie when you want to display the same Toast in succession updated fast and visually seamlessly for the user. For example showing the same toast (instance) that displays a counter going from 0 to 100. This is possible before on Oreo and before, but on Pie you have to cancel() the previously shown Toast (otherwise the new one is simply not displayed), which does not make it possible to display the described scenario (going from 0 to 100) without visual glitches. It's very well possible Toast were never designed for this in the first place. – b0b Aug 24 '18 at 23:00
  • 1
    Then there is the crash issue described in the OP and that can be easily reproduced by calling toast.cancel(); toast.show() repeatedly on the same toast several times (even on Oreo). I will probably submit a bug report on the Android issue tracker. – b0b Aug 24 '18 at 23:01
  • 1
    Worth including the issue tracker link: https://issuetracker.google.com/issues/71731536 – Mark Sep 26 '18 at 10:05
  • @Mark thank you for the link. Any chance it would be fixed soon by android dev team? – Oshri Oct 01 '18 at 18:12