1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6import 'dart:developer';
7
8import 'package:flutter/foundation.dart';
9
10import '_background_isolate_binary_messenger_io.dart'
11 if (dart.library.js_util) '_background_isolate_binary_messenger_web.dart';
12
13import 'binary_messenger.dart';
14import 'binding.dart';
15import 'debug.dart';
16import 'message_codec.dart';
17import 'message_codecs.dart';
18
19export '_background_isolate_binary_messenger_io.dart'
20 if (dart.library.js_util) '_background_isolate_binary_messenger_web.dart';
21
22export 'binary_messenger.dart' show BinaryMessenger;
23export 'binding.dart' show RootIsolateToken;
24export 'message_codec.dart' show MessageCodec, MethodCall, MethodCodec;
25
26/// Profile and print statistics on Platform Channel usage.
27///
28/// When this is true statistics about the usage of Platform Channels will be
29/// printed out periodically to the console and Timeline events will show the
30/// time between sending and receiving a message (encoding and decoding time
31/// excluded).
32///
33/// The statistics include the total bytes transmitted and the average number of
34/// bytes per invocation in the last quantum. "Up" means in the direction of
35/// Flutter to the host platform, "down" is the host platform to flutter.
36bool get shouldProfilePlatformChannels =>
37 kProfilePlatformChannels || (!kReleaseMode && debugProfilePlatformChannels);
38
39/// Controls whether platform channel usage can be debugged in release mode.
40///
41/// See also:
42///
43/// * [shouldProfilePlatformChannels], which checks both
44/// [kProfilePlatformChannels] and [debugProfilePlatformChannels] for the
45/// current run mode.
46/// * [debugProfilePlatformChannels], which determines whether platform
47/// channel usage can be debugged in non-release mode.
48const bool kProfilePlatformChannels = false;
49
50bool _profilePlatformChannelsIsRunning = false;
51const Duration _profilePlatformChannelsRate = Duration(seconds: 1);
52final Expando<BinaryMessenger> _profiledBinaryMessengers = Expando<BinaryMessenger>();
53
54class _ProfiledBinaryMessenger implements BinaryMessenger {
55 const _ProfiledBinaryMessenger(this.proxy, this.channelTypeName, this.codecTypeName);
56 final BinaryMessenger proxy;
57 final String channelTypeName;
58 final String codecTypeName;
59
60 @override
61 Future<void> handlePlatformMessage(
62 String channel,
63 ByteData? data,
64 PlatformMessageResponseCallback? callback,
65 ) {
66 return proxy.handlePlatformMessage(channel, data, callback);
67 }
68
69 Future<ByteData?>? sendWithPostfix(String channel, String postfix, ByteData? message) async {
70 _debugRecordUpStream(channelTypeName, '$channel$postfix', codecTypeName, message);
71 final TimelineTask timelineTask = TimelineTask()
72 ..start('Platform Channel send $channel$postfix');
73 final ByteData? result;
74 try {
75 result = await proxy.send(channel, message);
76 } finally {
77 timelineTask.finish();
78 }
79 _debugRecordDownStream(channelTypeName, '$channel$postfix', codecTypeName, result);
80 return result;
81 }
82
83 @override
84 Future<ByteData?>? send(String channel, ByteData? message) =>
85 sendWithPostfix(channel, '', message);
86
87 @override
88 void setMessageHandler(String channel, MessageHandler? handler) {
89 proxy.setMessageHandler(channel, handler);
90 }
91}
92
93class _PlatformChannelStats {
94 _PlatformChannelStats(this.channel, this.codec, this.type);
95
96 final String channel;
97 final String codec;
98 final String type;
99
100 int _upCount = 0;
101 int _upBytes = 0;
102 int get upBytes => _upBytes;
103 void addUpStream(int bytes) {
104 _upCount += 1;
105 _upBytes += bytes;
106 }
107
108 int _downCount = 0;
109 int _downBytes = 0;
110 int get downBytes => _downBytes;
111 void addDownStream(int bytes) {
112 _downCount += 1;
113 _downBytes += bytes;
114 }
115
116 double get averageUpPayload => _upBytes / _upCount;
117 double get averageDownPayload => _downBytes / _downCount;
118}
119
120final Map<String, _PlatformChannelStats> _profilePlatformChannelsStats =
121 <String, _PlatformChannelStats>{};
122
123Future<void> _debugLaunchProfilePlatformChannels() async {
124 if (!_profilePlatformChannelsIsRunning) {
125 _profilePlatformChannelsIsRunning = true;
126 await Future<dynamic>.delayed(_profilePlatformChannelsRate);
127 _profilePlatformChannelsIsRunning = false;
128 final StringBuffer log = StringBuffer();
129 log.writeln('Platform Channel Stats:');
130 final List<_PlatformChannelStats> allStats = _profilePlatformChannelsStats.values.toList();
131 // Sort highest combined bandwidth first.
132 allStats.sort(
133 (_PlatformChannelStats x, _PlatformChannelStats y) =>
134 (y.upBytes + y.downBytes) - (x.upBytes + x.downBytes),
135 );
136 for (final _PlatformChannelStats stats in allStats) {
137 log.writeln(
138 ' (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)})',
139 );
140 }
141 debugPrint(log.toString());
142 _profilePlatformChannelsStats.clear();
143 }
144}
145
146void _debugRecordUpStream(
147 String channelTypeName,
148 String name,
149 String codecTypeName,
150 ByteData? bytes,
151) {
152 final _PlatformChannelStats stats = _profilePlatformChannelsStats[name] ??= _PlatformChannelStats(
153 name,
154 codecTypeName,
155 channelTypeName,
156 );
157 stats.addUpStream(bytes?.lengthInBytes ?? 0);
158 _debugLaunchProfilePlatformChannels();
159}
160
161void _debugRecordDownStream(
162 String channelTypeName,
163 String name,
164 String codecTypeName,
165 ByteData? bytes,
166) {
167 final _PlatformChannelStats stats = _profilePlatformChannelsStats[name] ??= _PlatformChannelStats(
168 name,
169 codecTypeName,
170 channelTypeName,
171 );
172 stats.addDownStream(bytes?.lengthInBytes ?? 0);
173 _debugLaunchProfilePlatformChannels();
174}
175
176BinaryMessenger _findBinaryMessenger() {
177 return !kIsWeb && ServicesBinding.rootIsolateToken == null
178 ? BackgroundIsolateBinaryMessenger.instance
179 : ServicesBinding.instance.defaultBinaryMessenger;
180}
181
182/// A named channel for communicating with platform plugins using asynchronous
183/// message passing.
184///
185/// Messages are encoded into binary before being sent, and binary messages
186/// received are decoded into Dart values. The [MessageCodec] used must be
187/// compatible with the one used by the platform plugin. This can be achieved
188/// by creating a basic message channel counterpart of this channel on the
189/// platform side. The Dart type of messages sent and received is [T],
190/// but only the values supported by the specified [MessageCodec] can be used.
191/// The use of unsupported values should be considered programming errors, and
192/// will result in exceptions being thrown. The null message is supported
193/// for all codecs.
194///
195/// The logical identity of the channel is given by its name. Identically named
196/// channels will interfere with each other's communication.
197///
198/// All [BasicMessageChannel]s provided by the Flutter framework guarantee FIFO
199/// ordering. Applications can assume messages sent via a built-in
200/// [BasicMessageChannel] are delivered in the same order as they're sent.
201///
202/// See: <https://flutter.dev/to/platform-channels/>
203class BasicMessageChannel<T> {
204 /// Creates a [BasicMessageChannel] with the specified [name], [codec] and
205 /// [binaryMessenger].
206 ///
207 /// The default [ServicesBinding.defaultBinaryMessenger] instance is used if
208 /// [binaryMessenger] is null.
209 const BasicMessageChannel(this.name, this.codec, {BinaryMessenger? binaryMessenger})
210 : _binaryMessenger = binaryMessenger;
211
212 /// The logical channel on which communication happens, not null.
213 final String name;
214
215 /// The message codec used by this channel, not null.
216 final MessageCodec<T> codec;
217
218 /// The messenger which sends the bytes for this channel.
219 ///
220 /// On the root isolate or web, this defaults to the
221 /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default
222 /// value is a [BackgroundIsolateBinaryMessenger] from
223 /// [BackgroundIsolateBinaryMessenger.ensureInitialized].
224 BinaryMessenger get binaryMessenger {
225 final BinaryMessenger result = _binaryMessenger ?? _findBinaryMessenger();
226 return shouldProfilePlatformChannels
227 ? _profiledBinaryMessengers[this] ??= _ProfiledBinaryMessenger(
228 result,
229 runtimeType.toString(), // ignore: no_runtimetype_tostring
230 codec.runtimeType.toString(),
231 )
232 : result;
233 }
234
235 final BinaryMessenger? _binaryMessenger;
236
237 /// Sends the specified [message] to the platform plugins on this channel.
238 ///
239 /// Returns a [Future] which completes to the received response, which may
240 /// be null.
241 Future<T?> send(T message) async {
242 return codec.decodeMessage(await binaryMessenger.send(name, codec.encodeMessage(message)));
243 }
244
245 /// Sets a callback for receiving messages from the platform plugins on this
246 /// channel. Messages may be null.
247 ///
248 /// The given callback will replace the currently registered callback for this
249 /// channel, if any. To remove the handler, pass null as the `handler`
250 /// argument.
251 ///
252 /// The handler's return value is sent back to the platform plugins as a
253 /// message reply. It may be null.
254 void setMessageHandler(Future<T> Function(T? message)? handler) {
255 if (handler == null) {
256 binaryMessenger.setMessageHandler(name, null);
257 } else {
258 binaryMessenger.setMessageHandler(name, (ByteData? message) async {
259 return codec.encodeMessage(await handler(codec.decodeMessage(message)));
260 });
261 }
262 }
263
264 // Looking for setMockMessageHandler?
265 // See this shim package: packages/flutter_test/lib/src/deprecated.dart
266}
267
268/// A named channel for communicating with platform plugins using asynchronous
269/// method calls.
270///
271/// Method calls are encoded into binary before being sent, and binary results
272/// received are decoded into Dart values. The [MethodCodec] used must be
273/// compatible with the one used by the platform plugin. This can be achieved
274/// by creating a method channel counterpart of this channel on the
275/// platform side. The Dart type of arguments and results is `dynamic`,
276/// but only values supported by the specified [MethodCodec] can be used.
277/// The use of unsupported values should be considered programming errors, and
278/// will result in exceptions being thrown. The null value is supported
279/// for all codecs.
280///
281/// The logical identity of the channel is given by its name. Identically named
282/// channels will interfere with each other's communication.
283///
284/// {@template flutter.services.method_channel.FIFO}
285/// All [MethodChannel]s provided by the Flutter framework guarantee FIFO
286/// ordering. Applications can assume method calls sent via a built-in
287/// [MethodChannel] are received by the platform plugins in the same order as
288/// they're sent.
289/// {@endtemplate}
290///
291/// See: <https://flutter.dev/to/platform-channels/>
292@pragma('vm:keep-name')
293class MethodChannel {
294 /// Creates a [MethodChannel] with the specified [name].
295 ///
296 /// The [codec] used will be [StandardMethodCodec], unless otherwise
297 /// specified.
298 ///
299 /// The default [ServicesBinding.defaultBinaryMessenger] instance is used if
300 /// [binaryMessenger] is null.
301 const MethodChannel(
302 this.name, [
303 this.codec = const StandardMethodCodec(),
304 BinaryMessenger? binaryMessenger,
305 ]) : _binaryMessenger = binaryMessenger;
306
307 /// The logical channel on which communication happens, not null.
308 final String name;
309
310 /// The message codec used by this channel, not null.
311 final MethodCodec codec;
312
313 /// The messenger which sends the bytes for this channel.
314 ///
315 /// On the root isolate or web, this defaults to the
316 /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default
317 /// value is a [BackgroundIsolateBinaryMessenger] from
318 /// [BackgroundIsolateBinaryMessenger.ensureInitialized].
319 BinaryMessenger get binaryMessenger {
320 final BinaryMessenger result = _binaryMessenger ?? _findBinaryMessenger();
321 return shouldProfilePlatformChannels
322 ? _profiledBinaryMessengers[this] ??= _ProfiledBinaryMessenger(
323 result,
324 runtimeType.toString(), // ignore: no_runtimetype_tostring
325 codec.runtimeType.toString(),
326 )
327 : result;
328 }
329
330 final BinaryMessenger? _binaryMessenger;
331
332 /// Backend implementation of [invokeMethod].
333 ///
334 /// The `method` and `arguments` arguments are used to create a [MethodCall]
335 /// object that is passed to the [codec]'s [MethodCodec.encodeMethodCall]
336 /// method. The resulting message is then sent to the embedding using the
337 /// [binaryMessenger]'s [BinaryMessenger.send] method.
338 ///
339 /// If the result is null and `missingOk` is true, this returns null. (This is
340 /// the behavior of [OptionalMethodChannel.invokeMethod].)
341 ///
342 /// If the result is null and `missingOk` is false, this throws a
343 /// [MissingPluginException]. (This is the behavior of
344 /// [MethodChannel.invokeMethod].)
345 ///
346 /// Otherwise, the result is decoded using the [codec]'s
347 /// [MethodCodec.decodeEnvelope] method.
348 ///
349 /// The `T` type argument is the expected return type. It is treated as
350 /// nullable.
351 @optionalTypeArgs
352 Future<T?> _invokeMethod<T>(String method, {required bool missingOk, dynamic arguments}) async {
353 final ByteData input = codec.encodeMethodCall(MethodCall(method, arguments));
354 final ByteData? result = shouldProfilePlatformChannels
355 ? await (binaryMessenger as _ProfiledBinaryMessenger).sendWithPostfix(
356 name,
357 '#$method',
358 input,
359 )
360 : await binaryMessenger.send(name, input);
361 if (result == null) {
362 if (missingOk) {
363 return null;
364 }
365 throw MissingPluginException('No implementation found for method $method on channel $name');
366 }
367 return codec.decodeEnvelope(result) as T?;
368 }
369
370 /// Invokes a [method] on this channel with the specified [arguments].
371 ///
372 /// The static type of [arguments] is `dynamic`, but only values supported by
373 /// the [codec] of this channel can be used. The same applies to the returned
374 /// result. The values supported by the default codec and their platform-specific
375 /// counterparts are documented with [StandardMessageCodec].
376 ///
377 /// The generic argument `T` of the method can be inferred by the surrounding
378 /// context, or provided explicitly. If it does not match the returned type of
379 /// the channel, a [TypeError] will be thrown at runtime. `T` cannot be a class
380 /// with generics other than `dynamic`. For example, `Map<String, String>`
381 /// is not supported but `Map<dynamic, dynamic>` or `Map` is.
382 ///
383 /// Returns a [Future] which completes to one of the following:
384 ///
385 /// * a result (possibly null), on successful invocation;
386 /// * a [PlatformException], if the invocation failed in the platform plugin;
387 /// * a [MissingPluginException], if the method has not been implemented by a
388 /// platform plugin.
389 ///
390 /// The following code snippets demonstrate how to invoke platform methods
391 /// in Dart using a MethodChannel and how to implement those methods in Java
392 /// (for Android) and Objective-C (for iOS).
393 ///
394 /// {@tool snippet}
395 ///
396 /// The code might be packaged up as a musical plugin, see
397 /// <https://flutter.dev/to/develop-packages>:
398 ///
399 /// ```dart
400 /// abstract final class Music {
401 /// static const MethodChannel _channel = MethodChannel('music');
402 ///
403 /// static Future<bool> isLicensed() async {
404 /// // invokeMethod returns a Future, so we handle the case where
405 /// // the return value is null by treating null as false.
406 /// return _channel.invokeMethod<bool>('isLicensed').then<bool>((bool? value) => value ?? false);
407 /// }
408 ///
409 /// static Future<List<Song>> songs() async {
410 /// // invokeMethod here returns a Future that completes to a
411 /// // List with Map entries. Post-processing
412 /// // code thus cannot assume e.g. List> even though
413 /// // the actual values involved would support such a typed container.
414 /// // The correct type cannot be inferred with any value of `T`.
415 /// final List<dynamic>? songs = await _channel.invokeMethod<List<dynamic>>('getSongs');
416 /// return songs?.cast<Map<String, Object?>>().map<Song>(Song.fromJson).toList() ?? <Song>[];
417 /// }
418 ///
419 /// static Future<void> play(Song song, double volume) async {
420 /// // Errors occurring on the platform side cause invokeMethod to throw
421 /// // PlatformExceptions.
422 /// try {
423 /// return _channel.invokeMethod('play', <String, dynamic>{
424 /// 'song': song.id,
425 /// 'volume': volume,
426 /// });
427 /// } on PlatformException catch (e) {
428 /// throw ArgumentError('Unable to play ${song.title}: ${e.message}');
429 /// }
430 /// }
431 /// }
432 ///
433 /// class Song {
434 /// Song(this.id, this.title, this.artist);
435 ///
436 /// final String id;
437 /// final String title;
438 /// final String artist;
439 ///
440 /// static Song fromJson(Map<String, Object?> json) {
441 /// return Song(json['id']! as String, json['title']! as String, json['artist']! as String);
442 /// }
443 /// }
444 /// ```
445 /// {@end-tool}
446 ///
447 /// {@tool snippet}
448 ///
449 /// Java (for Android):
450 ///
451 /// ```java
452 /// // Assumes existence of an Android MusicApi.
453 /// public class MusicPlugin implements MethodCallHandler {
454 /// @Override
455 /// public void onMethodCall(MethodCall call, Result result) {
456 /// switch (call.method) {
457 /// case "isLicensed":
458 /// result.success(MusicApi.checkLicense());
459 /// break;
460 /// case "getSongs":
461 /// final List<MusicApi.Track> tracks = MusicApi.getTracks();
462 /// final List<Object> json = ArrayList<>(tracks.size());
463 /// for (MusicApi.Track track : tracks) {
464 /// json.add(track.toJson()); // Map entries
465 /// }
466 /// result.success(json);
467 /// break;
468 /// case "play":
469 /// final String song = call.argument("song");
470 /// final double volume = call.argument("volume");
471 /// try {
472 /// MusicApi.playSongAtVolume(song, volume);
473 /// result.success(null);
474 /// } catch (MusicalException e) {
475 /// result.error("playError", e.getMessage(), null);
476 /// }
477 /// break;
478 /// default:
479 /// result.notImplemented();
480 /// }
481 /// }
482 /// // Other methods elided.
483 /// }
484 /// ```
485 /// {@end-tool}
486 ///
487 /// {@tool snippet}
488 ///
489 /// Objective-C (for iOS):
490 ///
491 /// ```objectivec
492 /// @interface MusicPlugin : NSObject<FlutterPlugin>
493 /// @end
494 ///
495 /// // Assumes existence of an iOS Broadway Play Api.
496 /// @implementation MusicPlugin
497 /// - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
498 /// if ([@"isLicensed" isEqualToString:call.method]) {
499 /// result([NSNumber numberWithBool:[BWPlayApi isLicensed]]);
500 /// } else if ([@"getSongs" isEqualToString:call.method]) {
501 /// NSArray* items = [BWPlayApi items];
502 /// NSMutableArray* json = [NSMutableArray arrayWithCapacity:items.count];
503 /// for (final BWPlayItem* item in items) {
504 /// [json addObject:@{ @"id":item.itemId, @"title":item.name, @"artist":item.artist }];
505 /// }
506 /// result(json);
507 /// } else if ([@"play" isEqualToString:call.method]) {
508 /// NSString* itemId = call.arguments[@"song"];
509 /// NSNumber* volume = call.arguments[@"volume"];
510 /// NSError* error = nil;
511 /// BOOL success = [BWPlayApi playItem:itemId volume:volume.doubleValue error:&error];
512 /// if (success) {
513 /// result(nil);
514 /// } else {
515 /// result([FlutterError errorWithCode:[NSString stringWithFormat:@"Error %ld", error.code]
516 /// message:error.domain
517 /// details:error.localizedDescription]);
518 /// }
519 /// } else {
520 /// result(FlutterMethodNotImplemented);
521 /// }
522 /// }
523 /// // Other methods elided.
524 /// @end
525 /// ```
526 /// {@end-tool}
527 ///
528 /// See also:
529 ///
530 /// * [invokeListMethod], for automatically returning typed lists.
531 /// * [invokeMapMethod], for automatically returning typed maps.
532 /// * [StandardMessageCodec] which defines the payload values supported by
533 /// [StandardMethodCodec].
534 /// * [JSONMessageCodec] which defines the payload values supported by
535 /// [JSONMethodCodec].
536 /// * <https://api.flutter.dev/javadoc/io/flutter/plugin/common/MethodCall.html>
537 /// for how to access method call arguments on Android.
538 @optionalTypeArgs
539 Future<T?> invokeMethod<T>(String method, [dynamic arguments]) {
540 return _invokeMethod<T>(method, missingOk: false, arguments: arguments);
541 }
542
543 /// An implementation of [invokeMethod] that can return typed lists.
544 ///
545 /// Dart generics are reified, meaning that an untyped `List<dynamic>` cannot
546 /// masquerade as a `List<T>`. Since [invokeMethod] can only return dynamic
547 /// lists, we instead create a new typed list using [List.cast].
548 ///
549 /// See also:
550 ///
551 /// * [invokeMethod], which this call delegates to.
552 Future<List<T>?> invokeListMethod<T>(String method, [dynamic arguments]) async {
553 final List<dynamic>? result = await invokeMethod<List<dynamic>>(method, arguments);
554 return result?.cast<T>();
555 }
556
557 /// An implementation of [invokeMethod] that can return typed maps.
558 ///
559 /// Dart generics are reified, meaning that an untyped `Map<dynamic, dynamic>`
560 /// cannot masquerade as a `Map<K, V>`. Since [invokeMethod] can only return
561 /// dynamic maps, we instead create a new typed map using [Map.cast].
562 ///
563 /// See also:
564 ///
565 /// * [invokeMethod], which this call delegates to.
566 Future<Map<K, V>?> invokeMapMethod<K, V>(String method, [dynamic arguments]) async {
567 final Map<dynamic, dynamic>? result = await invokeMethod<Map<dynamic, dynamic>>(
568 method,
569 arguments,
570 );
571 return result?.cast<K, V>();
572 }
573
574 /// Sets a callback for receiving method calls on this channel.
575 ///
576 /// The given callback will replace the currently registered callback for this
577 /// channel, if any. To remove the handler, pass null as the
578 /// `handler` argument.
579 ///
580 /// If the future returned by the handler completes with a result, that value
581 /// is sent back to the platform plugin caller wrapped in a success envelope
582 /// as defined by the [codec] of this channel. If the future completes with
583 /// a [PlatformException], the fields of that exception will be used to
584 /// populate an error envelope which is sent back instead. If the future
585 /// completes with a [MissingPluginException], an empty reply is sent
586 /// similarly to what happens if no method call handler has been set.
587 /// Any other exception results in an error envelope being sent.
588 void setMethodCallHandler(Future<dynamic> Function(MethodCall call)? handler) {
589 assert(
590 _binaryMessenger != null || BindingBase.debugBindingType() != null,
591 'Cannot set the method call handler before the binary messenger has been initialized. '
592 'This happens when you call setMethodCallHandler() before the WidgetsFlutterBinding '
593 'has been initialized. You can fix this by either calling WidgetsFlutterBinding.ensureInitialized() '
594 'before this or by passing a custom BinaryMessenger instance to MethodChannel().',
595 );
596 binaryMessenger.setMessageHandler(
597 name,
598 handler == null ? null : (ByteData? message) => _handleAsMethodCall(message, handler),
599 );
600 }
601
602 Future<ByteData?> _handleAsMethodCall(
603 ByteData? message,
604 Future<dynamic> Function(MethodCall call) handler,
605 ) async {
606 final MethodCall call = codec.decodeMethodCall(message);
607 try {
608 return codec.encodeSuccessEnvelope(await handler(call));
609 } on PlatformException catch (e) {
610 return codec.encodeErrorEnvelope(code: e.code, message: e.message, details: e.details);
611 } on MissingPluginException {
612 return null;
613 } catch (error) {
614 return codec.encodeErrorEnvelope(code: 'error', message: error.toString());
615 }
616 }
617
618 // Looking for setMockMethodCallHandler or checkMethodCallHandler?
619 // See this shim package: packages/flutter_test/lib/src/deprecated.dart
620}
621
622/// A [MethodChannel] that ignores missing platform plugins.
623///
624/// When [invokeMethod] fails to find the platform plugin, it returns null
625/// instead of throwing an exception.
626///
627/// {@macro flutter.services.method_channel.FIFO}
628class OptionalMethodChannel extends MethodChannel {
629 /// Creates a [MethodChannel] that ignores missing platform plugins.
630 const OptionalMethodChannel(super.name, [super.codec, super.binaryMessenger]);
631
632 @override
633 Future<T?> invokeMethod<T>(String method, [dynamic arguments]) async {
634 return super._invokeMethod<T>(method, missingOk: true, arguments: arguments);
635 }
636}
637
638/// A named channel for communicating with platform plugins using event streams.
639///
640/// Stream setup requests are encoded into binary before being sent,
641/// and binary events and errors received are decoded into Dart values.
642/// The [MethodCodec] used must be compatible with the one used by the platform
643/// plugin. This can be achieved by creating an [EventChannel] counterpart of
644/// this channel on the platform side. The Dart type of events sent and received
645/// is `dynamic`, but only values supported by the specified [MethodCodec] can
646/// be used.
647///
648/// The logical identity of the channel is given by its name. Identically named
649/// channels will interfere with each other's communication.
650///
651/// See: <https://flutter.dev/to/platform-channels/>
652class EventChannel {
653 /// Creates an [EventChannel] with the specified [name].
654 ///
655 /// The [codec] used will be [StandardMethodCodec], unless otherwise
656 /// specified.
657 ///
658 /// Neither [name] nor [codec] may be null. The default [ServicesBinding.defaultBinaryMessenger]
659 /// instance is used if [binaryMessenger] is null.
660 const EventChannel(
661 this.name, [
662 this.codec = const StandardMethodCodec(),
663 BinaryMessenger? binaryMessenger,
664 ]) : _binaryMessenger = binaryMessenger;
665
666 /// The logical channel on which communication happens, not null.
667 final String name;
668
669 /// The message codec used by this channel, not null.
670 final MethodCodec codec;
671
672 /// The messenger which sends the bytes for this channel.
673 ///
674 /// On the root isolate or web, this defaults to the
675 /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default
676 /// value is a [BackgroundIsolateBinaryMessenger] from
677 /// [BackgroundIsolateBinaryMessenger.ensureInitialized].
678 BinaryMessenger get binaryMessenger => _binaryMessenger ?? _findBinaryMessenger();
679 final BinaryMessenger? _binaryMessenger;
680
681 /// Sets up a broadcast stream for receiving events on this channel.
682 ///
683 /// Returns a broadcast [Stream] which emits events to listeners as follows:
684 ///
685 /// * a decoded data event (possibly null) for each successful event
686 /// received from the platform plugin;
687 /// * an error event containing a [PlatformException] for each error event
688 /// received from the platform plugin.
689 ///
690 /// Errors occurring during stream activation or deactivation are reported
691 /// through the [FlutterError] facility. Stream activation happens only when
692 /// stream listener count changes from 0 to 1. Stream deactivation happens
693 /// only when stream listener count changes from 1 to 0.
694 Stream<dynamic> receiveBroadcastStream([dynamic arguments]) {
695 final MethodChannel methodChannel = MethodChannel(name, codec);
696 late StreamController<dynamic> controller;
697 controller = StreamController<dynamic>.broadcast(
698 onListen: () async {
699 binaryMessenger.setMessageHandler(name, (ByteData? reply) async {
700 if (reply == null) {
701 controller.close();
702 } else {
703 try {
704 controller.add(codec.decodeEnvelope(reply));
705 } on PlatformException catch (e) {
706 controller.addError(e);
707 }
708 }
709 return null;
710 });
711 try {
712 await methodChannel.invokeMethod<void>('listen', arguments);
713 } catch (exception, stack) {
714 FlutterError.reportError(
715 FlutterErrorDetails(
716 exception: exception,
717 stack: stack,
718 library: 'services library',
719 context: ErrorDescription('while activating platform stream on channel $name'),
720 ),
721 );
722 }
723 },
724 onCancel: () async {
725 binaryMessenger.setMessageHandler(name, null);
726 try {
727 await methodChannel.invokeMethod<void>('cancel', arguments);
728 } catch (exception, stack) {
729 FlutterError.reportError(
730 FlutterErrorDetails(
731 exception: exception,
732 stack: stack,
733 library: 'services library',
734 context: ErrorDescription('while de-activating platform stream on channel $name'),
735 ),
736 );
737 }
738 },
739 );
740 return controller.stream;
741 }
742}
743