flutter_web_plugins cleanup and documentation (#67164)
This commit is contained in:
parent
d411242468
commit
8d923bf9a7
@ -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';
|
||||
|
@ -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.
|
||||
///
|
||||
@ -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;
|
||||
/// When this is null, the [pluginBinaryMessenger] is used instead,
|
||||
/// which sends messages from the platform-side to the
|
||||
/// framework-side.
|
||||
final BinaryMessenger binaryMessenger;
|
||||
|
||||
/// Set the stream controller for this event channel.
|
||||
/// 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.'
|
||||
)
|
||||
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;
|
||||
@ -77,14 +118,15 @@ 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();
|
||||
@ -92,18 +134,17 @@ class _EventChannelHandler<T> {
|
||||
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;
|
||||
|
@ -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;
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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) {
|
||||
@ -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'),
|
||||
));
|
||||
}
|
||||
@ -125,9 +145,6 @@ 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
|
||||
@ -135,17 +152,24 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
|
||||
|
||||
@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();
|
||||
|
@ -23,7 +23,7 @@ void main() {
|
||||
webPluginRegistry.registerMessageHandler();
|
||||
});
|
||||
|
||||
test('can send events to an $EventChannel', () async {
|
||||
test('can send events to an $EventChannel (deprecated API)', () async {
|
||||
const EventChannel listeningChannel = EventChannel('test');
|
||||
const PluginEventChannel<String> sendingChannel =
|
||||
PluginEventChannel<String>('test');
|
||||
@ -39,7 +39,23 @@ void main() {
|
||||
await controller.close();
|
||||
});
|
||||
|
||||
test('can send errors to an $EventChannel', () async {
|
||||
test('can send events to an $EventChannel', () async {
|
||||
const EventChannel listeningChannel = EventChannel('test');
|
||||
const PluginEventChannel<String> sendingChannel =
|
||||
PluginEventChannel<String>('test');
|
||||
|
||||
final StreamController<String> controller = StreamController<String>();
|
||||
sendingChannel.setController(controller);
|
||||
|
||||
expect(listeningChannel.receiveBroadcastStream(),
|
||||
emitsInOrder(<String>['hello', 'world']));
|
||||
|
||||
controller.add('hello');
|
||||
controller.add('world');
|
||||
await controller.close();
|
||||
});
|
||||
|
||||
test('can send errors to an $EventChannel (deprecated API)', () async {
|
||||
const EventChannel listeningChannel = EventChannel('test2');
|
||||
const PluginEventChannel<String> sendingChannel =
|
||||
PluginEventChannel<String>('test2');
|
||||
@ -56,7 +72,24 @@ void main() {
|
||||
await controller.close();
|
||||
});
|
||||
|
||||
test('receives a listen event', () async {
|
||||
test('can send errors to an $EventChannel', () async {
|
||||
const EventChannel listeningChannel = EventChannel('test2');
|
||||
const PluginEventChannel<String> sendingChannel =
|
||||
PluginEventChannel<String>('test2');
|
||||
|
||||
final StreamController<String> controller = StreamController<String>();
|
||||
sendingChannel.setController(controller);
|
||||
|
||||
expect(
|
||||
listeningChannel.receiveBroadcastStream(),
|
||||
emitsError(predicate<dynamic>((dynamic e) =>
|
||||
e is PlatformException && e.message == 'Test error')));
|
||||
|
||||
controller.addError('Test error');
|
||||
await controller.close();
|
||||
});
|
||||
|
||||
test('receives a listen event (deprecated API)', () async {
|
||||
const EventChannel listeningChannel = EventChannel('test3');
|
||||
const PluginEventChannel<String> sendingChannel =
|
||||
PluginEventChannel<String>('test3');
|
||||
@ -72,7 +105,23 @@ void main() {
|
||||
await controller.close();
|
||||
});
|
||||
|
||||
test('receives a cancel event', () async {
|
||||
test('receives a listen event', () async {
|
||||
const EventChannel listeningChannel = EventChannel('test3');
|
||||
const PluginEventChannel<String> sendingChannel =
|
||||
PluginEventChannel<String>('test3');
|
||||
|
||||
final StreamController<String> controller = StreamController<String>(
|
||||
onListen: expectAsync0<void>(() {}, count: 1));
|
||||
sendingChannel.setController(controller);
|
||||
|
||||
expect(listeningChannel.receiveBroadcastStream(),
|
||||
emitsInOrder(<String>['hello']));
|
||||
|
||||
controller.add('hello');
|
||||
await controller.close();
|
||||
});
|
||||
|
||||
test('receives a cancel event (deprecated API)', () async {
|
||||
const EventChannel listeningChannel = EventChannel('test4');
|
||||
const PluginEventChannel<String> sendingChannel =
|
||||
PluginEventChannel<String>('test4');
|
||||
@ -92,5 +141,26 @@ void main() {
|
||||
|
||||
controller.add('hello');
|
||||
});
|
||||
|
||||
test('receives a cancel event', () async {
|
||||
const EventChannel listeningChannel = EventChannel('test4');
|
||||
const PluginEventChannel<String> sendingChannel =
|
||||
PluginEventChannel<String>('test4');
|
||||
|
||||
final StreamController<String> controller =
|
||||
StreamController<String>(onCancel: expectAsync0<void>(() {}));
|
||||
sendingChannel.setController(controller);
|
||||
|
||||
final Stream<dynamic> eventStream =
|
||||
listeningChannel.receiveBroadcastStream();
|
||||
StreamSubscription<dynamic> subscription;
|
||||
subscription =
|
||||
eventStream.listen(expectAsync1<void, dynamic>((dynamic x) {
|
||||
expect(x, equals('hello'));
|
||||
subscription.cancel();
|
||||
}));
|
||||
|
||||
controller.add('hello');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user