| // 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 'dart:developer'; |
| |
| import 'package:flute/foundation.dart'; |
| |
| import '_background_isolate_binary_messenger_io.dart' |
| ; |
| |
| import 'binary_messenger.dart'; |
| import 'binding.dart'; |
| import 'debug.dart' show debugProfilePlatformChannels; |
| import 'message_codec.dart'; |
| import 'message_codecs.dart'; |
| |
| export '_background_isolate_binary_messenger_io.dart' |
| ; |
| |
| export 'binary_messenger.dart' show BinaryMessenger; |
| export 'binding.dart' show RootIsolateToken; |
| export 'message_codec.dart' show MessageCodec, MethodCall, MethodCodec; |
| |
| bool _debugProfilePlatformChannelsIsRunning = false; |
| const Duration _debugProfilePlatformChannelsRate = Duration(seconds: 1); |
| final Expando<BinaryMessenger> _debugBinaryMessengers = Expando<BinaryMessenger>(); |
| |
| class _ProfiledBinaryMessenger implements BinaryMessenger { |
| const _ProfiledBinaryMessenger(this.proxy, this.channelTypeName, this.codecTypeName); |
| final BinaryMessenger proxy; |
| final String channelTypeName; |
| final String codecTypeName; |
| |
| @override |
| Future<void> handlePlatformMessage(String channel, ByteData? data, PlatformMessageResponseCallback? callback) { |
| return proxy.handlePlatformMessage(channel, data, callback); |
| } |
| |
| Future<ByteData?>? sendWithPostfix(String channel, String postfix, ByteData? message) async { |
| final TimelineTask task = TimelineTask(); |
| _debugRecordUpStream(channelTypeName, '$channel$postfix', codecTypeName, message); |
| task.start('Platform Channel send $channel$postfix'); |
| final ByteData? result; |
| try { |
| result = await proxy.send(channel, message); |
| } finally { |
| task.finish(); |
| } |
| _debugRecordDownStream(channelTypeName, '$channel$postfix', codecTypeName, result); |
| return result; |
| } |
| |
| @override |
| Future<ByteData?>? send(String channel, ByteData? message) => |
| sendWithPostfix(channel, '', message); |
| |
| @override |
| void setMessageHandler(String channel, MessageHandler? handler) { |
| proxy.setMessageHandler(channel, handler); |
| } |
| } |
| |
| class _PlatformChannelStats { |
| _PlatformChannelStats(this.channel, this.codec, this.type); |
| |
| final String channel; |
| final String codec; |
| final String type; |
| |
| int _upCount = 0; |
| int _upBytes = 0; |
| int get upBytes => _upBytes; |
| void addUpStream(int bytes) { |
| _upCount += 1; |
| _upBytes += bytes; |
| } |
| |
| int _downCount = 0; |
| int _downBytes = 0; |
| int get downBytes => _downBytes; |
| void addDownStream(int bytes) { |
| _downCount += 1; |
| _downBytes += bytes; |
| } |
| |
| double get averageUpPayload => _upBytes / _upCount; |
| double get averageDownPayload => _downBytes / _downCount; |
| } |
| |
| final Map<String, _PlatformChannelStats> _debugProfilePlatformChannelsStats = <String, _PlatformChannelStats>{}; |
| |
| Future<void> _debugLaunchProfilePlatformChannels() async { |
| if (!_debugProfilePlatformChannelsIsRunning) { |
| _debugProfilePlatformChannelsIsRunning = true; |
| await Future<dynamic>.delayed(_debugProfilePlatformChannelsRate); |
| _debugProfilePlatformChannelsIsRunning = false; |
| final StringBuffer log = StringBuffer(); |
| log.writeln('Platform Channel Stats:'); |
| final List<_PlatformChannelStats> allStats = |
| _debugProfilePlatformChannelsStats.values.toList(); |
| // Sort highest combined bandwidth first. |
| allStats.sort((_PlatformChannelStats x, _PlatformChannelStats y) => |
| (y.upBytes + y.downBytes) - (x.upBytes + x.downBytes)); |
| for (final _PlatformChannelStats stats in allStats) { |
| log.writeln( |
| ' (name:"${stats.channel}" type:"${stats.type}" codec:"${stats.codec}" upBytes:${stats.upBytes} upBytes_avg:${stats.averageUpPayload.toStringAsFixed(1)} downBytes:${stats.downBytes} downBytes_avg:${stats.averageDownPayload.toStringAsFixed(1)})'); |
| } |
| debugPrint(log.toString()); |
| _debugProfilePlatformChannelsStats.clear(); |
| } |
| } |
| |
| void _debugRecordUpStream(String channelTypeName, String name, |
| String codecTypeName, ByteData? bytes) { |
| final _PlatformChannelStats stats = |
| _debugProfilePlatformChannelsStats[name] ??= |
| _PlatformChannelStats(name, codecTypeName, channelTypeName); |
| stats.addUpStream(bytes?.lengthInBytes ?? 0); |
| _debugLaunchProfilePlatformChannels(); |
| } |
| |
| void _debugRecordDownStream(String channelTypeName, String name, |
| String codecTypeName, ByteData? bytes) { |
| final _PlatformChannelStats stats = |
| _debugProfilePlatformChannelsStats[name] ??= |
| _PlatformChannelStats(name, codecTypeName, channelTypeName); |
| stats.addDownStream(bytes?.lengthInBytes ?? 0); |
| _debugLaunchProfilePlatformChannels(); |
| } |
| |
| BinaryMessenger _findBinaryMessenger() { |
| return !kIsWeb && ServicesBinding.rootIsolateToken == null |
| ? BackgroundIsolateBinaryMessenger.instance |
| : ServicesBinding.instance.defaultBinaryMessenger; |
| } |
| |
| /// A named channel for communicating with platform plugins using asynchronous |
| /// message passing. |
| /// |
| /// Messages are encoded into binary before being sent, and binary messages |
| /// received are decoded into Dart values. The [MessageCodec] used must be |
| /// compatible with the one used by the platform plugin. This can be achieved |
| /// by creating a basic message channel counterpart of this channel on the |
| /// platform side. The Dart type of messages sent and received is [T], |
| /// but only the values supported by the specified [MessageCodec] can be used. |
| /// The use of unsupported values should be considered programming errors, and |
| /// will result in exceptions being thrown. The null message is supported |
| /// for all codecs. |
| /// |
| /// The logical identity of the channel is given by its name. Identically named |
| /// channels will interfere with each other's communication. |
| /// |
| /// All [BasicMessageChannel]s provided by the Flutter framework guarantee FIFO |
| /// ordering. Applications can assume messages sent via a built-in |
| /// [BasicMessageChannel] are delivered in the same order as they're sent. |
| /// |
| /// See: <https://flutter.dev/platform-channels/> |
| class BasicMessageChannel<T> { |
| /// Creates a [BasicMessageChannel] with the specified [name], [codec] and [binaryMessenger]. |
| /// |
| /// The [name] and [codec] arguments cannot be null. The default [ServicesBinding.defaultBinaryMessenger] |
| /// instance is used if [binaryMessenger] is null. |
| const BasicMessageChannel(this.name, this.codec, { BinaryMessenger? binaryMessenger }) |
| : assert(name != null), |
| assert(codec != null), |
| _binaryMessenger = binaryMessenger; |
| |
| /// The logical channel on which communication happens, not null. |
| final String name; |
| |
| /// The message codec used by this channel, not null. |
| final MessageCodec<T> codec; |
| |
| /// The messenger which sends the bytes for this channel. |
| /// |
| /// On the root isolate or web, this defaults to the |
| /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default |
| /// value is a [BackgroundIsolateBinaryMessenger] from |
| /// [BackgroundIsolateBinaryMessenger.ensureInitialized]. |
| BinaryMessenger get binaryMessenger { |
| final BinaryMessenger result = _binaryMessenger ?? _findBinaryMessenger(); |
| return !kReleaseMode && debugProfilePlatformChannels |
| ? _debugBinaryMessengers[this] ??= _ProfiledBinaryMessenger( |
| // ignore: no_runtimetype_tostring |
| result, runtimeType.toString(), codec.runtimeType.toString()) |
| : result; |
| } |
| final BinaryMessenger? _binaryMessenger; |
| |
| /// Sends the specified [message] to the platform plugins on this channel. |
| /// |
| /// Returns a [Future] which completes to the received response, which may |
| /// be null. |
| Future<T?> send(T message) async { |
| return codec.decodeMessage(await binaryMessenger.send(name, codec.encodeMessage(message))); |
| } |
| |
| /// Sets a callback for receiving messages from the platform plugins on this |
| /// channel. Messages may be null. |
| /// |
| /// The given callback will replace the currently registered callback for this |
| /// channel, if any. To remove the handler, pass null as the `handler` |
| /// argument. |
| /// |
| /// The handler's return value is sent back to the platform plugins as a |
| /// message reply. It may be null. |
| void setMessageHandler(Future<T> Function(T? message)? handler) { |
| if (handler == null) { |
| binaryMessenger.setMessageHandler(name, null); |
| } else { |
| binaryMessenger.setMessageHandler(name, (ByteData? message) async { |
| return codec.encodeMessage(await handler(codec.decodeMessage(message))); |
| }); |
| } |
| } |
| |
| // Looking for setMockMessageHandler? |
| // See this shim package: packages/flutter_test/lib/src/deprecated.dart |
| } |
| |
| /// A named channel for communicating with platform plugins using asynchronous |
| /// method calls. |
| /// |
| /// Method calls are encoded into binary before being sent, and binary results |
| /// received are decoded into Dart values. The [MethodCodec] used must be |
| /// compatible with the one used by the platform plugin. This can be achieved |
| /// by creating a method channel counterpart of this channel on the |
| /// platform side. The Dart type of arguments and results is `dynamic`, |
| /// but only values supported by the specified [MethodCodec] can be used. |
| /// The use of unsupported values should be considered programming errors, and |
| /// will result in exceptions being thrown. The null value is supported |
| /// for all codecs. |
| /// |
| /// The logical identity of the channel is given by its name. Identically named |
| /// channels will interfere with each other's communication. |
| /// |
| /// {@template flutter.services.method_channel.FIFO} |
| /// All [MethodChannel]s provided by the Flutter framework guarantee FIFO |
| /// ordering. Applications can assume method calls sent via a built-in |
| /// [MethodChannel] are received by the platform plugins in the same order as |
| /// they're sent. |
| /// {@endtemplate} |
| /// |
| /// See: <https://flutter.dev/platform-channels/> |
| class MethodChannel { |
| /// Creates a [MethodChannel] with the specified [name]. |
| /// |
| /// The [codec] used will be [StandardMethodCodec], unless otherwise |
| /// specified. |
| /// |
| /// The [name] and [codec] arguments cannot be null. The default [ServicesBinding.defaultBinaryMessenger] |
| /// instance is used if [binaryMessenger] is null. |
| const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger ]) |
| : assert(name != null), |
| assert(codec != null), |
| _binaryMessenger = binaryMessenger; |
| |
| /// The logical channel on which communication happens, not null. |
| final String name; |
| |
| /// The message codec used by this channel, not null. |
| final MethodCodec codec; |
| |
| /// The messenger which sends the bytes for this channel. |
| /// |
| /// On the root isolate or web, this defaults to the |
| /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default |
| /// value is a [BackgroundIsolateBinaryMessenger] from |
| /// [BackgroundIsolateBinaryMessenger.ensureInitialized]. |
| BinaryMessenger get binaryMessenger { |
| final BinaryMessenger result = _binaryMessenger ?? _findBinaryMessenger(); |
| return !kReleaseMode && debugProfilePlatformChannels |
| ? _debugBinaryMessengers[this] ??= _ProfiledBinaryMessenger( |
| // ignore: no_runtimetype_tostring |
| result, runtimeType.toString(), codec.runtimeType.toString()) |
| : result; |
| } |
| final BinaryMessenger? _binaryMessenger; |
| |
| /// Backend implementation of [invokeMethod]. |
| /// |
| /// The `method` and `arguments` arguments are used to create a [MethodCall] |
| /// object that is passed to the [codec]'s [MethodCodec.encodeMethodCall] |
| /// method. The resulting message is then sent to the embedding using the |
| /// [binaryMessenger]'s [BinaryMessenger.send] method. |
| /// |
| /// If the result is null and `missingOk` is true, this returns null. (This is |
| /// the behavior of [OptionalMethodChannel.invokeMethod].) |
| /// |
| /// If the result is null and `missingOk` is false, this throws a |
| /// [MissingPluginException]. (This is the behavior of |
| /// [MethodChannel.invokeMethod].) |
| /// |
| /// Otherwise, the result is decoded using the [codec]'s |
| /// [MethodCodec.decodeEnvelope] method. |
| /// |
| /// The `T` type argument is the expected return type. It is treated as |
| /// nullable. |
| @optionalTypeArgs |
| Future<T?> _invokeMethod<T>(String method, { required bool missingOk, dynamic arguments }) async { |
| assert(method != null); |
| final ByteData input = codec.encodeMethodCall(MethodCall(method, arguments)); |
| final ByteData? result = |
| !kReleaseMode && debugProfilePlatformChannels ? |
| await (binaryMessenger as _ProfiledBinaryMessenger).sendWithPostfix(name, '#$method', input) : |
| await binaryMessenger.send(name, input); |
| if (result == null) { |
| if (missingOk) { |
| return null; |
| } |
| throw MissingPluginException('No implementation found for method $method on channel $name'); |
| } |
| return codec.decodeEnvelope(result) as T?; |
| } |
| |
| /// Invokes a [method] on this channel with the specified [arguments]. |
| /// |
| /// The static type of [arguments] is `dynamic`, but only values supported by |
| /// the [codec] of this channel can be used. The same applies to the returned |
| /// result. The values supported by the default codec and their platform-specific |
| /// counterparts are documented with [StandardMessageCodec]. |
| /// |
| /// The generic argument `T` of the method can be inferred by the surrounding |
| /// context, or provided explicitly. If it does not match the returned type of |
| /// the channel, a [TypeError] will be thrown at runtime. `T` cannot be a class |
| /// with generics other than `dynamic`. For example, `Map<String, String>` |
| /// is not supported but `Map<dynamic, dynamic>` or `Map` is. |
| /// |
| /// Returns a [Future] which completes to one of the following: |
| /// |
| /// * a result (possibly null), on successful invocation; |
| /// * a [PlatformException], if the invocation failed in the platform plugin; |
| /// * a [MissingPluginException], if the method has not been implemented by a |
| /// platform plugin. |
| /// |
| /// The following code snippets demonstrate how to invoke platform methods |
| /// in Dart using a MethodChannel and how to implement those methods in Java |
| /// (for Android) and Objective-C (for iOS). |
| /// |
| /// {@tool snippet} |
| /// |
| /// The code might be packaged up as a musical plugin, see |
| /// <https://flutter.dev/developing-packages/>: |
| /// |
| /// ```dart |
| /// class Music { |
| /// // Class cannot be instantiated. |
| /// const Music._(); |
| /// |
| /// static const MethodChannel _channel = MethodChannel('music'); |
| /// |
| /// static Future<bool> isLicensed() async { |
| /// // invokeMethod returns a Future<T?>, so we handle the case where |
| /// // the return value is null by treating null as false. |
| /// return _channel.invokeMethod<bool>('isLicensed').then<bool>((bool? value) => value ?? false); |
| /// } |
| /// |
| /// static Future<List<Song>> songs() async { |
| /// // invokeMethod here returns a Future<dynamic> that completes to a |
| /// // List<dynamic> with Map<dynamic, dynamic> entries. Post-processing |
| /// // code thus cannot assume e.g. List<Map<String, String>> even though |
| /// // the actual values involved would support such a typed container. |
| /// // The correct type cannot be inferred with any value of `T`. |
| /// final List<dynamic>? songs = await _channel.invokeMethod<List<dynamic>>('getSongs'); |
| /// return songs?.cast<Map<String, Object?>>().map<Song>(Song.fromJson).toList() ?? <Song>[]; |
| /// } |
| /// |
| /// static Future<void> play(Song song, double volume) async { |
| /// // Errors occurring on the platform side cause invokeMethod to throw |
| /// // PlatformExceptions. |
| /// try { |
| /// return _channel.invokeMethod('play', <String, dynamic>{ |
| /// 'song': song.id, |
| /// 'volume': volume, |
| /// }); |
| /// } on PlatformException catch (e) { |
| /// throw ArgumentError('Unable to play ${song.title}: ${e.message}'); |
| /// } |
| /// } |
| /// } |
| /// |
| /// class Song { |
| /// Song(this.id, this.title, this.artist); |
| /// |
| /// final String id; |
| /// final String title; |
| /// final String artist; |
| /// |
| /// static Song fromJson(Map<String, Object?> json) { |
| /// return Song(json['id']! as String, json['title']! as String, json['artist']! as String); |
| /// } |
| /// } |
| /// ``` |
| /// {@end-tool} |
| /// |
| /// {@tool snippet} |
| /// |
| /// Java (for Android): |
| /// |
| /// ```java |
| /// // Assumes existence of an Android MusicApi. |
| /// public class MusicPlugin implements MethodCallHandler { |
| /// @Override |
| /// public void onMethodCall(MethodCall call, Result result) { |
| /// switch (call.method) { |
| /// case "isLicensed": |
| /// result.success(MusicApi.checkLicense()); |
| /// break; |
| /// case "getSongs": |
| /// final List<MusicApi.Track> tracks = MusicApi.getTracks(); |
| /// final List<Object> json = ArrayList<>(tracks.size()); |
| /// for (MusicApi.Track track : tracks) { |
| /// json.add(track.toJson()); // Map<String, Object> entries |
| /// } |
| /// result.success(json); |
| /// break; |
| /// case "play": |
| /// final String song = call.argument("song"); |
| /// final double volume = call.argument("volume"); |
| /// try { |
| /// MusicApi.playSongAtVolume(song, volume); |
| /// result.success(null); |
| /// } catch (MusicalException e) { |
| /// result.error("playError", e.getMessage(), null); |
| /// } |
| /// break; |
| /// default: |
| /// result.notImplemented(); |
| /// } |
| /// } |
| /// // Other methods elided. |
| /// } |
| /// ``` |
| /// {@end-tool} |
| /// |
| /// {@tool snippet} |
| /// |
| /// Objective-C (for iOS): |
| /// |
| /// ```objectivec |
| /// @interface MusicPlugin : NSObject<FlutterPlugin> |
| /// @end |
| /// |
| /// // Assumes existence of an iOS Broadway Play Api. |
| /// @implementation MusicPlugin |
| /// - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { |
| /// if ([@"isLicensed" isEqualToString:call.method]) { |
| /// result([NSNumber numberWithBool:[BWPlayApi isLicensed]]); |
| /// } else if ([@"getSongs" isEqualToString:call.method]) { |
| /// NSArray* items = [BWPlayApi items]; |
| /// NSMutableArray* json = [NSMutableArray arrayWithCapacity:items.count]; |
| /// for (final BWPlayItem* item in items) { |
| /// [json addObject:@{ @"id":item.itemId, @"title":item.name, @"artist":item.artist }]; |
| /// } |
| /// result(json); |
| /// } else if ([@"play" isEqualToString:call.method]) { |
| /// NSString* itemId = call.arguments[@"song"]; |
| /// NSNumber* volume = call.arguments[@"volume"]; |
| /// NSError* error = nil; |
| /// BOOL success = [BWPlayApi playItem:itemId volume:volume.doubleValue error:&error]; |
| /// if (success) { |
| /// result(nil); |
| /// } else { |
| /// result([FlutterError errorWithCode:[NSString stringWithFormat:@"Error %ld", error.code] |
| /// message:error.domain |
| /// details:error.localizedDescription]); |
| /// } |
| /// } else { |
| /// result(FlutterMethodNotImplemented); |
| /// } |
| /// } |
| /// // Other methods elided. |
| /// @end |
| /// ``` |
| /// {@end-tool} |
| /// |
| /// See also: |
| /// |
| /// * [invokeListMethod], for automatically returning typed lists. |
| /// * [invokeMapMethod], for automatically returning typed maps. |
| /// * [StandardMessageCodec] which defines the payload values supported by |
| /// [StandardMethodCodec]. |
| /// * [JSONMessageCodec] which defines the payload values supported by |
| /// [JSONMethodCodec]. |
| /// * <https://api.flutter.dev/javadoc/io/flutter/plugin/common/MethodCall.html> |
| /// for how to access method call arguments on Android. |
| @optionalTypeArgs |
| Future<T?> invokeMethod<T>(String method, [ dynamic arguments ]) { |
| return _invokeMethod<T>(method, missingOk: false, arguments: arguments); |
| } |
| |
| /// An implementation of [invokeMethod] that can return typed lists. |
| /// |
| /// Dart generics are reified, meaning that an untyped `List<dynamic>` cannot |
| /// masquerade as a `List<T>`. Since [invokeMethod] can only return dynamic |
| /// maps, we instead create a new typed list using [List.cast]. |
| /// |
| /// See also: |
| /// |
| /// * [invokeMethod], which this call delegates to. |
| Future<List<T>?> invokeListMethod<T>(String method, [ dynamic arguments ]) async { |
| final List<dynamic>? result = await invokeMethod<List<dynamic>>(method, arguments); |
| return result?.cast<T>(); |
| } |
| |
| /// An implementation of [invokeMethod] that can return typed maps. |
| /// |
| /// Dart generics are reified, meaning that an untyped `Map<dynamic, dynamic>` |
| /// cannot masquerade as a `Map<K, V>`. Since [invokeMethod] can only return |
| /// dynamic maps, we instead create a new typed map using [Map.cast]. |
| /// |
| /// See also: |
| /// |
| /// * [invokeMethod], which this call delegates to. |
| Future<Map<K, V>?> invokeMapMethod<K, V>(String method, [ dynamic arguments ]) async { |
| final Map<dynamic, dynamic>? result = await invokeMethod<Map<dynamic, dynamic>>(method, arguments); |
| return result?.cast<K, V>(); |
| } |
| |
| /// Sets a callback for receiving method calls on this channel. |
| /// |
| /// The given callback will replace the currently registered callback for this |
| /// channel, if any. To remove the handler, pass null as the |
| /// `handler` argument. |
| /// |
| /// If the future returned by the handler completes with a result, that value |
| /// is sent back to the platform plugin caller wrapped in a success envelope |
| /// as defined by the [codec] of this channel. If the future completes with |
| /// a [PlatformException], the fields of that exception will be used to |
| /// populate an error envelope which is sent back instead. If the future |
| /// completes with a [MissingPluginException], an empty reply is sent |
| /// similarly to what happens if no method call handler has been set. |
| /// Any other exception results in an error envelope being sent. |
| void setMethodCallHandler(Future<dynamic> Function(MethodCall call)? handler) { |
| assert( |
| _binaryMessenger != null || ServicesBinding.instance != null, |
| 'Cannot set the method call handler before the binary messenger has been initialized. ' |
| 'This happens when you call setMethodCallHandler() before the WidgetsFlutterBinding ' |
| 'has been initialized. You can fix this by either calling WidgetsFlutterBinding.ensureInitialized() ' |
| 'before this or by passing a custom BinaryMessenger instance to MethodChannel().', |
| ); |
| binaryMessenger.setMessageHandler( |
| name, |
| handler == null |
| ? null |
| : (ByteData? message) => _handleAsMethodCall(message, handler), |
| ); |
| } |
| |
| Future<ByteData?> _handleAsMethodCall(ByteData? message, Future<dynamic> Function(MethodCall call) handler) async { |
| final MethodCall call = codec.decodeMethodCall(message); |
| try { |
| return codec.encodeSuccessEnvelope(await handler(call)); |
| } on PlatformException catch (e) { |
| return codec.encodeErrorEnvelope( |
| code: e.code, |
| message: e.message, |
| details: e.details, |
| ); |
| } on MissingPluginException { |
| return null; |
| } catch (error) { |
| return codec.encodeErrorEnvelope(code: 'error', message: error.toString()); |
| } |
| } |
| |
| // Looking for setMockMethodCallHandler or checkMethodCallHandler? |
| // See this shim package: packages/flutter_test/lib/src/deprecated.dart |
| } |
| |
| /// A [MethodChannel] that ignores missing platform plugins. |
| /// |
| /// When [invokeMethod] fails to find the platform plugin, it returns null |
| /// instead of throwing an exception. |
| /// |
| /// {@macro flutter.services.method_channel.FIFO} |
| class OptionalMethodChannel extends MethodChannel { |
| /// Creates a [MethodChannel] that ignores missing platform plugins. |
| const OptionalMethodChannel(super.name, [super.codec, super.binaryMessenger]); |
| |
| @override |
| Future<T?> invokeMethod<T>(String method, [ dynamic arguments ]) async { |
| return super._invokeMethod<T>(method, missingOk: true, arguments: arguments); |
| } |
| } |
| |
| /// A named channel for communicating with platform plugins using event streams. |
| /// |
| /// Stream setup requests are encoded into binary before being sent, |
| /// and binary events and errors received are decoded into Dart values. |
| /// The [MethodCodec] used must be compatible with the one used by the platform |
| /// plugin. This can be achieved by creating an [EventChannel] counterpart of |
| /// this channel on the platform side. The Dart type of events sent and received |
| /// is `dynamic`, but only values supported by the specified [MethodCodec] can |
| /// be used. |
| /// |
| /// The logical identity of the channel is given by its name. Identically named |
| /// channels will interfere with each other's communication. |
| /// |
| /// See: <https://flutter.dev/platform-channels/> |
| class EventChannel { |
| /// Creates an [EventChannel] with the specified [name]. |
| /// |
| /// The [codec] used will be [StandardMethodCodec], unless otherwise |
| /// specified. |
| /// |
| /// Neither [name] nor [codec] may be null. The default [ServicesBinding.defaultBinaryMessenger] |
| /// instance is used if [binaryMessenger] is null. |
| const EventChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger]) |
| : assert(name != null), |
| assert(codec != null), |
| _binaryMessenger = binaryMessenger; |
| |
| /// The logical channel on which communication happens, not null. |
| final String name; |
| |
| /// The message codec used by this channel, not null. |
| final MethodCodec codec; |
| |
| /// The messenger which sends the bytes for this channel. |
| /// |
| /// On the root isolate or web, this defaults to the |
| /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default |
| /// value is a [BackgroundIsolateBinaryMessenger] from |
| /// [BackgroundIsolateBinaryMessenger.ensureInitialized]. |
| BinaryMessenger get binaryMessenger => |
| _binaryMessenger ?? _findBinaryMessenger(); |
| final BinaryMessenger? _binaryMessenger; |
| |
| /// Sets up a broadcast stream for receiving events on this channel. |
| /// |
| /// Returns a broadcast [Stream] which emits events to listeners as follows: |
| /// |
| /// * a decoded data event (possibly null) for each successful event |
| /// received from the platform plugin; |
| /// * an error event containing a [PlatformException] for each error event |
| /// received from the platform plugin. |
| /// |
| /// Errors occurring during stream activation or deactivation are reported |
| /// through the [FlutterError] facility. Stream activation happens only when |
| /// stream listener count changes from 0 to 1. Stream deactivation happens |
| /// only when stream listener count changes from 1 to 0. |
| Stream<dynamic> receiveBroadcastStream([ dynamic arguments ]) { |
| final MethodChannel methodChannel = MethodChannel(name, codec); |
| late StreamController<dynamic> controller; |
| controller = StreamController<dynamic>.broadcast(onListen: () async { |
| binaryMessenger.setMessageHandler(name, (ByteData? reply) async { |
| if (reply == null) { |
| controller.close(); |
| } else { |
| try { |
| controller.add(codec.decodeEnvelope(reply)); |
| } on PlatformException catch (e) { |
| controller.addError(e); |
| } |
| } |
| return null; |
| }); |
| try { |
| await methodChannel.invokeMethod<void>('listen', arguments); |
| } catch (exception, stack) { |
| FlutterError.reportError(FlutterErrorDetails( |
| exception: exception, |
| stack: stack, |
| library: 'services library', |
| context: ErrorDescription('while activating platform stream on channel $name'), |
| )); |
| } |
| }, onCancel: () async { |
| binaryMessenger.setMessageHandler(name, null); |
| try { |
| await methodChannel.invokeMethod<void>('cancel', arguments); |
| } catch (exception, stack) { |
| FlutterError.reportError(FlutterErrorDetails( |
| exception: exception, |
| stack: stack, |
| library: 'services library', |
| context: ErrorDescription('while de-activating platform stream on channel $name'), |
| )); |
| } |
| }); |
| return controller.stream; |
| } |
| } |