Search code examples
androidtoastandroid-toast

Android Toast on top of Toast - bottom one remains when top one goes


My application creates many toasts in many places by calling

Toast.makeText(getBaseContext(), "Blah", Toast.LENGTH_SHORT).show();

(okay, I use a resource id rather than a string). Everything works fine on every Android version from 4 to 7 I've ever used, but with Android 8.1.0, if a second toast pops up before the first one has disappeared, the first one is reinstated when the second one dissolves. My guess is that the display manager is remembering the pixels beneath the toast and reinstating them when the toast goes away, but it isn't clever enough to cope with a stack of toasts so the second one "remembers" the image of the first, and reinstates it instead of the underlying window area.

I can work around this problem using a "makeToast" wrapper that stashes the Toast object in a static variable each time and calls .cancel() on the old one before .show()ing the new one. Is this ultimate solution, or is there A Better Way?

private static Toast lastToast;
private static Context toastContext; // set in onCreate
public static void makeToast(int resId, int duration) {
    if (lastToast != null) lastToast.cancel();
    lastToast = Toast.makeText(toastContext, resId, duration);
    lastToast.show();
}

Solution

  • This fixed it for me - I wrapped Toast with my own class that cancel()s the last object to call show() before show()ing the new one. Seems like a massive hack, but it did get the job done and only required changing the import android.widget.Toast lines in the rest of my app.

    package my.app.hacks;
    
    import android.content.Context;
    import android.content.res.Resources;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.widget.TextView;    
    
    public class Toast extends android.widget.Toast {
        public static final int LENGTH_SHORT = android.widget.Toast.LENGTH_SHORT;
        public static final int LENGTH_LONG = android.widget.Toast.LENGTH_LONG;
    
        private static Toast lastShown;
    
        public Toast(Context context) {
            super(context);
        }
    
        public void show() {
            if (lastShown != null) lastShown.cancel();
            super.show();
            lastShown = this;
        }
    
        public static Toast makeText(Context context, CharSequence text, int duration) {
            //
            // this is almost copy-n-pasted from android.widget.Toast.makeText()
            //
            Toast result = new Toast(context);
    
            LayoutInflater inflate = (LayoutInflater)
                    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            // we can't directly access com.android.internal.R identifiers so use the public interface
            int layout = Resources.getSystem().getIdentifier("transient_notification", "layout", "android");
            View v = inflate.inflate(layout, null);
            int id = Resources.getSystem().getIdentifier("message", "id", "android");
            TextView tv = (TextView)v.findViewById(id);
            tv.setText(text);
    
            // the original code accesses private members here - again, we have to use the public interface
            result.setView(v);
            result.setDuration(duration);
    
            return result;
        }
    
        public static Toast makeText(Context context, int resId, int duration)
                throws Resources.NotFoundException {
            return makeText(context, context.getResources().getText(resId), duration);
        }
    
    }