plugin_event_channel.dart 5.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// 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.
22 23 24 25 26 27 28
///
/// 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
29 30
/// `controller`. This triggers the [StreamController.onListen] callback
/// on the `controller`.
31 32
///
/// The other method is `cancel`. When called, it stops forwarding
33
/// events to the framework. This triggers the [StreamController.onCancel]
34
/// callback on the `controller`.
35
///
36
/// Events added to the `controller` when the framework is not
37
/// subscribed are silently discarded.
38 39
class PluginEventChannel<T> {
  /// Creates a new plugin event channel.
40 41
  ///
  /// The [name] and [codec] arguments must not be null.
42 43 44
  const PluginEventChannel(
    this.name, [
    this.codec = const StandardMethodCodec(),
45
    this.binaryMessenger,
46
  ]);
47 48 49 50 51 52 53 54 55 56 57 58 59

  /// 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.
  ///
60 61 62
  /// When this is null, the [pluginBinaryMessenger] is used instead,
  /// which sends messages from the platform-side to the
  /// framework-side.
63
  final BinaryMessenger? binaryMessenger;
64 65 66 67 68 69 70 71 72

  /// 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.'
  )
73
  set controller(StreamController<T> controller) { // ignore: avoid_setters_without_getters
74 75 76 77 78 79 80
    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).
81
  void setController(StreamController<T>? controller) {
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
    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);
    }
101 102 103 104
  }
}

class _EventChannelHandler<T> {
105 106 107 108 109
  _EventChannelHandler(
    this.name,
    this.codec,
    this.controller,
    this.messenger,
110
  );
111 112 113 114 115 116

  final String name;
  final MethodCodec codec;
  final StreamController<T> controller;
  final BinaryMessenger messenger;

117
  StreamSubscription<T>? subscription;
118

119
  Future<ByteData>? handle(ByteData? message) {
120 121 122
    final MethodCall call = codec.decodeMethodCall(message);
    switch (call.method) {
      case 'listen':
123
        assert(call.arguments == null);
124 125
        return _listen();
      case 'cancel':
126
        assert(call.arguments == null);
127 128 129 130 131 132
        return _cancel();
    }
    return null;
  }

  Future<ByteData> _listen() async {
133 134
    // Cancel any existing subscription.
    await subscription?.cancel();
135 136 137
    subscription = controller.stream.listen((dynamic event) {
      messenger.send(name, codec.encodeSuccessEnvelope(event));
    }, onError: (dynamic error) {
138
      messenger.send(name, codec.encodeErrorEnvelope(code: 'error', message: '$error'));
139 140 141 142 143 144 145
    });
    return codec.encodeSuccessEnvelope(null);
  }

  Future<ByteData> _cancel() async {
    if (subscription == null) {
      return codec.encodeErrorEnvelope(
146 147 148
        code: 'error',
        message: 'No active subscription to cancel.',
      );
149
    }
150
    await subscription!.cancel();
151 152 153 154
    subscription = null;
    return codec.encodeSuccessEnvelope(null);
  }
}