blob: 65ea2393d6cd43c138f9bed2f19ff59df4987736 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.plugin.common;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import io.flutter.BuildConfig;
import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler;
import io.flutter.plugin.common.BinaryMessenger.BinaryReply;
import java.nio.ByteBuffer;
/**
* A named channel for communicating with the Flutter application using asynchronous method calls.
*
* <p>Incoming method calls are decoded from binary on receipt, and Java results are encoded into
* binary before being transmitted back to Flutter. The {@link MethodCodec} used must be compatible
* with the one used by the Flutter application. This can be achieved by creating a <a
* href="https://docs.flutter.io/flutter/services/MethodChannel-class.html">MethodChannel</a>
* counterpart of this channel on the Dart side. The Java type of method call arguments and results
* is {@code Object}, but only values supported by the specified {@link MethodCodec} can be used.
*
* <p>The logical identity of the channel is given by its name. Identically named channels will
* interfere with each other's communication.
*/
public final class MethodChannel {
private static final String TAG = "MethodChannel#";
private final BinaryMessenger messenger;
private final String name;
private final MethodCodec codec;
/**
* Creates a new channel associated with the specified {@link BinaryMessenger} and with the
* specified name and the standard {@link MethodCodec}.
*
* @param messenger a {@link BinaryMessenger}.
* @param name a channel name String.
*/
public MethodChannel(BinaryMessenger messenger, String name) {
this(messenger, name, StandardMethodCodec.INSTANCE);
}
/**
* Creates a new channel associated with the specified {@link BinaryMessenger} and with the
* specified name and {@link MethodCodec}.
*
* @param messenger a {@link BinaryMessenger}.
* @param name a channel name String.
* @param codec a {@link MessageCodec}.
*/
public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
if (BuildConfig.DEBUG) {
if (messenger == null) {
Log.e(TAG, "Parameter messenger must not be null.");
}
if (name == null) {
Log.e(TAG, "Parameter name must not be null.");
}
if (codec == null) {
Log.e(TAG, "Parameter codec must not be null.");
}
}
this.messenger = messenger;
this.name = name;
this.codec = codec;
}
/**
* Invokes a method on this channel, expecting no result.
*
* @param method the name String of the method.
* @param arguments the arguments for the invocation, possibly null.
*/
@UiThread
public void invokeMethod(@NonNull String method, @Nullable Object arguments) {
invokeMethod(method, arguments, null);
}
/**
* Invokes a method on this channel, optionally expecting a result.
*
* <p>Any uncaught exception thrown by the result callback will be caught and logged.
*
* @param method the name String of the method.
* @param arguments the arguments for the invocation, possibly null.
* @param callback a {@link Result} callback for the invocation result, or null.
*/
@UiThread
public void invokeMethod(String method, @Nullable Object arguments, Result callback) {
messenger.send(
name,
codec.encodeMethodCall(new MethodCall(method, arguments)),
callback == null ? null : new IncomingResultHandler(callback));
}
/**
* Registers a method call handler on this channel.
*
* <p>Overrides any existing handler registration for (the name of) this channel.
*
* <p>If no handler has been registered, any incoming method call on this channel will be handled
* silently by sending a null reply. This results in a <a
* href="https://docs.flutter.io/flutter/services/MissingPluginException-class.html">MissingPluginException</a>
* on the Dart side, unless an <a
* href="https://docs.flutter.io/flutter/services/OptionalMethodChannel-class.html">OptionalMethodChannel</a>
* is used.
*
* @param handler a {@link MethodCallHandler}, or null to deregister.
*/
@UiThread
public void setMethodCallHandler(final @Nullable MethodCallHandler handler) {
messenger.setMessageHandler(
name, handler == null ? null : new IncomingMethodCallHandler(handler));
}
/**
* Adjusts the number of messages that will get buffered when sending messages to channels that
* aren't fully setup yet. For example, the engine isn't running yet or the channel's message
* handler isn't setup on the Dart side yet.
*/
public void resizeChannelBuffer(int newSize) {
BasicMessageChannel.resizeChannelBuffer(messenger, name, newSize);
}
/** A handler of incoming method calls. */
public interface MethodCallHandler {
/**
* Handles the specified method call received from Flutter.
*
* <p>Handler implementations must submit a result for all incoming calls, by making a single
* call on the given {@link Result} callback. Failure to do so will result in lingering Flutter
* result handlers. The result may be submitted asynchronously. Calls to unknown or
* unimplemented methods should be handled using {@link Result#notImplemented()}.
*
* <p>Any uncaught exception thrown by this method will be caught by the channel implementation
* and logged, and an error result will be sent back to Flutter.
*
* <p>The handler is called on the platform thread (Android main thread). For more details see
* <a href="https://github.com/flutter/engine/wiki/Threading-in-the-Flutter-Engine">Threading in
* the Flutter Engine</a>.
*
* @param call A {@link MethodCall}.
* @param result A {@link Result} used for submitting the result of the call.
*/
@UiThread
void onMethodCall(@NonNull MethodCall call, @NonNull Result result);
}
/**
* Method call result callback. Supports dual use: Implementations of methods to be invoked by
* Flutter act as clients of this interface for sending results back to Flutter. Invokers of
* Flutter methods provide implementations of this interface for handling results received from
* Flutter.
*
* <p>All methods of this class must be called on the platform thread (Android main thread). For
* more details see <a
* href="https://github.com/flutter/engine/wiki/Threading-in-the-Flutter-Engine">Threading in the
* Flutter Engine</a>.
*/
public interface Result {
/**
* Handles a successful result.
*
* @param result The result, possibly null. The result must be an Object type supported by the
* codec. For instance, if you are using StandardCodec (default), please see {@link
* StandardMessageCodec} documentation on what types are supported.
*/
@UiThread
void success(@Nullable Object result);
/**
* Handles an error result.
*
* @param errorCode An error code String.
* @param errorMessage A human-readable error message String, possibly null.
* @param errorDetails Error details, possibly null. The details must be an Object type
* supported by the codec. For instance, if you are using StandardCodec (default), please
* see {@link StandardMessageCodec} documentation on what types are supported.
*/
@UiThread
void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails);
/** Handles a call to an unimplemented method. */
@UiThread
void notImplemented();
}
private final class IncomingResultHandler implements BinaryReply {
private final Result callback;
IncomingResultHandler(Result callback) {
this.callback = callback;
}
@Override
@UiThread
public void reply(ByteBuffer reply) {
try {
if (reply == null) {
callback.notImplemented();
} else {
try {
callback.success(codec.decodeEnvelope(reply));
} catch (FlutterException e) {
callback.error(e.code, e.getMessage(), e.details);
}
}
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to handle method call result", e);
}
}
}
private final class IncomingMethodCallHandler implements BinaryMessageHandler {
private final MethodCallHandler handler;
IncomingMethodCallHandler(MethodCallHandler handler) {
this.handler = handler;
}
@Override
@UiThread
public void onMessage(ByteBuffer message, final BinaryReply reply) {
final MethodCall call = codec.decodeMethodCall(message);
try {
handler.onMethodCall(
call,
new Result() {
@Override
public void success(Object result) {
reply.reply(codec.encodeSuccessEnvelope(result));
}
@Override
public void error(String errorCode, String errorMessage, Object errorDetails) {
reply.reply(codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
}
@Override
public void notImplemented() {
reply.reply(null);
}
});
} catch (RuntimeException e) {
Log.e(TAG + name, "Failed to handle method call", e);
reply.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
}
}
}
}