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

flutter_web_plugins cleanup and documentation #67164

Merged
merged 1 commit into from Oct 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/flutter_web_plugins/lib/flutter_web_plugins.dart
Expand Up @@ -4,5 +4,18 @@

// @dart = 2.8

/// The platform channels and plugin registry implementations for
/// the web implementations of Flutter plugins.
///
/// This library provides the [Registrar] class, which is used in the
/// `registerWith` method that is itself called by the code generated
/// by the `flutter` tool for web applications.
///
/// See also:
///
/// * [How to Write a Flutter Web Plugin](https://medium.com/flutter/how-to-write-a-flutter-web-plugin-5e26c689ea1), a Medium article
/// describing how the [url_launcher] package was created using [flutter_web_plugins].
library flutter_web_plugins;

export 'src/plugin_event_channel.dart';
export 'src/plugin_registry.dart';
93 changes: 67 additions & 26 deletions packages/flutter_web_plugins/lib/src/plugin_event_channel.dart
Expand Up @@ -21,15 +21,32 @@ import 'plugin_registry.dart';
/// [StandardMethodCodec] is used. If no [binaryMessenger] is provided, then
/// [pluginBinaryMessenger], which sends messages to the framework-side,
/// is used.
///
/// Channels created using this class implement two methods for
/// subscribing to the event stream. The methods use the encoding of
/// the specified [codec].
///
/// The first method is `listen`. When called, it begins forwarding
/// messages to the framework side when they are added to the
/// [controller]. This triggers the [onListen] callback on the
/// [controller].
///
/// The other method is `cancel`. When called, it stops forwarding
/// events to the framework. This triggers the [onCancel] callback on
/// the [controller].
///
/// Events added to the [controller] when the framework is not
/// subscribed are silently discarded.
class PluginEventChannel<T> {
/// Creates a new plugin event channel.
///
/// The [name] and [codec] arguments must not be null.
const PluginEventChannel(
this.name, [
this.codec = const StandardMethodCodec(),
BinaryMessenger binaryMessenger,
this.binaryMessenger,
]) : assert(name != null),
assert(codec != null),
_binaryMessenger = binaryMessenger;
assert(codec != null);

/// The logical channel on which communication happens.
///
Expand All @@ -43,28 +60,52 @@ class PluginEventChannel<T> {

/// The messenger used by this channel to send platform messages.
///
/// This must not be null. If not provided, defaults to
/// [pluginBinaryMessenger], which sends messages from the platform-side
/// to the framework-side.
BinaryMessenger get binaryMessenger =>
_binaryMessenger ?? pluginBinaryMessenger;
final BinaryMessenger _binaryMessenger;

/// Set the stream controller for this event channel.
/// When this is null, the [pluginBinaryMessenger] is used instead,
/// which sends messages from the platform-side to the
/// framework-side.
final BinaryMessenger binaryMessenger;

/// Use [setController] instead.
///
/// This setter is deprecated because it has no corresponding getter,
/// and providing a getter would require making this class non-const.
@Deprecated(
'Replace calls to the "controller" setter with calls to the "setController" method. '
'This feature was deprecated after v1.23.0-7.0.pre.'
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to clean this up before stable release. How widespread is the usage of this setter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Piinks is working on a deprecation policy.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make sure to send you the proposal, it will include what usage data I've been able to collect and some guidelines for cleaning up old code. <=1 week out from publishing :)

set controller(StreamController<T> controller) {
final _EventChannelHandler<T> handler = _EventChannelHandler<T>(
name,
codec,
controller,
binaryMessenger,
);
binaryMessenger.setMessageHandler(
name, controller == null ? null : handler.handle);
setController(controller);
}

/// Changes the stream controller for this event channel.
///
/// Setting the controller to null disconnects from the channel (setting
/// the message handler on the [binaryMessenger] to null).
void setController(StreamController<T> controller) {
final BinaryMessenger messenger = binaryMessenger ?? pluginBinaryMessenger;
if (controller == null) {
messenger.setMessageHandler(name, null);
} else {
// The handler object is kept alive via its handle() method
// keeping a reference to itself. Ideally we would keep a
// reference to it so that there was a clear ownership model,
// but that would require making this class non-const. Having
// this class be const is convenient since it allows references
// to be obtained by using the constructor rather than having
// to literally pass references around.
final _EventChannelHandler<T> handler = _EventChannelHandler<T>(
name,
codec,
controller,
messenger,
);
messenger.setMessageHandler(name, handler.handle);
}
}
}

class _EventChannelHandler<T> {
_EventChannelHandler(this.name, this.codec, this.controller, this.messenger);
_EventChannelHandler(this.name, this.codec, this.controller, this.messenger) : assert(messenger != null);

final String name;
final MethodCodec codec;
Expand All @@ -77,33 +118,33 @@ class _EventChannelHandler<T> {
final MethodCall call = codec.decodeMethodCall(message);
switch (call.method) {
case 'listen':
assert(call.arguments == null);
return _listen();
case 'cancel':
assert(call.arguments == null);
return _cancel();
}
return null;
}

// TODO(hterkelsen): Support arguments.
Future<ByteData> _listen() async {
if (subscription != null) {
await subscription.cancel();
}
subscription = controller.stream.listen((dynamic event) {
messenger.send(name, codec.encodeSuccessEnvelope(event));
}, onError: (dynamic error) {
messenger.send(name,
codec.encodeErrorEnvelope(code: 'error', message: error.toString()));
messenger.send(name, codec.encodeErrorEnvelope(code: 'error', message: '$error'));
});

return codec.encodeSuccessEnvelope(null);
}

// TODO(hterkelsen): Support arguments.
Future<ByteData> _cancel() async {
if (subscription == null) {
return codec.encodeErrorEnvelope(
code: 'error', message: 'No active stream to cancel.');
code: 'error',
message: 'No active subscription to cancel.',
);
}
await subscription.cancel();
subscription = null;
Expand Down
78 changes: 51 additions & 27 deletions packages/flutter_web_plugins/lib/src/plugin_registry.dart
Expand Up @@ -13,8 +13,13 @@ import 'package:flutter/services.dart';
typedef _MessageHandler = Future<ByteData> Function(ByteData);

/// This class registers web platform plugins.
///
/// An instance of this class is available as [webPluginRegistry].
class PluginRegistry {
/// Creates a plugin registry.
///
/// The argument selects the [BinaryMessenger] to use. An
/// appropriate value would be [pluginBinaryMessenger].
PluginRegistry(this._binaryMessenger);

final BinaryMessenger _binaryMessenger;
Expand All @@ -25,6 +30,17 @@ class PluginRegistry {
/// Registers this plugin handler with the engine, so that unrecognized
/// platform messages are forwarded to the registry, where they can be
/// correctly dispatched to one of the registered plugins.
///
/// Code generated by the `flutter` tool automatically calls this method
/// for the global [webPluginRegistry] at startup.
///
/// Only one [PluginRegistry] can be registered at a time. Calling this
/// method a second time silently unregisters the first [PluginRegistry]
/// and replaces it with the new one.
///
/// This method uses a function called `webOnlySetPluginHandler` in
/// the [dart:ui] library. That function is only available when
/// compiling for the web.
void registerMessageHandler() {
// The function below is only defined in the Web dart:ui.
// ignore: undefined_function
Expand All @@ -46,22 +62,26 @@ class Registrar {
/// Use this [BinaryMessenger] when creating platform channels in order for
/// them to receive messages from the platform side. For example:
///
///
/// class MyPlugin {
/// static void registerWith(Registrar registrar) {
/// final MethodChannel channel = MethodChannel(
/// 'com.my_plugin/my_plugin',
/// const StandardMethodCodec(),
/// registrar.messenger);
/// final MyPlugin instance = MyPlugin();
/// channel.setMethodCallHandler(instance.handleMethodCall);
/// }
/// ...
/// }
/// ```dart
/// class MyPlugin {
/// static void registerWith(Registrar registrar) {
/// final MethodChannel channel = MethodChannel(
/// 'com.my_plugin/my_plugin',
/// const StandardMethodCodec(),
/// registrar.messenger,
/// );
/// final MyPlugin instance = MyPlugin();
/// channel.setMethodCallHandler(instance.handleMethodCall);
/// }
/// // ...
/// }
/// ```
final BinaryMessenger messenger;
}

/// The default plugin registry for the web.
///
/// Uses [pluginBinaryMessenger] as the [BinaryMessenger].
final PluginRegistry webPluginRegistry = PluginRegistry(pluginBinaryMessenger);

/// A [BinaryMessenger] which does the inverse of the default framework
Expand All @@ -75,23 +95,23 @@ class _PlatformBinaryMessenger extends BinaryMessenger {

/// Receives a platform message from the framework.
@override
Future<void> handlePlatformMessage(String channel, ByteData data,
ui.PlatformMessageResponseCallback callback) async {
Future<void> handlePlatformMessage(
String channel,
ByteData data,
ui.PlatformMessageResponseCallback callback,
) async {
ByteData response;
try {
final MessageHandler handler = _handlers[channel];
if (handler != null) {
response = await handler(data);
} else {
ui.channelBuffers.push(channel, data, callback);
callback = null;
}
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'flutter web shell',
context: ErrorDescription('during a plugin platform message call'),
library: 'flutter web plugins',
context: ErrorDescription('during a framework-to-plugin message'),
));
} finally {
if (callback != null) {
Expand All @@ -111,7 +131,7 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'flutter web shell',
library: 'flutter web plugins',
context: ErrorDescription('during a plugin-to-framework message'),
));
}
Expand All @@ -125,27 +145,31 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
_handlers.remove(channel);
else
_handlers[channel] = handler;
ui.channelBuffers.drain(channel, (ByteData data, ui.PlatformMessageResponseCallback callback) async {
await handlePlatformMessage(channel, data, callback);
});
}

@override
bool checkMessageHandler(String channel, MessageHandler handler) => _handlers[channel] == handler;

@override
void setMockMessageHandler(
String channel, Future<ByteData> Function(ByteData message) handler) {
String channel,
Future<ByteData> Function(ByteData message) handler,
) {
throw FlutterError(
'Setting mock handlers is not supported on the platform side.');
'Setting mock handlers is not supported on the platform side.',
);
}

@override
bool checkMockMessageHandler(String channel, MessageHandler handler) {
throw FlutterError(
'Setting mock handlers is not supported on the platform side.');
'Setting mock handlers is not supported on the platform side.',
);
}
}

/// The default [BinaryMessenger] for Flutter Web plugins.
/// The default [BinaryMessenger] for Flutter web plugins.
///
/// This is the value used for [webPluginRegistry]'s [PluginRegistry]
/// constructor argument.
final BinaryMessenger pluginBinaryMessenger = _PlatformBinaryMessenger();