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

Provided by KDAB

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