// 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, ]) : assert(name != null), assert(codec != null); /// 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) { 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, ) : assert(messenger != null); 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); } }