From afb206d373da8edc30cdd0de43bfa495ece66ab6 Mon Sep 17 00:00:00 2001 From: Mikkel Nygaard Ravn Date: Wed, 29 Mar 2017 11:38:14 +0200 Subject: [PATCH] Flutter channel API cleanup (#3532) --- shell/platform/android/BUILD.gn | 1 + .../plugin/common/FlutterEventChannel.java | 213 ++++++++++ .../plugin/common/FlutterMethodChannel.java | 163 ++------ .../io/flutter/plugin/common/MethodCall.java | 2 +- .../plugin/editing/TextInputPlugin.java | 2 +- .../plugin/platform/PlatformPlugin.java | 3 +- .../Headers/FlutterBinaryMessenger.h | 65 ++- .../ios/framework/Headers/FlutterChannels.h | 370 ++++++++++++++++-- .../ios/framework/Headers/FlutterCodecs.h | 260 +++++++++++- .../ios/framework/Source/FlutterChannels.mm | 149 ++++--- .../ios/framework/Source/FlutterCodecs.mm | 20 +- .../framework/Source/FlutterPlatformPlugin.mm | 26 +- .../framework/Source/FlutterStandardCodec.mm | 42 +- .../Source/FlutterTextInputPlugin.mm | 14 +- .../framework/Source/FlutterViewController.mm | 9 +- .../Source/flutter_codecs_unittest.mm | 17 +- .../Source/flutter_standard_codec_unittest.mm | 29 +- travis/licenses_golden/licenses_flutter | 1 + 18 files changed, 1096 insertions(+), 290 deletions(-) create mode 100644 shell/platform/android/io/flutter/plugin/common/FlutterEventChannel.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index f7af41532..9995b7503 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -81,6 +81,7 @@ java_library("flutter_shell_java") { "io/flutter/plugin/common/ActivityLifecycleListener.java", "io/flutter/plugin/common/BinaryCodec.java", "io/flutter/plugin/common/FlutterException.java", + "io/flutter/plugin/common/FlutterEventChannel.java", "io/flutter/plugin/common/FlutterMessageChannel.java", "io/flutter/plugin/common/FlutterMethodChannel.java", "io/flutter/plugin/common/JSONMessageCodec.java", diff --git a/shell/platform/android/io/flutter/plugin/common/FlutterEventChannel.java b/shell/platform/android/io/flutter/plugin/common/FlutterEventChannel.java new file mode 100644 index 000000000..04a6aff4e --- /dev/null +++ b/shell/platform/android/io/flutter/plugin/common/FlutterEventChannel.java @@ -0,0 +1,213 @@ +// Copyright 2017 The Chromium 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 io.flutter.view.FlutterView; +import io.flutter.view.FlutterView.BinaryMessageResponse; +import io.flutter.view.FlutterView.OnBinaryMessageListenerAsync; + +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A named channel for communicating with the Flutter application using asynchronous + * event streams. + * + * Incoming requests for event stream setup are decoded from binary on receipt, and + * Java responses and events 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 PlatformEventChannel + * counterpart of this channel on the Flutter side. The Java type of responses and + * events is Object, but only values supported by the specified {@link MethodCodec} + * can be used. + * + * The identity of the channel is given by its name, so other uses of that name + * with may interfere with this channel's communication. + */ +public final class FlutterEventChannel { + private static final String TAG = "FlutterEventChannel#"; + + private final FlutterView view; + private final String name; + private final MethodCodec codec; + + /** + * Creates a new channel associated with the specified {@link FlutterView} and with the + * specified name and the standard {@link MethodCodec}. + * + * @param view a {@link FlutterView}. + * @param name a channel name String. + */ + public FlutterEventChannel(FlutterView view, String name) { + this(view, name, StandardMethodCodec.INSTANCE); + } + + /** + * Creates a new channel associated with the specified {@link FlutterView} and with the + * specified name and {@link MethodCodec}. + * + * @param view a {@link FlutterView}. + * @param name a channel name String. + * @param codec a {@link MessageCodec}. + */ + public FlutterEventChannel(FlutterView view, String name, MethodCodec codec) { + assert view != null; + assert name != null; + assert codec != null; + this.view = view; + this.name = name; + this.codec = codec; + } + + /** + * Registers a stream handler on this channel. + * + * Overrides any existing handler registration. + * + * @param handler a {@link StreamHandler}, or null to deregister. + */ + public void setStreamHandler(final StreamHandler handler) { + view.addOnBinaryMessageListenerAsync(name, + handler == null ? null : new StreamListener(handler)); + } + + /** + * Strategy for handling event streams. Supports dual use: + * Producers of events to be sent to Flutter act as clients of this interface + * for sending events. Consumers of events sent from Flutter implement + * this interface for handling received events. + */ + public interface EventSink { + /** + * Consumes a successful event. + * + * @param event The event, possibly null. + */ + void success(Object event); + + /** + * Consumes an error event. + * + * @param errorCode An error code String. + * @param errorMessage A human-readable error message String, possibly null. + * @param errorDetails Error details, possibly null + */ + void error(String errorCode, String errorMessage, Object errorDetails); + + /** + * Consumes end of stream. No calls to {@link #success(Object)} or + * {@link #error(String, String, Object)} will be made following a call + * to this method. + */ + void endOfStream(); + } + + /** + * A call-back interface for handling stream setup and tear-down requests. + */ + public interface StreamHandler { + /** + * Handles a request to set up an event stream. + * + * @param arguments Stream configuration arguments, possibly null. + * @param eventSink An {@link EventSink} for sending events to the Flutter receiver. + */ + void onListen(Object arguments, EventSink eventSink); + + /** + * Handles a request to tear down an event stream. + * + * @param arguments Stream configuration arguments, possibly null. + */ + void onCancel(Object arguments); + } + + private final class StreamListener implements OnBinaryMessageListenerAsync { + private final StreamHandler handler; + private final AtomicReference activeSink = new AtomicReference<>(null); + + StreamListener(StreamHandler handler) { + this.handler = handler; + } + + @Override + public void onMessage(FlutterView view, ByteBuffer message, + final BinaryMessageResponse response) { + final MethodCall call = codec.decodeMethodCall(message); + if (call.method.equals("listen")) { + onListen(call.arguments, response); + } else if (call.method.equals("cancel")) { + onCancel(call.arguments, response); + } else { + response.send(null); + } + } + + private void onListen(Object arguments, BinaryMessageResponse response) { + final EventSink eventSink = new EventSinkImplementation(); + if (activeSink.compareAndSet(null, eventSink)) { + try { + handler.onListen(arguments, eventSink); + response.send(codec.encodeSuccessEnvelope(null)); + } catch (Exception e) { + activeSink.set(null); + Log.e(TAG + name, "Failed to open event stream", e); + response.send(codec.encodeErrorEnvelope("error", e.getMessage(), null)); + } + } else { + response.send(codec.encodeErrorEnvelope("error", "Stream already active", null)); + } + } + + private void onCancel(Object arguments, BinaryMessageResponse response) { + final EventSink oldSink = activeSink.getAndSet(null); + if (oldSink != null) { + try { + handler.onCancel(arguments); + response.send(codec.encodeSuccessEnvelope(null)); + } catch (Exception e) { + Log.e(TAG + name, "Failed to close event stream", e); + response.send(codec.encodeErrorEnvelope("error", e.getMessage(), null)); + } + } else { + response.send(codec.encodeErrorEnvelope("error", "No active stream to cancel", null)); + } + } + + private final class EventSinkImplementation implements EventSink { + @Override + public void success(Object event) { + if (activeSink.get() != this) { + return; + } + FlutterEventChannel.this.view.sendBinaryMessage( + name, + codec.encodeSuccessEnvelope(event), + null); + } + + @Override + public void error(String errorCode, String errorMessage, + Object errorDetails) { + if (activeSink.get() != this) { + return; + } + FlutterEventChannel.this.view.sendBinaryMessage( + name, + codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails), + null); + } + + @Override + public void endOfStream() { + if (activeSink.get() != this) { + return; + } + FlutterEventChannel.this.view.sendBinaryMessage(name, null, null); + } + } + } +} diff --git a/shell/platform/android/io/flutter/plugin/common/FlutterMethodChannel.java b/shell/platform/android/io/flutter/plugin/common/FlutterMethodChannel.java index f8d4e2aa3..1a9e9f88d 100644 --- a/shell/platform/android/io/flutter/plugin/common/FlutterMethodChannel.java +++ b/shell/platform/android/io/flutter/plugin/common/FlutterMethodChannel.java @@ -10,11 +10,10 @@ import io.flutter.view.FlutterView.BinaryMessageReplyCallback; import io.flutter.view.FlutterView.BinaryMessageResponse; import io.flutter.view.FlutterView.OnBinaryMessageListenerAsync; import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicBoolean; /** * A named channel for communicating with the Flutter application using asynchronous - * method calls and event streams. + * method calls. * * 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 @@ -86,7 +85,7 @@ public final class FlutterMethodChannel { /** * Registers a method call handler on this channel. * - * Overrides any existing handler registration (for messages, method calls, or streams). + * Overrides any existing handler registration. * * @param handler a {@link MethodCallHandler}, or null to deregister. */ @@ -96,82 +95,45 @@ public final class FlutterMethodChannel { } /** - * Registers a stream handler on this channel. - * - * Overrides any existing handler registration (for messages, method calls, or streams). - * - * @param handler a {@link StreamHandler}, or null to deregister. - */ - public void setStreamHandler(final StreamHandler handler) { - view.addOnBinaryMessageListenerAsync(name, - handler == null ? null : new StreamListener(handler)); - } - - /** - * A call-back interface for handling incoming method calls. - */ - public interface MethodCallHandler { - - /** - * Handles the specified method call. - * - * @param call A {@link MethodCall}. - * @param response A {@link Response} for providing a single method call result. - */ - void onMethodCall(MethodCall call, Response response); - } - - /** - * A call-back interface for handling stream setup and teardown requests. - */ - public interface StreamHandler { - /** - * Handles a stream setup request. - * - * @param arguments Stream configuration arguments, possibly null. - * @param eventSink An {@link EventSink} used to emit events once the stream has been set - * up. - */ - void listen(Object arguments, EventSink eventSink); - - /** - * Handles a stream tear-down request. - * - * @param arguments Stream configuration arguments, possibly null. - */ - void cancel(Object arguments); - } - - /** - * Response interface for sending results back to Flutter. + * Strategy for handling the result of a method call. 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. */ public interface Response { /** - * Submits a successful result. + * Handles a successful result. * * @param result The result, possibly null. */ void success(Object result); /** - * Submits an error during message handling, an error result of a method call, or an error - * event. + * 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 */ void error(String errorCode, String errorMessage, Object errorDetails); + + /** + * Handles a call to an unimplemented method. + */ + void notImplemented(); } /** - * A {@link Response} supporting multiple results and which can be terminated. + * A call-back interface for handling incoming method calls. */ - public interface EventSink extends Response { + public interface MethodCallHandler { /** - * Signals that no more events will be emitted. + * Handles the specified method call. + * + * @param call A {@link MethodCall}. + * @param response A {@link Response} for providing a single method call result. */ - void done(); + void onMethodCall(MethodCall call, Response response); } private final class MethodCallResultCallback implements BinaryMessageReplyCallback { @@ -183,11 +145,15 @@ public final class FlutterMethodChannel { @Override public void onReply(ByteBuffer reply) { - try { - final Object result = codec.decodeEnvelope(reply); - handler.success(result); - } catch (FlutterException e) { - handler.error(e.code, e.getMessage(), e.details); + if (reply == null) { + handler.notImplemented(); + } else { + try { + final Object result = codec.decodeEnvelope(reply); + handler.success(result); + } catch (FlutterException e) { + handler.error(e.code, e.getMessage(), e.details); + } } } } @@ -222,6 +188,13 @@ public final class FlutterMethodChannel { done = true; } + @Override + public void notImplemented() { + checkDone(); + response.send(null); + done = true; + } + private void checkDone() { if (done) { throw new IllegalStateException("Call result already provided"); @@ -234,68 +207,4 @@ public final class FlutterMethodChannel { } } } - - private final class StreamListener implements OnBinaryMessageListenerAsync { - private final StreamHandler handler; - - StreamListener(StreamHandler handler) { - this.handler = handler; - } - - @Override - public void onMessage(FlutterView view, ByteBuffer message, - final BinaryMessageResponse response) { - final MethodCall call = codec.decodeMethodCall(message); - final AtomicBoolean cancelled = new AtomicBoolean(false); - if (call.method.equals("listen")) { - try { - handler.listen(call.arguments, new EventSink() { - @Override - public void success(Object event) { - if (cancelled.get()) { - return; - } - FlutterMethodChannel.this.view.sendBinaryMessage( - name, - codec.encodeSuccessEnvelope(event), - null); - } - - @Override - public void error(String errorCode, String errorMessage, - Object errorDetails) { - if (cancelled.get()) { - return; - } - FlutterMethodChannel.this.view.sendBinaryMessage( - name, - codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails), - null); - } - - @Override - public void done() { - if (cancelled.get()) { - return; - } - FlutterMethodChannel.this.view.sendBinaryMessage(name, null, null); - } - }); - response.send(codec.encodeSuccessEnvelope(null)); - } catch (Exception e) { - Log.e(TAG + name, "Failed to open event stream", e); - response.send(codec.encodeErrorEnvelope("error", e.getMessage(), null)); - } - } else if (call.method.equals("cancel")) { - cancelled.set(true); - try { - handler.cancel(call.arguments); - response.send(codec.encodeSuccessEnvelope(null)); - } catch (Exception e) { - Log.e(TAG + name, "Failed to close event stream", e); - response.send(codec.encodeErrorEnvelope("error", e.getMessage(), null)); - } - } - } - } } diff --git a/shell/platform/android/io/flutter/plugin/common/MethodCall.java b/shell/platform/android/io/flutter/plugin/common/MethodCall.java index 6bf49135c..6f3418e82 100644 --- a/shell/platform/android/io/flutter/plugin/common/MethodCall.java +++ b/shell/platform/android/io/flutter/plugin/common/MethodCall.java @@ -5,7 +5,7 @@ package io.flutter.plugin.common; /** - * Command object representing a method call on a {@link FlutterMessageChannel}. + * Command object representing a method call on a {@link FlutterMethodChannel}. */ public final class MethodCall { /** diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 69fb3a9ad..c6946289e 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -65,7 +65,7 @@ public class TextInputPlugin implements MethodCallHandler { clearTextInputClient(); response.success(null); } else { - response.error("unknown", "Unknown method: " + call.method, null); + response.notImplemented(); } } catch (JSONException e) { response.error("error", "JSON error: " + e.getMessage(), null); diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java index 0a36c8f16..0ffb00196 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java @@ -17,6 +17,7 @@ import android.view.HapticFeedbackConstants; import android.view.SoundEffectConstants; import android.view.View; +import io.flutter.plugin.common.FlutterMethodChannel; import io.flutter.util.PathUtils; import io.flutter.plugin.common.ActivityLifecycleListener; @@ -81,7 +82,7 @@ public class PlatformPlugin implements MethodCallHandler, ActivityLifecycleListe } else if (method.equals("PathProvider.getApplicationDocumentsDirectory")) { response.success(getPathProviderApplicationDocumentsDirectory()); } else { - response.error("unknown", "Unknown method: " + method, null); + response.notImplemented(); } } catch (JSONException e) { response.error("error", "JSON error: " + e.getMessage(), null); diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterBinaryMessenger.h b/shell/platform/darwin/ios/framework/Headers/FlutterBinaryMessenger.h index 6b0b2d1d5..619e14204 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterBinaryMessenger.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterBinaryMessenger.h @@ -2,28 +2,75 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef FLUTTER_FLUTTERBINARYMESSAGES_H_ -#define FLUTTER_FLUTTERBINARYMESSAGES_H_ +#ifndef FLUTTER_FLUTTERBINARYMESSENGER_H_ +#define FLUTTER_FLUTTERBINARYMESSENGER_H_ #import #include "FlutterMacros.h" -typedef void (^FlutterBinaryReplyHandler)(NSData* reply); +NS_ASSUME_NONNULL_BEGIN +/** + A strategy for handling a binary message reply. + */ +typedef void (^FlutterBinaryReplyHandler)(NSData* _Nullable reply); + +/** + A strategy for handling incoming binary messages and to send asynchronous + replies. + */ typedef void (^FlutterBinaryMessageHandler)( - NSData* message, + NSData* _Nullable message, FlutterBinaryReplyHandler replyHandler); +/** + A facility for communicating with the Flutter side using asynchronous message + passing with binary messages. + + - SeeAlso: + - `FlutterMessageChannel`, which supports communication using structured messages. + - `FlutterMethodChannel`, which supports communication using asynchronous method calls. + - `FlutterEventChannel`, which supports commuication using event streams. + */ FLUTTER_EXPORT @protocol FlutterBinaryMessenger -- (void)sendBinaryMessage:(NSData*)message channelName:(NSString*)channelName; +/** + Sends a binary message to the Flutter side on the specified channel, expecting + no reply. + + - Parameters: + - message: The message. + - channelName: The channel name. + */ +- (void)sendBinaryMessage:(NSData* _Nullable)message + channelName:(NSString*)channelName; + +/** + Sends a binary message to the Flutter side on the specified channel, expecting + an asynchronous reply. -- (void)sendBinaryMessage:(NSData*)message + - Parameters: + - message: The message. + - channelName: The channel name. + - handler: A reply handler. + */ +- (void)sendBinaryMessage:(NSData* _Nullable)message channelName:(NSString*)channelName binaryReplyHandler:(FlutterBinaryReplyHandler)handler; +/** + Registers a message handler for incoming binary messages from the Flutter side + on the specified channel. + + Replaces any existing handler. Use a `nil` handler for unregistering the + existing handler. + + - Parameters: + - channelName: The channel name. + - handler: The message handler. + */ - (void)setBinaryMessageHandlerOnChannel:(NSString*)channelName - binaryMessageHandler:(FlutterBinaryMessageHandler)handler; + binaryMessageHandler:(FlutterBinaryMessageHandler _Nullable)handler; @end - -#endif // FLUTTER_FLUTTERBINARYMESSAGES_H_ +NS_ASSUME_NONNULL_END +#endif // FLUTTER_FLUTTERBINARYMESSENGER_H_ diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h b/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h index 22e3c3261..a2f822ef7 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h @@ -8,48 +8,372 @@ #include "FlutterBinaryMessenger.h" #include "FlutterCodecs.h" +NS_ASSUME_NONNULL_BEGIN +/** + A strategy for handling a message reply. + + - Parameter reply: The reply. + */ typedef void (^FlutterReplyHandler)(id reply); + +/** + A strategy for handling a message. + + - Parameters: + - message: The incoming message. + - replyHandler: A call-back to asynchronously supply a reply to the message. + */ typedef void (^FlutterMessageHandler)(id message, FlutterReplyHandler replyHandler); +/** + A channel for communicating with the Flutter side using asynchronous message + passing. + */ FLUTTER_EXPORT @interface FlutterMessageChannel : NSObject -+ (instancetype)messageChannelNamed:(NSString*)name - binaryMessenger:(NSObject*)messenger - codec:(NSObject*)codec; +/** + Creates a `FlutterMessageChannel` with the specified name and binary messenger. + + The channel name logically identifies the channel; identically named channels + interfere with each other's communication. + + The binary messenger is a facility for sending raw, binary messages to the + Flutter side. This protocol is implemented by `FlutterViewController`. + + The channel uses `FlutterStandardMessageCodec` to encode and decode messages. + + - Parameters: + - name: The channel name. + - messenger: The binary messenger. + */ ++ (instancetype)messageChannelWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger; + +/** + Creates a `FlutterMessageChannel` with the specified name, binary messenger, + and message codec. + + The channel name logically identifies the channel; identically named channels + interfere with each other's communication. + + The binary messenger is a facility for sending raw, binary messages to the + Flutter side. This protocol is implemented by `FlutterViewController`. + + - Parameters: + - name: The channel name. + - messenger: The binary messenger. + - codec: The message codec. + */ ++ (instancetype)messageChannelWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec; + +/** + Initializes a `FlutterMessageChannel` with the specified nane, binary messenger, + and message codec. + + The channel name logically identifies the channel; identically named channels + interfere with each other's communication. + + The binary messenger is a facility for sending raw, binary messages to the + Flutter side. This protocol is implemented by `FlutterViewController`. + + - Parameters: + - name: The channel name. + - messenger: The binary messenger. + - codec: The message codec. + */ - (instancetype)initWithName:(NSString*)name binaryMessenger:(NSObject*)messenger codec:(NSObject*)codec; -- (void)sendMessage:(id)message; -- (void)sendMessage:(id)message replyHandler:(FlutterReplyHandler)handler; -- (void)setMessageHandler:(FlutterMessageHandler)handler; + +/** + Sends the specified message to the Flutter side, ignoring any reply. + + - Parameter message: The message. Must be supported by the codec of this channel. + */ +- (void)sendMessage:(id _Nullable)message; + +/** + Sends the specified message to the Flutter side, expecting an asynchronous reply. + + - Parameters: + - message: The message. Must be supported by the codec of this channel. + - handler: The reply handler. + */ +- (void)sendMessage:(id _Nullable)message replyHandler:(FlutterReplyHandler _Nullable)handler; + +/** + Registers a message handler with this channel. + + Replaces any existing handler. Use a `nil` handler for unregistering the + existing handler. + + - Parameter handler: The message handler. + */ +- (void)setMessageHandler:(FlutterMessageHandler _Nullable)handler; @end -typedef void (^FlutterResultReceiver)(id successResult, - FlutterError* errorResult); -typedef void (^FlutterEventReceiver)(id successEvent, - FlutterError* errorEvent, - BOOL done); +/** + A receiver of the result of a method call. + + - Parameter result: The result. Will be a `FlutterError` instance, if the method + call resulted in an error on the Flutter side. Will be + `FlutterMethodNotImplemented`, if the method called was not implemented on + the Flutter side. All other values, including `nil` should be interpreted + as successful results. + */ +typedef void (^FlutterResultReceiver)(id _Nullable result); + +/** + A strategy for handling method calls. + + - Parameters: + - call: The incoming method call. + - resultReceiver: A call-back to asynchronously supply the result of the call. + Invoke the call-back with a `FlutterError` to indicate that the call failed. + Invoke the call-back with `FlutterMethodNotImplemented` to indicate that the + method was unknown. Any other values, including `nil` are interpreted as + successful results. + */ typedef void (^FlutterMethodCallHandler)(FlutterMethodCall* call, FlutterResultReceiver resultReceiver); -typedef void (^FlutterStreamHandler)(FlutterMethodCall* call, - FlutterResultReceiver resultReceiver, - FlutterEventReceiver eventReceiver); +/** + A constant used with `FlutterMethodCallHandler` to respond to the call of an + unknown method. + */ +FLUTTER_EXPORT +extern NSObject const* FlutterMethodNotImplemented; + + +/** + A channel for communicating with the Flutter side using invocation of + asynchronous methods. + */ FLUTTER_EXPORT @interface FlutterMethodChannel : NSObject -+ (instancetype)methodChannelNamed:(NSString*)name - binaryMessenger:(NSObject*)messenger +/** + Creates a `FlutterMethodChannel` with the specified name and binary messenger. + + The channel name logically identifies the channel; identically named channels + interfere with each other's communication. + + The binary messenger is a facility for sending raw, binary messages to the + Flutter side. This protocol is implemented by `FlutterViewController`. + + The channel uses `FlutterStandardMethodCodec` to encode and decode method calls + and result envelopes. + + - Parameters: + - name: The channel name. + - messenger: The binary messenger. + */ ++ (instancetype)methodChannelWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger; + +/** + Creates a `FlutterMethodChannel` with the specified name, binary messenger, and + method codec. + + The channel name logically identifies the channel; identically named channels + interfere with each other's communication. + + The binary messenger is a facility for sending raw, binary messages to the + Flutter side. This protocol is implemented by `FlutterViewController`. + + - Parameters: + - name: The channel name. + - messenger: The binary messenger. + - codec: The method codec. + */ ++ (instancetype)methodChannelWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger codec:(NSObject*)codec; + +/** + Initializes a `FlutterMethodChannel` with the specified name, binary messenger, + and method codec. + + The channel name logically identifies the channel; identically named channels + interfere with each other's communication. + + The binary messenger is a facility for sending raw, binary messages to the + Flutter side. This protocol is implemented by `FlutterViewController`. + + - Parameters: + - name: The channel name. + - messenger: The binary messenger. + - codec: The method codec. + */ - (instancetype)initWithName:(NSString*)name - binaryMessenger:(NSObject*)messenger - codec:(NSObject*)codec; -- (void)invokeMethod:(NSString*)method arguments:(id)arguments; + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec; + +/** + Invokes the specified Flutter method with the specified arguments, expecting + no results. + + - Parameters: + - method: The name of the method to invoke. + - arguments: The arguments. Must be a value supported by the codec of this + channel. + */ +- (void)invokeMethod:(NSString*)method arguments:(id _Nullable)arguments; + +/** + Invokes the specified Flutter method with the specified arguments, expecting + an asynchronous result. + + - Parameters: + - method: The name of the method to invoke. + - arguments: The arguments. Must be a value supported by the codec of this + channel. + - resultReceiver: A call-back for receipt of an asynchronous result. + The result will be a `FlutterError` instance, if the method call resulted + in an error on the Flutter side. Will be `FlutterMethodNotImplemented`, if + the method called was not implemented on the Flutter side. Any other value, + including `nil` should be interpreted as successful results. + */ - (void)invokeMethod:(NSString*)method - arguments:(id)arguments - resultReceiver:(FlutterResultReceiver)resultReceiver; -- (void)setMethodCallHandler:(FlutterMethodCallHandler)handler; -- (void)setStreamHandler:(FlutterStreamHandler)handler; + arguments:(id _Nullable)arguments + resultReceiver:(FlutterResultReceiver _Nullable)resultReceiver; + +/** + Registers a handler for method calls from the Flutter side. + + Replaces any existing handler. Use a `nil` handler for unregistering the + existing handler. + + - Parameter handler: The method call handler. + */ +- (void)setMethodCallHandler:(FlutterMethodCallHandler _Nullable)handler; +@end + +/** + A strategy for consuming events. + + - Parameter event: The event. Will be a `FlutterError` instance, if the + event represents an error. Will be `FlutterEndOfEventStream`, if no more + events will be emitted. All other values, including `nil` should be + interpreted as success events. + */ +typedef void (^FlutterEventReceiver)(id _Nullable event); + +/** + A strategy for exposing an event stream to the Flutter side. + */ +FLUTTER_EXPORT +@protocol FlutterStreamHandler +/** + Sets up an event stream and begin emitting events. + + Invoked when the first listener is registered with the Stream associated to + this channel on the Flutter side. + + - Parameters: + - arguments: Arguments for the stream. + - eventReceiver: A call-back to asynchronously emit events. Invoke the + call-back with a `FlutterError` to emit an error event. Invoke the + call-back with `FlutterEndOfEventStream` to indicate that no more + events will be emitted. Any other value, including `nil` are emitted as + successful events. + - Returns: A FlutterError instance, if setup fails. + */ +- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments + eventReceiver:(FlutterEventReceiver)eventReceiver; + +/** + Tears down an event stream. + + Invoked when the last listener is deregistered from the Stream associated to + this channel on the Flutter side. + + - Parameter arguments: Arguments for the stream. + - Returns: A FlutterError instance, if teardown fails. + */ +- (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments; +@end + +/** + A constant used with `FlutterEventChannel` to indicate end of stream. + */ +FLUTTER_EXPORT +extern NSObject const* FlutterEndOfEventStream; + +/** + A channel for communicating with the Flutter side using event streams. + */ +FLUTTER_EXPORT +@interface FlutterEventChannel : NSObject +/** + Creates a `FlutterEventChannel` with the specified name and binary messenger. + + The channel name logically identifies the channel; identically named channels + interfere with each other's communication. + + The binary messenger is a facility for sending raw, binary messages to the + Flutter side. This protocol is implemented by `FlutterViewController`. + + The channel uses `FlutterStandardMethodCodec` to decode stream setup and + teardown requests, and to encode event envelopes. + + - Parameters: + - name: The channel name. + - messenger: The binary messenger. + - codec: The method codec. + */ ++ (instancetype)eventChannelWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger; + +/** + Creates a `FlutterEventChannel` with the specified name, binary messenger, + and method codec. + + The channel name logically identifies the channel; identically named channels + interfere with each other's communication. + + The binary messenger is a facility for sending raw, binary messages to the + Flutter side. This protocol is implemented by `FlutterViewController`. + + - Parameters: + - name: The channel name. + - messenger: The binary messenger. + - codec: The method codec. + */ ++ (instancetype)eventChannelWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec; + +/** + Initializes a `FlutterEventChannel` with the specified name, binary messenger, + and method codec. + + The channel name logically identifies the channel; identically named channels + interfere with each other's communication. + + The binary messenger is a facility for sending raw, binary messages to the + Flutter side. This protocol is implemented by `FlutterViewController`. + + - Parameters: + - name: The channel name. + - messenger: The binary messenger. + - codec: The method codec. + */ +- (instancetype)initWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec; +/** + Registers a handler for stream setup requests from the Flutter side. + + Replaces any existing handler. Use a `nil` handler for unregistering the + existing handler. + + - Parameter handler: The stream handler. + */ +- (void)setStreamHandler:(NSObject* _Nullable)streamHandler; @end +NS_ASSUME_NONNULL_END #endif // FLUTTER_FLUTTERCHANNELS_H_ diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h b/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h index 2ac6359a5..f16ceaa63 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterCodecs.h @@ -8,47 +8,148 @@ #import #include "FlutterMacros.h" +NS_ASSUME_NONNULL_BEGIN + +/** + A message encoding/decoding mechanism. + */ FLUTTER_EXPORT @protocol FlutterMessageCodec +/** + Returns a shared instance of this `FlutterMessageCodec`. + */ + (instancetype)sharedInstance; -- (NSData*)encode:(id)message; -- (id)decode:(NSData*)message; + +/** + Encodes the specified message into binary. + + - Parameter message: The message. + - Returns: The binary encoding, or `nil`, if `message` was `nil`. + */ +- (NSData* _Nullable)encode:(id _Nullable)message; + +/** + Decodes the specified message from binary. + + - Parameter message: The message. + - Returns: The decoded message, or `nil`, if `message` was `nil`. + */ +- (id _Nullable)decode:(NSData* _Nullable)message; @end +/** + A `FlutterMessageCodec` using unencoded binary messages, represented as + `NSData` instances. + */ FLUTTER_EXPORT @interface FlutterBinaryCodec : NSObject @end +/** + A `FlutterMessageCodec` using UTF-8 encoded `NSString` messages. + */ FLUTTER_EXPORT @interface FlutterStringCodec : NSObject @end +/** + A `FlutterMessageCodec` using UTF-8 encoded JSON messages. + + Supports the same values as `NSJSONSerialization`. + */ FLUTTER_EXPORT @interface FlutterJSONMessageCodec : NSObject @end +/** + A `FlutterMessageCodec` using the Flutter standard binary encoding. + + The standard encoding is guaranteed to be compatible with the corresponding + standard codec for PlatformMessageChannels on the Flutter side. These parts + of the Flutter SDK are evolved synchronously. + + Supported messages are acyclic values of these forms: + + - `nil` or `NSNull` + - `NSNumber` (including their representation of Boolean values) + - `FlutterStandardBigInteger` + - `FlutterStandardTypedData` + - `NSString` + - `NSArray` of supported values + - `NSDictionary` with supported keys and values + */ FLUTTER_EXPORT @interface FlutterStandardMessageCodec : NSObject @end +/** + Command object representing a method call on a `FlutterMethodChannel`. + */ FLUTTER_EXPORT @interface FlutterMethodCall : NSObject +/** + Creates a method call for invoking the specified named method with the + specified arguments. + + - Parameters: + - method: the name of the method to call. + - arguments: the arguments value. + */ + (instancetype)methodCallWithMethodName:(NSString*)method - arguments:(id)arguments; + arguments:(id _Nullable)arguments; + +/** + The method name. + */ @property(readonly, nonatomic) NSString* method; -@property(readonly, nonatomic) id arguments; + +/** + The arguments. + */ +@property(readonly, nonatomic, nullable) id arguments; @end +/** + Error object representing an unsuccessful outcome of invoking a method + on a `FlutterMethodChannel`, or an error event on a `FlutterEventChannel`. + */ FLUTTER_EXPORT @interface FlutterError : NSObject +/** + Creates a `FlutterError` with the specified error code, message, and details. + + - Parameters: + - code: An error code string for programmatic use. + - message: A human-readable error message. + - details: Custom error details. + */ + (instancetype)errorWithCode:(NSString*)code - message:(NSString*)message - details:(id)details; + message:(NSString* _Nullable)message + details:(id _Nullable)details; +/** + The error code. + */ @property(readonly, nonatomic) NSString* code; -@property(readonly, nonatomic) NSString* message; -@property(readonly, nonatomic) id details; + +/** + The error message. + */ +@property(readonly, nonatomic, nullable) NSString* message; + +/** + The error details. + */ +@property(readonly, nonatomic, nullable) id details; @end +/** + Type of numeric data items encoded in a `FlutterStandardDataType`. + + - FlutterStandardDataTypeUInt8: plain bytes + - FlutterStandardDataTypeInt32: 32-bit signed integers + - FlutterStandardDataTypeInt64: 64-bit signed integers + - FlutterStandardDataTypeFloat64: 64-bit floats + */ typedef NS_ENUM(NSInteger, FlutterStandardDataType) { FlutterStandardDataTypeUInt8, FlutterStandardDataTypeInt32, @@ -56,40 +157,179 @@ typedef NS_ENUM(NSInteger, FlutterStandardDataType) { FlutterStandardDataTypeFloat64, }; +/** + A byte buffer holding `UInt8`, `SInt32`, `SInt64`, or `Float64` values, used + with `FlutterStandardMessageCodec` and `FlutterStandardMethodCodec`. + + Two's complement encoding is used for signed integers. IEEE754 + double-precision representation is used for floats. The platform's native + endianness is assumed. + */ FLUTTER_EXPORT @interface FlutterStandardTypedData : NSObject +/** + Creates a `FlutterStandardTypedData` which interprets the specified data + as plain bytes. + + - Parameter data: the byte data. + */ + (instancetype)typedDataWithBytes:(NSData*)data; + +/** + Creates a `FlutterStandardTypedData` which interprets the specified data + as 32-bit signed integers. + + - Parameter data: the byte data. The length must be divisible by 4. + */ + (instancetype)typedDataWithInt32:(NSData*)data; + +/** + Creates a `FlutterStandardTypedData` which interprets the specified data + as 64-bit signed integers. + + - Parameter data: the byte data. The length must be divisible by 8. + */ + (instancetype)typedDataWithInt64:(NSData*)data; + +/** + Creates a `FlutterStandardTypedData` which interprets the specified data + as 64-bit floats. + + - Parameter data: the byte data. The length must be divisible by 8. + */ + (instancetype)typedDataWithFloat64:(NSData*)data; + +/** + The raw underlying data buffer. + */ @property(readonly, nonatomic) NSData* data; + +/** + The type of the encoded values. + */ @property(readonly, nonatomic) FlutterStandardDataType type; + +/** + The number of value items encoded. + */ @property(readonly, nonatomic) UInt32 elementCount; + +/** + The number of bytes used by the encoding of a single value item. + */ @property(readonly, nonatomic) UInt8 elementSize; @end +/** + An arbitrarily large integer value, used with `FlutterStandardMessageCodec` + and `FlutterStandardMethodCodec`. + */ FLUTTER_EXPORT @interface FlutterStandardBigInteger : NSObject +/** + Creates a `FlutterStandardBigInteger` from a hexadecimal representation. + + - Parameter hex: a hexadecimal string. + */ + (instancetype)bigIntegerWithHex:(NSString*)hex; + +/** + The hexadecimal string representation of this integer. + */ @property(readonly, nonatomic) NSString* hex; @end +/** + A codec for method calls and enveloped results. + + Method calls are encoded as binary messages with enough structure that the + codec can extract a method name `NSString` and an arguments `NSObject`, + possibly `nil`. These data items are used to populate a `FlutterMethodCall`. + + Result envelopes are encoded as binary messages with enough structure that + the codec can determine whether the result was successful or an error. In + the former case, the codec can extract the result `NSObject`, possibly `nil`. + In the latter case, the codec can extract an error code `NSString`, a + human-readable `NSString` error message (possibly `nil`), and a custom + error details `NSObject`, possibly `nil`. These data items are used to + populate a `FlutterError`. + */ FLUTTER_EXPORT @protocol FlutterMethodCodec +/** + Provides access to a shared instance this codec. + + - Returns: The shared instance. + */ + (instancetype)sharedInstance; + +/** + Encodes the specified method call into binary. + + - Parameter methodCall: The method call. The arguments value + must be supported by this codec. + - Returns: The binary encoding. + */ - (NSData*)encodeMethodCall:(FlutterMethodCall*)methodCall; + +/** + Decodes the specified method call from binary. + + - Parameter methodCall: The method call to decode. + - Returns: The decoded method call. + */ - (FlutterMethodCall*)decodeMethodCall:(NSData*)methodCall; -- (NSData*)encodeSuccessEnvelope:(id)result; + +/** + Encodes the specified successful result into binary. + + - Parameter result: The result. Must be a value supported by this codec. + - Returns: The binary encoding. + */ +- (NSData*)encodeSuccessEnvelope:(id _Nullable)result; + +/** + Encodes the specified error result into binary. + + - Parameter error: The error object. The error details value must be supported + by this codec. + - Returns: The binary encoding. + */ - (NSData*)encodeErrorEnvelope:(FlutterError*)error; -- (id)decodeEnvelope:(NSData*)envelope error:(FlutterError**)error; + +/** + Deccodes the specified result envelope from binary. + + - Parameter error: The error object. + - Returns: The result value, if the envelope represented a successful result, + or a `FlutterError` instance, if not. + */ +- (id _Nullable)decodeEnvelope:(NSData*)envelope; @end +/** + A `FlutterMethodCodec` using UTF-8 encoded JSON method calls and result + envelopes. Values supported as methods arguments and result payloads are + those supported as top-level or leaf values by `FlutterJSONMessageCodec`. + */ FLUTTER_EXPORT @interface FlutterJSONMethodCodec : NSObject @end +/** + A `FlutterMethodCodec` using the Flutter standard binary encoding. + + The standard codec is guaranteed to be compatible with the corresponding + standard codec for PlatformMethodChannels on the Flutter side. These parts of + the Flutter SDK are evolved synchronously. + + Values supported as method arguments and result payloads are those supported by + `FlutterStandardMessageCodec`. + */ FLUTTER_EXPORT @interface FlutterStandardMethodCodec : NSObject @end +NS_ASSUME_NONNULL_END + #endif // FLUTTER_FLUTTERCODECS_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterChannels.mm b/shell/platform/darwin/ios/framework/Source/FlutterChannels.mm index a15e4edcd..040e5ef10 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterChannels.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterChannels.mm @@ -11,9 +11,16 @@ NSString* _name; NSObject* _codec; } -+ (instancetype)messageChannelNamed:(NSString*)name - binaryMessenger:(NSObject*)messenger - codec:(NSObject*)codec { ++ (instancetype)messageChannelWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger { + NSObject* codec = [FlutterStandardMessageCodec sharedInstance]; + return [FlutterMessageChannel messageChannelWithName:name + binaryMessenger:messenger + codec:codec]; +} ++ (instancetype)messageChannelWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec { return [[[FlutterMessageChannel alloc] initWithName:name binaryMessenger:messenger codec:codec] autorelease]; @@ -22,11 +29,11 @@ - (instancetype)initWithName:(NSString*)name binaryMessenger:(NSObject*)messenger codec:(NSObject*)codec { - if (self = [super init]) { - _name = [name retain]; - _messenger = [messenger retain]; - _codec = [codec retain]; - } + self = [super init]; + NSAssert(self, @"Super init cannot be nil"); + _name = [name retain]; + _messenger = [messenger retain]; + _codec = [codec retain]; return self; } @@ -83,11 +90,11 @@ message:(NSString*)message details:(id)details { NSAssert(code, @"Code cannot be nil"); - if (self = [super init]) { - _code = [code retain]; - _message = [message retain]; - _details = [details retain]; - } + self = [super init]; + NSAssert(self, @"Super init cannot be nil"); + _code = [code retain]; + _message = [message retain]; + _details = [details retain]; return self; } @@ -125,10 +132,10 @@ - (instancetype)initWithMethodName:(NSString*)method arguments:(id)arguments { NSAssert(method, @"Method name cannot be nil"); - if (self = [super init]) { - _method = [method retain]; - _arguments = [arguments retain]; - } + self = [super init]; + NSAssert(self, @"Super init cannot be nil"); + _method = [method retain]; + _arguments = [arguments retain]; return self; } @@ -154,13 +161,23 @@ } @end +NSObject const* FlutterMethodNotImplemented = [NSObject new]; + @implementation FlutterMethodChannel { NSObject* _messenger; NSString* _name; NSObject* _codec; } -+ (instancetype)methodChannelNamed:(NSString*)name ++ (instancetype)methodChannelWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger { + NSObject* codec = [FlutterStandardMethodCodec sharedInstance]; + return [FlutterMethodChannel methodChannelWithName:name + binaryMessenger:messenger + codec:codec]; +} + ++ (instancetype)methodChannelWithName:(NSString*)name binaryMessenger:(NSObject*)messenger codec:(NSObject*)codec { return [[[FlutterMethodChannel alloc] initWithName:name @@ -171,11 +188,11 @@ - (instancetype)initWithName:(NSString*)name binaryMessenger:(NSObject*)messenger codec:(NSObject*)codec { - if (self = [super init]) { - _name = [name retain]; - _messenger = [messenger retain]; - _codec = [codec retain]; - } + self = [super init]; + NSAssert(self, @"Super init cannot be nil"); + _name = [name retain]; + _messenger = [messenger retain]; + _codec = [codec retain]; return self; } @@ -201,9 +218,7 @@ NSData* message = [_codec encodeMethodCall:methodCall]; FlutterBinaryReplyHandler replyHandler = ^(NSData* reply) { if (resultReceiver) { - FlutterError* flutterError = nil; - id result = [_codec decodeEnvelope:reply error:&flutterError]; - resultReceiver(result, flutterError); + resultReceiver([_codec decodeEnvelope:reply]); } }; [_messenger sendBinaryMessage:message @@ -220,9 +235,11 @@ FlutterBinaryMessageHandler messageHandler = ^(NSData* message, FlutterBinaryReplyHandler reply) { FlutterMethodCall* call = [_codec decodeMethodCall:message]; - handler(call, ^(id result, FlutterError* error) { - if (error) - reply([_codec encodeErrorEnvelope:error]); + handler(call, ^(id result) { + if (result == FlutterMethodNotImplemented) + reply(nil); + else if ([result isKindOfClass:[FlutterError class]]) + reply([_codec encodeErrorEnvelope:(FlutterError*)result]); else reply([_codec encodeSuccessEnvelope:result]); }); @@ -230,8 +247,45 @@ [_messenger setBinaryMessageHandlerOnChannel:_name binaryMessageHandler:messageHandler]; } +@end + +#pragma mark - Event channel + +NSObject const* FlutterEndOfEventStream = [NSObject new]; + +@implementation FlutterEventChannel { + NSObject* _messenger; + NSString* _name; + NSObject* _codec; +} ++ (instancetype)eventChannelWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger { + NSObject* codec = [FlutterStandardMethodCodec sharedInstance]; + return [FlutterEventChannel eventChannelWithName:name + binaryMessenger:messenger + codec:codec]; +} + ++ (instancetype)eventChannelWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec { + return [[[FlutterEventChannel alloc] initWithName:name + binaryMessenger:messenger + codec:codec] autorelease]; +} + +- (instancetype)initWithName:(NSString*)name + binaryMessenger:(NSObject*)messenger + codec:(NSObject*)codec { + self = [super init]; + NSAssert(self, @"Super init cannot be nil"); + _name = [name retain]; + _messenger = [messenger retain]; + _codec = [codec retain]; + return self; +} -- (void)setStreamHandler:(FlutterStreamHandler)handler { +- (void)setStreamHandler:(NSObject*)handler { if (!handler) { [_messenger setBinaryMessageHandlerOnChannel:_name binaryMessageHandler:nil]; @@ -240,24 +294,31 @@ FlutterBinaryMessageHandler messageHandler = ^( NSData* message, FlutterBinaryReplyHandler reply) { FlutterMethodCall* call = [_codec decodeMethodCall:message]; - FlutterResultReceiver resultReceiver = ^(id result, FlutterError* error) { + if ([call.method isEqual:@"listen"]) { + FlutterEventReceiver eventReceiver = ^(id event) { + if (event == FlutterEndOfEventStream) + [_messenger sendBinaryMessage:nil channelName:_name]; + else if ([event isKindOfClass: [FlutterError class]]) + [_messenger sendBinaryMessage:[_codec encodeErrorEnvelope:(FlutterError*)event] + channelName:_name]; + else + [_messenger sendBinaryMessage:[_codec encodeSuccessEnvelope:event] + channelName:_name]; + }; + FlutterError* error = [handler onListenWithArguments:call.arguments eventReceiver:eventReceiver]; if (error) reply([_codec encodeErrorEnvelope:error]); else reply([_codec encodeSuccessEnvelope:nil]); - }; - FlutterEventReceiver eventReceiver = - ^(id event, FlutterError* error, BOOL done) { - if (error) - [_messenger sendBinaryMessage:[_codec encodeErrorEnvelope:error] - channelName:_name]; - else if (done) - [_messenger sendBinaryMessage:[NSData data] channelName:_name]; - else - [_messenger sendBinaryMessage:[_codec encodeSuccessEnvelope:event] - channelName:_name]; - }; - handler(call, resultReceiver, eventReceiver); + } else if ([call.method isEqual:@"cancel"]) { + FlutterError* error = [handler onCancelWithArguments:call.arguments]; + if (error) + reply([_codec encodeErrorEnvelope:error]); + else + reply([_codec encodeSuccessEnvelope:nil]); + } else { + reply(nil); + } }; [_messenger setBinaryMessageHandlerOnChannel:_name binaryMessageHandler:messageHandler]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterCodecs.mm b/shell/platform/darwin/ios/framework/Source/FlutterCodecs.mm index 6e0af54ee..e73669c6f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterCodecs.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterCodecs.mm @@ -14,10 +14,14 @@ } - (NSData*)encode:(NSData*)message { + if (!message.length) + return nil; return message; } - (NSData*)decode:(NSData*)message { + if (!message.length) + return nil; return message; } @end @@ -32,14 +36,15 @@ } - (NSData*)encode:(NSString*)message { - if (!message.length) { - return [NSData data]; - } + if (!message.length) + return nil; const char* utf8 = message.UTF8String; return [NSData dataWithBytes:utf8 length:strlen(utf8)]; } - (NSString*)decode:(NSData*)message { + if (!message.length) + return nil; return [[[NSString alloc] initWithData:message encoding:NSUTF8StringEncoding] autorelease]; } @@ -55,6 +60,8 @@ } - (NSData*)encode:(id)message { + if (message == nil) + return nil; NSData* encoding = [NSJSONSerialization dataWithJSONObject:message options:0 error:nil]; NSAssert(encoding, @"Invalid JSON message, encoding failed"); @@ -62,6 +69,8 @@ } - (id)decode:(NSData*)message { + if (!message.length) + return nil; id decoded = [NSJSONSerialization JSONObjectWithData:message options:0 error:nil]; NSAssert(decoded, @"Invalid JSON message, decoding failed"); @@ -107,14 +116,13 @@ return [FlutterMethodCall methodCallWithMethodName:method arguments:arguments]; } -- (id)decodeEnvelope:(NSData*)envelope error:(FlutterError**)error { +- (id)decodeEnvelope:(NSData*)envelope { NSArray* array = [[FlutterJSONMessageCodec sharedInstance] decode:envelope]; if (array.count == 1) return array[0]; NSAssert(array.count == 3, @"Invalid JSON envelope"); NSAssert([array[0] isKindOfClass:[NSString class]], @"Invalid JSON envelope"); NSAssert(array[1] == nil || [array[1] isKindOfClass:[NSString class]], @"Invalid JSON envelope"); - *error = [FlutterError errorWithCode:array[0] message:array[1] details:array[2]]; - return nil; + return [FlutterError errorWithCode:array[0] message:array[1] details:array[2]]; } @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm index 77a20e9ad..d267a3375 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm @@ -46,39 +46,39 @@ using namespace shell; id args = call.arguments; if ([method isEqualToString:@"SystemSound.play"]) { [self playSystemSound:args]; - resultReceiver(nil, nil); + resultReceiver(nil); } else if ([method isEqualToString:@"HapticFeedback.vibrate"]) { [self vibrateHapticFeedback]; - resultReceiver(nil, nil); + resultReceiver(nil); } else if ([method isEqualToString:@"UrlLauncher.launch"]) { [self launchURL:args]; - resultReceiver(nil, nil); + resultReceiver(nil); } else if ([method isEqualToString:@"SystemChrome.setPreferredOrientations"]) { [self setSystemChromePreferredOrientations:args]; - resultReceiver(nil, nil); + resultReceiver(nil); } else if ([method isEqualToString:@"SystemChrome.setApplicationSwitcherDescription"]) { [self setSystemChromeApplicationSwitcherDescription:args]; - resultReceiver(nil, nil); + resultReceiver(nil); } else if ([method isEqualToString:@"SystemChrome.setEnabledSystemUIOverlays"]) { [self setSystemChromeEnabledSystemUIOverlays:args]; - resultReceiver(nil, nil); + resultReceiver(nil); } else if ([method isEqualToString:@"SystemChrome.setSystemUIOverlayStyle"]) { [self setSystemChromeSystemUIOverlayStyle:args]; - resultReceiver(nil, nil); + resultReceiver(nil); } else if ([method isEqualToString:@"SystemNavigator.pop"]) { [self popSystemNavigator]; - resultReceiver(nil, nil); + resultReceiver(nil); } else if ([method isEqualToString:@"Clipboard.getData"]) { - resultReceiver([self getClipboardData:args], nil); + resultReceiver([self getClipboardData:args]); } else if ([method isEqualToString:@"Clipboard.setData"]) { [self setClipboardData:args]; - resultReceiver(nil, nil); + resultReceiver(nil); } else if ([method isEqualToString:@"PathProvider.getTemporaryDirectory"]) { - resultReceiver([self getPathProviderTemporaryDirectory], nil); + resultReceiver([self getPathProviderTemporaryDirectory]); } else if ([method isEqualToString:@"PathProvider.getApplicationDocumentsDirectory"]) { - resultReceiver([self getPathProviderApplicationDocumentsDirectory], nil); + resultReceiver([self getPathProviderApplicationDocumentsDirectory]); } else { - resultReceiver(nil, [FlutterError errorWithCode:@"UNKNOWN" message:@"Unknown method" details: nil]); + resultReceiver(FlutterMethodNotImplemented); } } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterStandardCodec.mm b/shell/platform/darwin/ios/framework/Source/FlutterStandardCodec.mm index c8e22a78f..967fc0614 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterStandardCodec.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterStandardCodec.mm @@ -16,6 +16,8 @@ } - (NSData*)encode:(id)message { + if (message == nil) + return nil; NSMutableData* data = [NSMutableData dataWithCapacity:32]; FlutterStandardWriter* writer = [FlutterStandardWriter writerWithData:data]; [writer writeValue:message]; @@ -23,6 +25,8 @@ } - (id)decode:(NSData*)message { + if (!message.length) + return nil; FlutterStandardReader* reader = [FlutterStandardReader readerWithData:message]; id value = [reader readValue]; @@ -79,7 +83,7 @@ return [FlutterMethodCall methodCallWithMethodName:value1 arguments:value2]; } -- (id)decodeEnvelope:(NSData*)envelope error:(FlutterError**)error { +- (id)decodeEnvelope:(NSData*)envelope { FlutterStandardReader* reader = [FlutterStandardReader readerWithData:envelope]; UInt8 flag = [reader readByte]; @@ -99,9 +103,7 @@ @"Invalid standard envelope"); NSAssert(message == nil || [message isKindOfClass:[NSString class]], @"Invalid standard envelope"); - *error = - [FlutterError errorWithCode:code message:message details:details]; - result = nil; + result = [FlutterError errorWithCode:code message:message details:details]; } break; } return result; @@ -148,12 +150,12 @@ using namespace shell; NSAssert(data, @"Data cannot be nil"); NSAssert(data.length % elementSize == 0, @"Data must contain integral number of elements"); - if (self = [super init]) { - _data = [data retain]; - _type = type; - _elementSize = elementSize; - _elementCount = data.length / elementSize; - } + self = [super init]; + NSAssert(self, @"Super init cannot be nil"); + _data = [data retain]; + _type = type; + _elementSize = elementSize; + _elementCount = data.length / elementSize; return self; } @@ -184,9 +186,9 @@ using namespace shell; - (instancetype)initWithHex:(NSString*)hex { NSAssert(hex, @"Hex cannot be nil"); - if (self = [super init]) { - _hex = [hex retain]; - } + self = [super init]; + NSAssert(self, @"Super init cannot be nil"); + _hex = [hex retain]; return self; } @@ -223,9 +225,9 @@ using namespace shell; } - (instancetype)initWithData:(NSMutableData*)data { - if (self = [super init]) { - _data = [data retain]; - } + self = [super init]; + NSAssert(self, @"Super init cannot be nil"); + _data = [data retain]; return self; } @@ -359,10 +361,10 @@ using namespace shell; } - (instancetype)initWithData:(NSData*)data { - if (self = [super init]) { - _data = [data retain]; - _range = NSMakeRange(0, 0); - } + self = [super init]; + NSAssert(self, @"Super init cannot be nil"); + _data = [data retain]; + _range = NSMakeRange(0, 0); return self; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 6aa60119b..973fb902e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -160,23 +160,21 @@ static UIKeyboardType ToUIKeyboardType(NSString* inputType) { id args = call.arguments; if ([method isEqualToString:@"TextInput.show"]) { [self showTextInput]; - resultReceiver(nil, nil); + resultReceiver(nil); } else if ([method isEqualToString:@"TextInput.hide"]) { [self hideTextInput]; - resultReceiver(nil, nil); + resultReceiver(nil); } else if ([method isEqualToString:@"TextInput.setClient"]) { [self setTextInputClient:[args[0] intValue] withConfiguration:args[1]]; - resultReceiver(nil, nil); + resultReceiver(nil); } else if ([method isEqualToString:@"TextInput.setEditingState"]) { [self setTextInputEditingState:args]; - resultReceiver(nil, nil); + resultReceiver(nil); } else if ([method isEqualToString:@"TextInput.clearClient"]) { [self clearTextInputClient]; - resultReceiver(nil, nil); + resultReceiver(nil); } else { - resultReceiver(nil, [FlutterError errorWithCode:@"UNKNOWN" - message:@"Unknown method" - details:nil]); + resultReceiver(FlutterMethodNotImplemented); } } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index a174f1456..561d134e9 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -585,8 +585,9 @@ constexpr CGFloat kStandardStatusBarHeight = 20.0; #pragma mark - Application Messages - (void)sendBinaryMessage:(NSData*)message channelName:(NSString*)channel { - NSAssert(message, @"The message must not be null"); NSAssert(channel, @"The channel must not be null"); + if (message == nil) + message = [NSData data]; _platformView->DispatchPlatformMessage( ftl::MakeRefCounted( channel.UTF8String, shell::GetVectorFromNSData(message), nil)); @@ -595,15 +596,15 @@ constexpr CGFloat kStandardStatusBarHeight = 20.0; - (void)sendBinaryMessage:(NSData*)message channelName:(NSString*)channel binaryReplyHandler:(FlutterBinaryReplyHandler)callback { - NSAssert(message, @"The message must not be null"); NSAssert(channel, @"The channel must not be null"); NSAssert(callback, @"The callback must not be null"); + if (message == nil) + message = [NSData data]; _platformView->DispatchPlatformMessage( ftl::MakeRefCounted( channel.UTF8String, shell::GetVectorFromNSData(message), ftl::MakeRefCounted(^(NSData* reply) { - if (callback) - callback(reply); + callback(reply); }))); } diff --git a/shell/platform/darwin/ios/framework/Source/flutter_codecs_unittest.mm b/shell/platform/darwin/ios/framework/Source/flutter_codecs_unittest.mm index 5d921141b..44e654917 100644 --- a/shell/platform/darwin/ios/framework/Source/flutter_codecs_unittest.mm +++ b/shell/platform/darwin/ios/framework/Source/flutter_codecs_unittest.mm @@ -7,16 +7,14 @@ TEST(FlutterStringCodec, CanEncodeAndDecodeNil) { FlutterStringCodec* codec = [FlutterStringCodec sharedInstance]; - ASSERT_TRUE([[codec encode:nil] isEqualTo:[NSData data]]); - ASSERT_TRUE([[codec decode:nil] isEqualTo:@""]); + ASSERT_TRUE([codec encode:nil] == nil); + ASSERT_TRUE([codec decode:nil] == nil); } TEST(FlutterStringCodec, CanEncodeAndDecodeEmptyString) { - NSString* value = @""; FlutterStringCodec* codec = [FlutterStringCodec sharedInstance]; - NSData* encoded = [codec encode:value]; - NSString* decoded = [codec decode:encoded]; - ASSERT_TRUE([value isEqualTo:decoded]); + ASSERT_TRUE([codec encode:@""] == nil); + ASSERT_TRUE([codec decode:[NSData data]] == nil); } TEST(FlutterStringCodec, CanEncodeAndDecodeAsciiString) { @@ -43,6 +41,13 @@ TEST(FlutterStringCodec, CanEncodeAndDecodeNonBMPString) { ASSERT_TRUE([value isEqualTo:decoded]); } +TEST(FlutterJSONCodec, CanEncodeAndDecodeNil) { + FlutterStringCodec* codec = [FlutterStringCodec sharedInstance]; + ASSERT_TRUE([codec encode:nil] == nil); + ASSERT_TRUE([codec decode:nil] == nil); + ASSERT_TRUE([codec decode:[NSData data]] == nil); +} + TEST(FlutterJSONCodec, CanEncodeAndDecodeArray) { NSArray* value = @[ [NSNull null], @"hello", @3.14, @47, diff --git a/shell/platform/darwin/ios/framework/Source/flutter_standard_codec_unittest.mm b/shell/platform/darwin/ios/framework/Source/flutter_standard_codec_unittest.mm index 4c404d076..ebf393198 100644 --- a/shell/platform/darwin/ios/framework/Source/flutter_standard_codec_unittest.mm +++ b/shell/platform/darwin/ios/framework/Source/flutter_standard_codec_unittest.mm @@ -9,7 +9,10 @@ void checkEncodeDecode(id value, NSData* expectedEncoding) { FlutterStandardMessageCodec* codec = [FlutterStandardMessageCodec sharedInstance]; NSData* encoded = [codec encode:value]; - ASSERT_TRUE([encoded isEqual:expectedEncoding]); + if (expectedEncoding == nil) + ASSERT_TRUE(encoded == nil); + else + ASSERT_TRUE([encoded isEqual:expectedEncoding]); id decoded = [codec decode:encoded]; if (value == nil || value == [NSNull null]) ASSERT_TRUE(decoded == nil); @@ -29,8 +32,7 @@ void checkEncodeDecode(id value) { } TEST(FlutterStandardCodec, CanEncodeAndDecodeNil) { - char bytes[1] = {0x00}; - checkEncodeDecode(nil, [NSData dataWithBytes:bytes length:1]); + checkEncodeDecode(nil, nil); } TEST(FlutterStandardCodec, CanEncodeAndDecodeNSNull) { @@ -216,9 +218,7 @@ TEST(FlutterStandardCodec, HandlesSuccessEnvelopesWithNilResult) { FlutterStandardMethodCodec* codec = [FlutterStandardMethodCodec sharedInstance]; NSData* encoded = [codec encodeSuccessEnvelope:nil]; - FlutterError* error = nil; - id decoded = [codec decodeEnvelope:encoded error:&error]; - ASSERT_TRUE(error == nil); + id decoded = [codec decodeEnvelope:encoded]; ASSERT_TRUE(decoded == nil); } @@ -226,10 +226,8 @@ TEST(FlutterStandardCodec, HandlesSuccessEnvelopesWithSingleResult) { FlutterStandardMethodCodec* codec = [FlutterStandardMethodCodec sharedInstance]; NSData* encoded = [codec encodeSuccessEnvelope:@42]; - FlutterError* decodedError = nil; - id decodedResult = [codec decodeEnvelope:encoded error:&decodedError]; - ASSERT_TRUE(decodedError == nil); - ASSERT_TRUE([decodedResult isEqual:@42]); + id decoded = [codec decodeEnvelope:encoded]; + ASSERT_TRUE([decoded isEqual:@42]); } TEST(FlutterStandardCodec, HandlesSuccessEnvelopesWithResultMap) { @@ -237,9 +235,8 @@ TEST(FlutterStandardCodec, HandlesSuccessEnvelopesWithResultMap) { [FlutterStandardMethodCodec sharedInstance]; NSDictionary* result = @{ @"a" : @42, @42 : @"a" }; NSData* encoded = [codec encodeSuccessEnvelope:result]; - FlutterError* decodedError = nil; - id decodedResult = [codec decodeEnvelope:encoded error:&decodedError]; - ASSERT_TRUE([decodedResult isEqual:result]); + id decoded = [codec decodeEnvelope:encoded]; + ASSERT_TRUE([decoded isEqual:result]); } TEST(FlutterStandardCodec, HandlesErrorEnvelopes) { @@ -250,8 +247,6 @@ TEST(FlutterStandardCodec, HandlesErrorEnvelopes) { message:@"something failed" details:details]; NSData* encoded = [codec encodeErrorEnvelope:error]; - FlutterError* decodedError = nil; - id decodedResult = [codec decodeEnvelope:encoded error:&decodedError]; - ASSERT_TRUE(decodedResult == nil); - ASSERT_TRUE([decodedError isEqual:error]); + id decoded = [codec decodeEnvelope:encoded]; + ASSERT_TRUE([decoded isEqual:error]); } diff --git a/travis/licenses_golden/licenses_flutter b/travis/licenses_golden/licenses_flutter index 471376287..ed70142b7 100644 --- a/travis/licenses_golden/licenses_flutter +++ b/travis/licenses_golden/licenses_flutter @@ -1434,6 +1434,7 @@ FILE: ../../../flutter/lib/ui/painting/vertices.h FILE: ../../../flutter/shell/gpu/gpu_surface_software.cc FILE: ../../../flutter/shell/gpu/gpu_surface_software.h FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/BinaryCodec.java +FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/FlutterEventChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/FlutterException.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/FlutterMessageChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/FlutterMethodChannel.java -- GitLab