Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugins crash with "Methods marked with @UiThread must be executed on the main thread." #34993

Closed
vincevargadev opened this issue Jun 24, 2019 · 38 comments

Comments

@vincevargadev
Copy link

vincevargadev commented Jun 24, 2019

There are multiple plugins that are breaking with the error message Methods marked with @UiThread must be executed on the main thread.. (See issues: https://github.com/flutter/flutter/issues?utf8=%E2%9C%93&q=%5C%40UiThread).

I didn't find any release notes, or guides for plugin authors to handle this issue (and to be honest, I don't 100% understand when it's necessary). Do you have any plans on updating the docs?

This issue is more like a question, or docs/release notes improvement request, and the examples below could serve for people who face the same issues. Also, I'm on dev channel, so maybe the issue (docs, release notes) can be fixed before you release to stable.

Crash

E/AndroidRuntime(31031): FATAL EXCEPTION: Thread-16
E/AndroidRuntime(31031): Process: com.smaho.mobile_app, PID: 31031
E/AndroidRuntime(31031): java.lang.RuntimeException: Methods marked with @UiThread must be executed on the main thread. Current thread: Thread-16
E/AndroidRuntime(31031): 	at io.flutter.embedding.engine.FlutterJNI.ensureRunningOnMainThread(FlutterJNI.java:605)
E/AndroidRuntime(31031): 	at io.flutter.embedding.engine.FlutterJNI.dispatchPlatformMessage(FlutterJNI.java:515)
E/AndroidRuntime(31031): 	at io.flutter.embedding.engine.dart.DartMessenger.send(DartMessenger.java:76)
E/AndroidRuntime(31031): 	at io.flutter.embedding.engine.dart.DartExecutor.send(DartExecutor.java:151)
E/AndroidRuntime(31031): 	at io.flutter.view.FlutterNativeView.send(FlutterNativeView.java:144)
E/AndroidRuntime(31031): 	at io.flutter.plugin.common.EventChannel$IncomingStreamRequestHandler$EventSinkImplementation.success(EventChannel.java:226)

Versions

$ flutter --version
Flutter 1.7.3 • channel dev • https://github.com/flutter/flutter.git
Framework • revision 362b999b90 (2 weeks ago) • 2019-06-07 12:43:27 -0700
Engine • revision 0602dbb275
Tools • Dart 2.3.2 (build 2.3.2-dev.0.1 6e0d978505)

How to solve to issue

I found some fixes already in the wild, so I just had to tweak the MethodChannel.Result solutions to be able to fix the EventSink issues.

The issues + solutions I found are here:

The code examples in this issue use Java and prefer clarity over lines of code. If you use Kotlin, a less verbose option might be possible.

They solved it by wrapping the MethodChannel.Result result with a "handler+looper+runnable combo":

 private static class MainThreadResult implements MethodChannel.Result {
    private MethodChannel.Result result;
    private Handler handler;

    MainThreadResult(MethodChannel.Result result) {
      this.result = result;
      handler = new Handler(Looper.getMainLooper());
    }

    @Override
    public void success(final Object result) {
      handler.post(
          new Runnable() {
            @Override
            public void run() {
              result.success(result);
            }
          });
    }

    @Override
    public void error(
        final String errorCode, final String errorMessage, final Object errorDetails) {
      handler.post(
          new Runnable() {
            @Override
            public void run() {
              result.error(errorCode, errorMessage, errorDetails);
            }
          });
    }

    @Override
    public void notImplemented() {
      handler.post(
          new Runnable() {
            @Override
            public void run() {
              result.notImplemented();
            }
          });
    }
  }

I was working with an EventChannel.EventSink eventSink, so I had to patch my plugin, too. The solution for the event sink was similar:

private static class MainThreadEventSink implements EventChannel.EventSink {
    private EventChannel.EventSink eventSink;
    private Handler handler;

    MainThreadEventSink(EventChannel.EventSink eventSink) {
      this.eventSink = eventSink;
      handler = new Handler(Looper.getMainLooper());
    }

    @Override
    public void success(final Object o) {
      handler.post(new Runnable() {
        @Override
        public void run() {
          eventSink.success(o);
        }
      });
    }

    @Override
    public void error(final String s, final String s1, final Object o) {
      handler.post(new Runnable() {
        @Override
        public void run() {
          eventSink.error(s, s1, o);
        }
      });
    }
}
@ymback
Copy link

ymback commented Jun 25, 2019

Use activity.runOnUiThread

@vincevargadev
Copy link
Author

vincevargadev commented Jun 25, 2019

And do you know why it changed, why it worked before?

@ymback
Copy link

ymback commented Jun 25, 2019

See this
flutter/engine@2c9e37c

@vincevargadev
Copy link
Author

vincevargadev commented Jun 25, 2019

Thanks @ymback, the messages in flutter/engine#8830 really helped understanding the context.

runOnUiThread is very convenient and a couple of lines shorter when you are working on your app's Android code (you are in class MainActivity : FlutterActivity()). For plugins, I find that the code above is slightly more convenient, as you don't need to "get" a reference to an activity (which is also not very difficult if your plugin code is simple).

@michael-cheung-thebus
Copy link

michael-cheung-thebus commented Jul 13, 2019

Thanks for this. This change also broke at least one more of the flutter team's own plugins (android_alarm_manager), and probably would have been immensely frustrating to figure out how to handle on my own.

@AndruByrne
Copy link

AndruByrne commented Jul 26, 2019

rxJava devs may find the familiar
.[subscribe/observe]On(Schedulers.from(new HandlerExecutor(Looper.getMainLooper())))
to be helpful here.

@tymur-l
Copy link

tymur-l commented Aug 5, 2019

I have to keep running some code in my plugin in a separate thread for UI not to block, but when I try to run the code, I get Methods marked with @UiThread must be executed on the main thread.

How do I fix this?

@MichealReed
Copy link

Experiencing same.. Downgrading flutter until it's fixed.

@AndruByrne
Copy link

AndruByrne commented Aug 7, 2019

Wow @ymback, nice link; missed it the first time. @Sitiritis and @michaelreed would likely appreciate the insight in the comments. tl;dr this change will not be reverted; it is protecting against indeterminant code that was likely crashing silently; it is improving your app, though it will not be obvious how, given that you are dealing with concurrency

@tymur-l
Copy link

tymur-l commented Aug 7, 2019

@AndruByrne, I don't quite get to use this insight. I am creating a thread in the Android plugin and then start it. What should I change?

@MichealReed
Copy link

@AndruByrne, could you help me understand how I can use an isolate with UIThread execution? From my basic understanding of them, I cannot (which probably explains why the alarm manager plugin is broken as well).

@amreniouinnovent
Copy link

amreniouinnovent commented Aug 10, 2019

I am using rxJava and here my solution

 .subscribeOn(Schedulers.newThread())
                        .observeOn(AndroidSchedulers.mainThread())

https://github.com/amorenew/ACS_NFC_Flutter/blob/master/android/src/main/java/com/acs/nfc/acs_nfc/AcsNfcPlugin.java

@pavel-ismailov
Copy link

pavel-ismailov commented Aug 11, 2019

First one from @vargavince91 works like a charm, but I did slightly shorter.
Same approach, but I don't want to use it everywhere.

public class BitcoinWalletPlugin implements MethodCallHandler, ... {

  private Handler uiThreadHandler = new Handler(Looper.getMainLooper());

  @Override
  public void onListen(Object o, EventChannel.EventSink eventSink) {
    switch (this.channel) {
      ...
      case CHANNEL_NAME:
        // before
        // bitcoinWallet.setOnStartupListener(() -> eventSink.success(true));

        // after
        bitcoinWallet.setOnStartupListener(() -> uiThreadHandler.post(() -> eventSink.success(true)));
        break;
    }
  }

}

@Sitiritis

I have to keep running some code in my plugin in a separate thread for UI not to block, but when I try to run the code, I get Methods marked with @UiThread must be executed on the main thread.

How do I fix this?

Make sure you are creating handler like that new Handler(Looper.getMainLooper());
Looper.getMainLooper() is the main point which make it works in UI thread

@MichealReed

@AndruByrne, could you help me understand how I can use an isolate with UIThread execution? From my basic understanding of them, I cannot (which probably explains why the alarm manager plugin is broken as well).

You don't need isolate here, just use new Handler(Looper.getMainLooper()); as first option OR save link to activity from here, and then use activity.runOnUiThread(..) everywhere as a second option

public static void registerWith(Registrar registrar) {

  activity = registrar.activity();

}

@MichealReed
Copy link

@pavel-ismailov I'm using isolates to do work in a background thread. I cannot do execution on the UI thread as UI is a foreground process.

@pavel-ismailov
Copy link

@MichealReed

Not sure if I understood you right, but looks like your schema is:

  • You go to background in Flutter (isolate)
  • From that thread you are calling native host
  • Native host do some work (maybe in own background)
  • Native host return it back to Flutter
  • As you were in background in Flutter, you wondering what the heck you need make calls events.success(result) in UI thread, because you want return result to your previous background thread (isolate)

If so - answer is I don't know, why, but you MUST do it, because if you will check that flutter source code

    private final class EventSinkImplementation implements EventChannel.EventSink {
      final AtomicBoolean hasEnded;

      private EventSinkImplementation() {
        this.hasEnded = new AtomicBoolean(false);
      }

      @UiThread
      public void success(Object event) {
        if (!this.hasEnded.get() && IncomingStreamRequestHandler.this.activeSink.get() == this) {
          EventChannel.this.messenger.send(EventChannel.this.name, EventChannel.this.codec.encodeSuccessEnvelope(event));
        }
      }

      @UiThread
      public void error(String errorCode, String errorMessage, Object errorDetails) {
        if (!this.hasEnded.get() && IncomingStreamRequestHandler.this.activeSink.get() == this) {
          EventChannel.this.messenger.send(EventChannel.this.name, EventChannel.this.codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
        }
      }

      @UiThread
      public void endOfStream() {
        if (!this.hasEnded.getAndSet(true) && IncomingStreamRequestHandler.this.activeSink.get() == this) {
          EventChannel.this.messenger.send(EventChannel.this.name, (ByteBuffer)null);
        }
      }
    }

You will see @UiThread annotations which force you call that methods only in UI thread.
Maybe you don't need isolates in Flutter code? Try to avoid it and only go background on native host.

@Purukitto
Copy link

I tried to implement a number of solutions from a number of threads but all show me some or the other error.
I tried both @vargavince91 and @pavel-ismailov 's methods but both give me the error that 'private' isn't a type and on removing/replacing 'private' I get 'Handler' isn't a type

I have been trying to build a music player and the plugin crashes when I play the music with Methods marked with @UiThread must be executed on the main thread.

Can anyone help me with the direction I should take. Is it a problem in the plugin's dart file or some problem in flutter itself?

If the plugin's code can help I will update the with the code.

Any assistance is appreciated

@MichealReed
Copy link

@pavel-ismailov I really feel that this is a huge oversight from the flutter development team, as mentioned before, they even broke their own example application for developing background services with flutter.

@matthew-carroll it would be great if you could chime in with some background as to why this change was made and if it will be reversed or fixed any time soon. Someone previously linked this commit flutter/engine@2c9e37c.

@amirh
Copy link
Contributor

amirh commented Aug 12, 2019

@matthew-carroll is there a specific plugin in flutter/plugins you're aware is still crashing with this? I believe all plugins that had this issue have been fixed.

@matthew-carroll
Copy link
Contributor

@MichealReed unfortunately, I don't have context around the linked file, but I'm not aware of any issue with using method channels with background isolates. The UI thread is an Android concept rather than a Flutter/Dart concept, so you manage it from the Android side. If you place your messaging code within Runnables that post to Android's UI thread, I think you should be fine. If you have a specific minimal code reproduction of an issue in that area, I would recommend filing a new bug and including the specific, minimal steps that we can take on our end to replicate the issue.

With regard to Activitys, that's a complicated issue that would be very difficult to answer in general. The idea of "using activities within isolates" sounds fishy because an Activity is something that exists in Android and an isolate is something that exists in Dart, so I'm not quite sure what really means. Can you elaborate?

@amirh No, I'm not aware of any further plugin issues. I tagged you because some earlier posts on this thread seemed to indicate continuing crashes, but if all the plugins have been fixed then I assume we're good.

@MichealReed
Copy link

MichealReed commented Aug 13, 2019

@matthew-carroll I will see if the maintainer of that repository can assist with a minimal code reproduction to open a new issue with. To elaborate, he found a work around using context that allowed him to create something similar to an activity with background execution cross platform via dart. Prior to this change, use of context was allowed, but from my basic understanding of android development, context does not provide functionality to run on the UI thread, so the change here blocks the use of that approach.

@matthew-carroll
Copy link
Contributor

@MichealReed please see this guide, you should be able to run code on the UI thread from anywhere:
https://flutter.dev/docs/development/platform-integration/platform-channels#channels-and-platform-threading

@appstute-pratik
Copy link

I have updated my flutter version, and issue got resolved.
Following is my flutter version details:
Flutter 1.7.8+hotfix.4 • channel stable • https://github.com/flutter/flutter.git Framework • revision 20e5931 (4 weeks ago) • 2019-07-18 20:04:33 -0700 Engine • revision fee001c93f Tools • Dart 2.4.0

@matthew-carroll
Copy link
Contributor

matthew-carroll commented Aug 19, 2019

I'm going to mark this issue as closed. Presently:

If there is more work to be done on this particular issue, feel free to re-open.

If similar, but distinct problems appear, please file new issues.

@devxpy
Copy link

devxpy commented Aug 30, 2019

Shameless plug: flutter-plugin-scaffold will protect you from this error (using the fix in OP), and other quirks faced when writing flutter plugins.

@vikramkapoor
Copy link

I'm going to mark this issue as closed. Presently:

If there is more work to be done on this particular issue, feel free to re-open.

If similar, but distinct problems appear, please file new issues.

I'm going to mark this issue as closed. Presently:

If there is more work to be done on this particular issue, feel free to re-open.

If similar, but distinct problems appear, please file new issues.

@matthew-carroll
I have a bluetooth button hooked up to my flutter application using an Isolate. Before this restriction it used to work fine when the phone screen was off. After this restriction was enforced the bluetooth button press started crashing the application. So I implemented the recommended method to switch to UI thread in the isolate to handle the button press. It fixed the crash but now the button presses don't work when the screen is off. Is there another work around that you can think of? This is major issue for our application. The button needs to work when the phone is in users pocket with the screen off.

@matthew-carroll
Copy link
Contributor

@vikramkapoor it depends why the button isn't working. The UI thread still exists and still executes even when the screen is off, so long as your app is still running. In general the issue you're describing is probably about Android background execution more than it is about Flutter. Are you running a Service to keep your app alive? Have you verified that your app is receiving the Bluetooth signal before it tries to send that signal to Flutter?

@vikramkapoor
Copy link

@matthew-carroll thank you for that answer as it makes me wonder why I moved bluetooth to background isolate in the first place if the UI thread executes when the screen is off. I have moved the bluetooth implementation back to the UI thread and it still works with the screen off. Flutter_blue plug-in that I am using doesn't seem to be running a service for Android background execution.

@matthew-carroll
Copy link
Contributor

@vikramkapoor Bluetooth is pretty difficult to get right. I'm not sure if you're dealing with standard Bluetooth or BLE, but I would recommend doing as much manual testing of edge cases as possible. I think an active audio connection probably lets your app keep priority so that it doesn't get killed, but with BLE I think you probably need an explicit foreground Service to ensure that your app isn't killed while connected to the device. Also, background restrictions and capabilities have changed pretty dramatically through recent versions of Android, so you'll need to carefully test manually across probably the last 4 versions of Android at least. These issues are well beyond the scope of Flutter, itself, but that's my recommendation as someone who used to work on Android apps with BLE integration.

@vikramkapoor
Copy link

@matthew-carroll I am using the BLE iTag button with the flutter_blue plugin. The application is able to respond to BLE notification upon button presses with the phone screen off even after the timers in flutter app stop triggering. So far I have only tested on Android 4.3, 6.0.1 though. It works fine on iOS. I plan to implement a foreground service once I learn how to do that.

@bravekingzhang
Copy link

这个解决方法很优雅,但是我觉得这个问题flutter框架该解决一下。

@chandankumarshanbhag
Copy link

Solution:

wrap the method invocation in
runOnUiThread(() -> {
result.success(identifier);
});

binglingziyu added a commit to binglingziyu/flutter_qiniu that referenced this issue Mar 20, 2020
binglingziyu added a commit to binglingziyu/flutter_qiniu that referenced this issue Mar 20, 2020
binglingziyu added a commit to binglingziyu/flutter_qiniu that referenced this issue Mar 20, 2020
binglingziyu added a commit to binglingziyu/flutter_qiniu that referenced this issue Mar 20, 2020
@Mostafa-SA-developer
Copy link

Solution:

wrap the method invocation in
runOnUiThread(() -> {
result.success(identifier);
});

worked for me thanx

@lock
Copy link

lock bot commented Apr 26, 2020

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@lock lock bot locked and limited conversation to collaborators Apr 26, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests