5

I need to make an Android toast from a processing thread, which is custom for OpenCV so I can't use runOnUiThread() as suggested here: Android: Toast in a thread.

Most of this code is from the CVCamera sample app. But those unfamiliar, when I select the Surf menu button, the SURFProcessor is called like so:

           else if (item.getTitle().equals("SURF")) {

                   defaultcallbackstack.addFirst(new SURFProcessor());
                   toasts(DIALOG_TUTORIAL_SURF, "");

           }

This Processor thread is run so that when I press the phone's camera button (capturePress = true), an image is taken and processing done. I want to call toasts method as shown:

class SURFProcessor implements NativeProcessor.PoolCallback {

           @Override
           public void process(int idx, image_pool pool, long timestamp,
                           NativeProcessor nativeProcessor) {
                   if(capturePress) {
                           String processMsg = processor.processFeatures(idx, pool, cvcamera.DETECT_SURF);
                           capturePress = false;
                           toasts(PROCESS_MESSAGE, processMsg);
                   }
           }
}

Here is the toasts method, located in the main class extending Activity:

void toasts(int id, String msg) {
           switch (id) {
           case PROCESS_MESSAGE:
                   Toast.makeText(MMRapp.this, msg, Toast.LENGTH_LONG).show();
                   break;
.........

Right now this code gives me an error: "can't create handler inside thread that has not called Looper.prepare()." How do I go about calling the toasts method? Or is it possible to have the toasts method listen for a change in processMsg? If possible, I can get by with sending the processMsg or changing a class variable instead. In essence, I need a String updated from this Processor thread.

Thank you very much, and I will provide more info/code if wanted.
-Tom

Community
  • 1
  • 1
wrapperapps
  • 937
  • 2
  • 18
  • 30

3 Answers3

6

use a handler and a runnable Make the Handler and runnable in the activity:

// these are members in the Activity class
Handler toastHandler = new Handler();
Runnable toastRunnable = new Runnable() {public void run() {Toast.makeText(Activity.this,...).show();}}

then to invoke it from your thread use

toastHandler.post(toastRunnable);

The handler executes the runnable in the thread it was created in.

siliconeagle
  • 7,379
  • 3
  • 29
  • 41
1

Use the overload that fit your needs.

/**
 * Muestra un toast sin necesidad de preocuparse de estar en el hilo de la
 * UI o no.
 *
 * @param mContext
 * @param sMessage
 */
public static void showToast(final Context mContext, final int nMessageId) {
    if (Utils.isUiThread()) {
        Toast.makeText(mContext.getApplicationContext(), nMessageId, Toast.LENGTH_LONG).show();
        return;
    }
    Runnable mRunnableToast = new Runnable() {
        @Override
        public void run() {
            Toast.makeText(mContext.getApplicationContext(), nMessageId, Toast.LENGTH_LONG).show();
        }
    };
    if (mContext instanceof Activity) {
        ((Activity) mContext).runOnUiThread(mRunnableToast);
        return;
    }
    Utils.runOnUiThread(mRunnableToast);
}

/**
 * Muestra un toast sin necesidad de preocuparse de estar en el hilo de la
 * UI o no.
 *
 * @param mContext
 * @param sMessage
 */
public static void showToast(final Context mContext, final CharSequence sMessage) {
    if (Utils.isUiThread()) {
        Toast.makeText(mContext.getApplicationContext(), sMessage, Toast.LENGTH_LONG).show();
        return;
    }
    Runnable mRunnableToast = new Runnable() {
        @Override
        public void run() {
            Toast.makeText(mContext.getApplicationContext(), sMessage, Toast.LENGTH_LONG).show();
        }
    };
    if (mContext instanceof Activity) {
        ((Activity) mContext).runOnUiThread(mRunnableToast);
        return;
    }
    Utils.runOnUiThread(mRunnableToast);
}

public static boolean isUiThread() {
    Looper mCurrentLooper = Looper.myLooper();
    if (mCurrentLooper == null) {
        return false;
    }
    if (mCurrentLooper.equals(Looper.getMainLooper())) {
        return true;
    }
    return false;
}

public static void runOnUiThread(Runnable mRunnable, Context mContext) {
    if (mContext instanceof Activity) {
        runOnUiThread(mRunnable, (Activity) mContext);
    } else {
        Utils.runOnUiThread(mRunnable);
    }
}

public static void runOnUiThread(Runnable mRunnable, View vView) {
    if (Utils.isUiThread()) {
        mRunnable.run();
    } else {
        vView.post(mRunnable);
    }
}

public static void runOnUiThread(Runnable mRunnable, Activity mActivity) {
    if (mActivity != null) {
        mActivity.runOnUiThread(mRunnable);
    } else {
        Utils.runOnUiThread(mRunnable);
    }
}

public static void runOnUiThread(Runnable mRunnable) {
    if (Utils.isUiThread()) {
        mRunnable.run();
    } else {
        Handler mUiHandler = new Handler(Looper.getMainLooper());
        mUiHandler.post(mRunnable);
    }
}
Reaper
  • 21
  • 1
  • For the benefit of others who are reading this question and your answer, could you please be more specific about how this solves the problem? Which of these should used for the case the original poster asked? – Mike Zavarello Jul 06 '16 at 11:12
  • 1
    Well, it's pretty simple. OP's problem is that he is trying to post toasts outside the UI thread and that is not allowed. My snippet takes care of that. Depending of which object does he have access to in his code (a BroadcastReceiver context, an Activity, an Application, etc) he can use one or other overload. Maybe this one: `public static void showToast(final Context mContext, final CharSequence sMessage)` – Reaper Jul 07 '16 at 10:26
  • This elaboration is very helpful. Thank you for expanding on this! – Mike Zavarello Jul 07 '16 at 11:19
0

Why not simply to use a broadcast reveiver?
Write it

public class ToastTrigger extends BroadcastReceiver {

    public static final String EXTRA_MESSAGE = "message";

    @Override
    public void onReceive(Context context, Intent intent) {
        Timber.d("ToastTrigger: received");
        if (intent.hasExtra(EXTRA_MESSAGE)) {
            Toast.makeText(context, intent.getStringExtra(EXTRA_MESSAGE), Toast.LENGTH_SHORT)
                .show();
        }
    }
}

Define it

    <receiver
        android:name=".receivers.ToastTrigger"
        android:enabled="true"
        android:exported="false">
        <intent-filter>
            <action android:name="com.example.TOAST" />
        </intent-filter>
    </receiver>

Trigger it

public void showMessage(String message) {
    Intent intent = new Intent();
    intent.setAction(getPackageName() + ".TOAST");
    intent.putExtra(ToastTrigger.EXTRA_MESSAGE, message);
    sendBroadcast(intent);
}
Vlad
  • 7,997
  • 3
  • 56
  • 43