In my previous post, I've presented a Toast solution via a Handler and a Runnable. While that solution was a nice one and serves pretty well, I still got an exception. Therefore, I've decided to implement a builder-type wrapper to cover all situations:

  • Direct: just display a Toast,
  • Runnable: Build a custom Runnable to display a toast, apparently ideal for Services
  • AsyncTask: Use the onPostExecute() of an AsyncTask to display the Toast

The result can be used like:

new ToastBuilder()
  .onHandler(mHandler) // use a Handler (hence a Runnable)
  .message("Show a Toast from an AsyncTask")
  .withContext(this)
  .build();

The Full Code

At the time of writing, the code looks like this:

package com.laurivan.android.common.ui.runnables;

import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.util.Log;
import android.widget.Toast;

/**
  * A class capable of building a toast using different methods:
  * - a {@link java.lang.Runnable} + {@link android.os.Handler}
  * - an {@link android.os.AsyncTask}
  * - direct {@link android.widget.Toast}
  * <p/>
  * Created by ivanlla on 30/03/2015.
  */
public class ToastBuilder {
    private static final String TAG = ToastBuilder.class.getSimpleName();

    private Handler mHandler;
    private Context mContext;
    private String mMessage;
    private ToastType mToastType = ToastType.TOAST_INVALID;
    private int mToastDuration = Toast.LENGTH_SHORT;
    private int mDelay = 0;

    public ToastBuilder onHandler(Handler handler) {
        if (mToastType != ToastType.TOAST_INVALID)
            throw new IllegalStateException(
                String.format("Cannot set a new Toast type from %s",
                    mToastType));
        mToastType = ToastType.TOAST_RUNNABLE;
        mHandler = handler;
        return this;
    }

    public ToastBuilder withContext(Context context) {
        mContext = context.getApplicationContext();
        return this;
    }

    public ToastBuilder as(ToastType toastType) {
        this.mToastType = toastType;
        return this;
    }

    public ToastBuilder withDuration(int toastDuration) {
        this.mToastDuration = toastDuration;
        return this;
    }

    public ToastBuilder withDelay(int toastDelay) {
        if (mToastType != ToastType.TOAST_RUNNABLE)
            throw new IllegalStateException(
                String.format("Delay is invalid for %s. Only a runnable toast can be used.",
                    mToastType));
        this.mDelay = toastDelay;
        return this;
    }

    public ToastBuilder message(String message) {
        this.mMessage = message;
        return this;
    }

    public void build() {
        if (mMessage == null)
            throw new IllegalStateException("Message is null. Please set message for a Toast");
        if (mToastType == ToastType.TOAST_INVALID)
            throw new IllegalStateException("Unset toast type.");

        switch (mToastType) {
            case TOAST_RUNNABLE:
                if (mDelay > 0)
                    mHandler.postDelayed(new ToastRunnable(), mDelay);
                else
                    mHandler.post(new ToastRunnable());
                break;
            case TOAST_ASYNC_TASK:
                new ToastAsyncTask().execute();
                break;
            case TOAST_DIRECT:
                Toast.makeText(mContext, mMessage, mToastDuration).show();
                break;
            default:
                throw new IllegalStateException(
                    String.format("Unknown toast type: %s", mToastType));
        }
    }

    public enum ToastType {
        TOAST_INVALID,
        TOAST_RUNNABLE,
        TOAST_ASYNC_TASK,
        TOAST_DIRECT,
    }

    private class ToastRunnable implements Runnable {

        /**
          * Starts executing the active part of the class' code. This method is
          * called when a thread is started that has been created with a class which
          * implements {@code Runnable}.
          */
        @Override
        public void run() {
            Toast.makeText(mContext, mMessage, mToastDuration).show();
        }
    }

    private class ToastAsyncTask extends AsyncTask<Void, Void, Void> {

        /**
          * Override this method to perform a computation on a background thread. The
          * specified parameters are the parameters passed to {@link #execute}
          * by the caller of this task.
          * <p/>
          * This method can call {@link #publishProgress} to publish updates
          * on the UI thread.
          *
          * @param params The parameters of the task.
          * @return A result, defined by the subclass of this task.
          * @see #onPreExecute()
          * @see #onPostExecute
          * @see #publishProgress
          */
        @Override
        protected Void doInBackground(Void... params) {
            try {
                if (mDelay > 0)
                    Thread.sleep(mDelay);
            } catch (InterruptedException e) {
                Log.i(TAG, "Thread was interrupted. No big deal.");
            }
            return null;
        }

        /**
          * <p>Runs on the UI thread after {@link #doInBackground}. The
          * specified result is the value returned by {@link #doInBackground}.</p>
          * <p/>
          * <p>This method won't be invoked if the task was cancelled.</p>
          *
          * @param aVoid The result of the operation computed by {@link #doInBackground}.
          * @see #onPreExecute
          * @see #doInBackground
          * @see #onCancelled(Object)
          */
        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            Toast.makeText(mContext, mMessage, mToastDuration).show();
        }
    }
}

If it's useful, I'll publish it on Github.

HTH,