- 
                Notifications
    You must be signed in to change notification settings 
- Fork 29.4k
Description
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);
        }
      });
    }
}