diff --git a/android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java b/android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java index eb9d5bea0..ddacb7a48 100644 --- a/android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java +++ b/android/src/main/java/io/ably/flutter/plugin/AblyMethodCallHandler.java @@ -133,46 +133,13 @@ public AblyMethodCallHandler(final MethodChannel methodChannel, _map.put(PlatformConstants.PlatformMethod.cryptoGenerateRandomKey, this::cryptoGenerateRandomKey); } - // MethodChannel.Result wrapper that responds on the platform thread. - // - // Plugins crash with "Methods marked with @UiThread must be executed on the main thread." - // This happens while making network calls in thread other than main thread - // - // https://github.com/flutter/flutter/issues/34993#issue-459900986 - // https://github.com/aloisdeniel/flutter_geocoder/commit/bc34cfe473bfd1934fe098bb7053248b75200241 - private static class MethodResultWrapper implements MethodChannel.Result { - private final MethodChannel.Result methodResult; - private final Handler handler; - - MethodResultWrapper(MethodChannel.Result result) { - methodResult = result; - handler = new Handler(Looper.getMainLooper()); - } - - @Override - public void success(final Object result) { - handler.post(() -> methodResult.success(result)); - } - - @Override - public void error( - final String errorCode, final String errorMessage, final Object errorDetails) { - handler.post(() -> methodResult.error(errorCode, errorMessage, errorDetails)); - } - - @Override - public void notImplemented() { - handler.post(methodResult::notImplemented); - } - } - private void handleAblyException(@NonNull MethodChannel.Result result, @NonNull AblyException e) { result.error(Integer.toString(e.errorInfo.code), e.getMessage(), e.errorInfo); } @Override public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result rawResult) { - final MethodChannel.Result result = new MethodResultWrapper(rawResult); + final MethodChannel.Result result = new SingleTimeMethodResult(new MainThreadMethodResult(rawResult)); Log.v(TAG, String.format("onMethodCall: Ably Flutter platform method \"%s\" invoked.", call.method)); final BiConsumer handler = _map.get(call.method); if (null == handler) { @@ -183,7 +150,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result // We have a handler for a method with this name so delegate to it. handler.accept(call, result); } catch (Exception e) { - Log.e(TAG, String.format("\"%s\" platform method received error during invocation", call.method), e); + Log.e(TAG, String.format("\"%s\" platform method received error during invocation, caused by: %s", call.method, e.getMessage()), e); } } } diff --git a/android/src/main/java/io/ably/flutter/plugin/MainThreadMethodResult.java b/android/src/main/java/io/ably/flutter/plugin/MainThreadMethodResult.java new file mode 100644 index 000000000..bff69bf45 --- /dev/null +++ b/android/src/main/java/io/ably/flutter/plugin/MainThreadMethodResult.java @@ -0,0 +1,39 @@ +package io.ably.flutter.plugin; + +import android.os.Handler; +import android.os.Looper; + +import io.flutter.plugin.common.MethodChannel; + +// MethodChannel.Result wrapper that responds on the platform thread. +// +// Plugins crash with "Methods marked with @UiThread must be executed on the main thread." +// This happens while making network calls in thread other than main thread +// +// https://github.com/flutter/flutter/issues/34993#issue-459900986 +// https://github.com/aloisdeniel/flutter_geocoder/commit/bc34cfe473bfd1934fe098bb7053248b75200241 +public class MainThreadMethodResult implements MethodChannel.Result { + private final MethodChannel.Result methodResult; + private final Handler handler; + + MainThreadMethodResult(MethodChannel.Result result) { + methodResult = result; + handler = new Handler(Looper.getMainLooper()); + } + + @Override + public void success(final Object result) { + handler.post(() -> methodResult.success(result)); + } + + @Override + public void error( + final String errorCode, final String errorMessage, final Object errorDetails) { + handler.post(() -> methodResult.error(errorCode, errorMessage, errorDetails)); + } + + @Override + public void notImplemented() { + handler.post(methodResult::notImplemented); + } +} \ No newline at end of file diff --git a/android/src/main/java/io/ably/flutter/plugin/SingleTimeMethodResult.java b/android/src/main/java/io/ably/flutter/plugin/SingleTimeMethodResult.java new file mode 100644 index 000000000..599b156c2 --- /dev/null +++ b/android/src/main/java/io/ably/flutter/plugin/SingleTimeMethodResult.java @@ -0,0 +1,48 @@ +package io.ably.flutter.plugin; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.flutter.plugin.common.MethodChannel; + +/** + * Prevents multiple MethodChannel.Result invocation, because it leads to app crash + * @link https://github.com/flutter/flutter/issues/29092 + */ +public class SingleTimeMethodResult implements MethodChannel.Result { + private final MethodChannel.Result methodChannelResultInstance; + private final AtomicInteger invocationsCount = new AtomicInteger(0); + + public SingleTimeMethodResult(MethodChannel.Result methodChannelResultInstance) { + this.methodChannelResultInstance = methodChannelResultInstance; + } + + @Override + public void success(@Nullable Object result) { + if (invocationsCount.getAndIncrement() == 0) { + methodChannelResultInstance.success(result); + } else { + throw new IllegalStateException("Result shouldn't be called more than once"); + } + } + + @Override + public void error(@NonNull String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { + if (invocationsCount.getAndIncrement() == 0) { + methodChannelResultInstance.error(errorCode, errorMessage, errorDetails); + } else { + throw new IllegalStateException("Result shouldn't be called more than once"); + } + } + + @Override + public void notImplemented() { + if (invocationsCount.getAndIncrement() == 0) { + methodChannelResultInstance.notImplemented(); + } else { + throw new IllegalStateException("Result shouldn't be called more than once"); + } + } +}