155 lines
5.1 KiB
Dart
155 lines
5.1 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
import 'plugin_registry.dart';
|
|
|
|
/// A named channel for sending events to the framework-side using streams.
|
|
///
|
|
/// This is the platform-side equivalent of [EventChannel]. Whereas
|
|
/// [EventChannel] receives a stream of events from platform plugins, this
|
|
/// channel sends a stream of events to the handler listening on the
|
|
/// framework-side.
|
|
///
|
|
/// The channel [name] must not be null. If no [codec] is provided, then
|
|
/// [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 [StreamController.onListen] callback
|
|
/// on the `controller`.
|
|
///
|
|
/// The other method is `cancel`. When called, it stops forwarding
|
|
/// events to the framework. This triggers the [StreamController.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(),
|
|
this.binaryMessenger,
|
|
]);
|
|
|
|
/// The logical channel on which communication happens.
|
|
///
|
|
/// This must not be null.
|
|
final String name;
|
|
|
|
/// The message codec used by this channel.
|
|
///
|
|
/// This must not be null. This defaults to [StandardMethodCodec].
|
|
final MethodCodec codec;
|
|
|
|
/// The messenger used by this channel to send platform messages.
|
|
///
|
|
/// 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.'
|
|
)
|
|
set controller(StreamController<T> controller) { // ignore: avoid_setters_without_getters
|
|
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,
|
|
);
|
|
|
|
final String name;
|
|
final MethodCodec codec;
|
|
final StreamController<T> controller;
|
|
final BinaryMessenger messenger;
|
|
|
|
StreamSubscription<T>? subscription;
|
|
|
|
Future<ByteData>? handle(ByteData? message) {
|
|
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;
|
|
}
|
|
|
|
Future<ByteData> _listen() async {
|
|
// Cancel any existing subscription.
|
|
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'));
|
|
});
|
|
return codec.encodeSuccessEnvelope(null);
|
|
}
|
|
|
|
Future<ByteData> _cancel() async {
|
|
if (subscription == null) {
|
|
return codec.encodeErrorEnvelope(
|
|
code: 'error',
|
|
message: 'No active subscription to cancel.',
|
|
);
|
|
}
|
|
await subscription!.cancel();
|
|
subscription = null;
|
|
return codec.encodeSuccessEnvelope(null);
|
|
}
|
|
}
|