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

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com