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
5/// @docImport 'dart:ui';
6///
7/// @docImport 'package:flutter/widgets.dart';
8///
9/// @docImport 'system_chrome.dart';
10library;
11
12import 'dart:async';
13import 'dart:convert';
14import 'dart:io';
15import 'dart:ui' as ui;
16
17import 'package:flutter/foundation.dart';
18import 'package:flutter/scheduler.dart';
19
20import 'asset_bundle.dart';
21import 'binary_messenger.dart';
22import 'debug.dart';
23import 'hardware_keyboard.dart';
24import 'message_codec.dart';
25import 'platform_channel.dart';
26import 'raw_keyboard.dart' show RawKeyboard;
27import 'restoration.dart';
28import 'service_extensions.dart';
29import 'system_channels.dart';
30import 'system_chrome.dart';
31import 'text_input.dart';
32
33export 'dart:ui' show ChannelBuffers, RootIsolateToken;
34
35export 'binary_messenger.dart' show BinaryMessenger;
36export 'hardware_keyboard.dart' show HardwareKeyboard, KeyEventManager;
37export 'restoration.dart' show RestorationManager;
38
39/// Listens for platform messages and directs them to the [defaultBinaryMessenger].
40///
41/// The [ServicesBinding] also registers a [LicenseEntryCollector] that exposes
42/// the licenses found in the `LICENSE` file stored at the root of the asset
43/// bundle, and implements the `ext.flutter.evict` service extension (see
44/// [evict]).
45mixin ServicesBinding on BindingBase, SchedulerBinding {
46 @override
47 void initInstances() {
48 super.initInstances();
49 _instance = this;
50 _defaultBinaryMessenger = createBinaryMessenger();
51 _restorationManager = createRestorationManager();
52 _initKeyboard();
53 initLicenses();
54 SystemChannels.system.setMessageHandler(
55 (dynamic message) => handleSystemMessage(message as Object),
56 );
57 SystemChannels.accessibility.setMessageHandler(
58 (dynamic message) => _handleAccessibilityMessage(message as Object),
59 );
60 SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
61 SystemChannels.platform.setMethodCallHandler(_handlePlatformMessage);
62 platformDispatcher.onViewFocusChange = handleViewFocusChanged;
63 TextInput.ensureInitialized();
64 readInitialLifecycleStateFromNativeWindow();
65 initializationComplete();
66 }
67
68 /// The current [ServicesBinding], if one has been created.
69 ///
70 /// Provides access to the features exposed by this mixin. The binding must
71 /// be initialized before using this getter; this is typically done by calling
72 /// [runApp] or [WidgetsFlutterBinding.ensureInitialized].
73 static ServicesBinding get instance => BindingBase.checkInstance(_instance);
74 static ServicesBinding? _instance;
75
76 /// The global singleton instance of [HardwareKeyboard], which can be used to
77 /// query keyboard states.
78 HardwareKeyboard get keyboard => _keyboard;
79 late final HardwareKeyboard _keyboard;
80
81 /// The global singleton instance of [KeyEventManager], which is used
82 /// internally to dispatch key messages.
83 ///
84 /// This property is deprecated, and will be removed. See
85 /// [HardwareKeyboard.addHandler] instead.
86 @Deprecated(
87 'No longer supported. Add a handler to HardwareKeyboard instead. '
88 'This feature was deprecated after v3.18.0-2.0.pre.',
89 )
90 KeyEventManager get keyEventManager => _keyEventManager;
91 late final KeyEventManager _keyEventManager;
92
93 void _initKeyboard() {
94 _keyboard = HardwareKeyboard();
95 _keyEventManager = KeyEventManager(_keyboard, RawKeyboard.instance);
96 _keyboard.syncKeyboardState().then((_) {
97 platformDispatcher.onKeyData = _keyEventManager.handleKeyData;
98 SystemChannels.keyEvent.setMessageHandler(_keyEventManager.handleRawKeyMessage);
99 });
100 }
101
102 /// The default instance of [BinaryMessenger].
103 ///
104 /// This is used to send messages from the application to the platform, and
105 /// keeps track of which handlers have been registered on each channel so
106 /// it may dispatch incoming messages to the registered handler.
107 ///
108 /// The default implementation returns a [BinaryMessenger] that delivers the
109 /// messages in the same order in which they are sent.
110 BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger;
111 late final BinaryMessenger _defaultBinaryMessenger;
112
113 /// A token that represents the root isolate, used for coordinating with
114 /// background isolates.
115 ///
116 /// This property is primarily intended for use with
117 /// [BackgroundIsolateBinaryMessenger.ensureInitialized], which takes a
118 /// [RootIsolateToken] as its argument. The value `null` is returned when
119 /// executed from background isolates.
120 static ui.RootIsolateToken? get rootIsolateToken => ui.RootIsolateToken.instance;
121
122 /// The low level buffering and dispatch mechanism for messages sent by
123 /// plugins on the engine side to their corresponding plugin code on
124 /// the framework side.
125 ///
126 /// This exposes the [dart:ui.channelBuffers] object. Bindings can override
127 /// this getter to intercept calls to the [ChannelBuffers] mechanism (for
128 /// example, for tests).
129 ///
130 /// In production, direct access to this object should not be necessary.
131 /// Messages are received and dispatched by the [defaultBinaryMessenger]. This
132 /// object is primarily used to send mock messages in tests, via the
133 /// [ChannelBuffers.push] method (simulating a plugin sending a message to the
134 /// framework).
135 ///
136 /// See also:
137 ///
138 /// * [PlatformDispatcher.sendPlatformMessage], which is used for sending
139 /// messages to plugins from the framework (the opposite of
140 /// [channelBuffers]).
141 /// * [platformDispatcher], the [PlatformDispatcher] singleton.
142 ui.ChannelBuffers get channelBuffers => ui.channelBuffers;
143
144 /// Creates a default [BinaryMessenger] instance that can be used for sending
145 /// platform messages.
146 ///
147 /// Many Flutter framework components that communicate with the platform
148 /// assume messages are received by the platform in the same order in which
149 /// they are sent. When overriding this method, be sure the [BinaryMessenger]
150 /// implementation guarantees FIFO delivery.
151 @protected
152 BinaryMessenger createBinaryMessenger() {
153 return const _DefaultBinaryMessenger._();
154 }
155
156 /// Called when the operating system notifies the application of a memory
157 /// pressure situation.
158 ///
159 /// This method exposes the `memoryPressure` notification from
160 /// [SystemChannels.system].
161 @protected
162 @mustCallSuper
163 void handleMemoryPressure() {
164 rootBundle.clear();
165 }
166
167 /// Handler called for messages received on the [SystemChannels.system]
168 /// message channel.
169 ///
170 /// Other bindings may override this to respond to incoming system messages.
171 @protected
172 @mustCallSuper
173 Future<void> handleSystemMessage(Object systemMessage) async {
174 final Map<String, dynamic> message = systemMessage as Map<String, dynamic>;
175 final String type = message['type'] as String;
176 switch (type) {
177 case 'memoryPressure':
178 handleMemoryPressure();
179 }
180 return;
181 }
182
183 /// Adds relevant licenses to the [LicenseRegistry].
184 ///
185 /// By default, the [ServicesBinding]'s implementation of [initLicenses] adds
186 /// all the licenses collected by the `flutter` tool during compilation.
187 @protected
188 @mustCallSuper
189 void initLicenses() {
190 LicenseRegistry.addLicense(_addLicenses);
191 }
192
193 Stream<LicenseEntry> _addLicenses() {
194 late final StreamController<LicenseEntry> controller;
195 controller = StreamController<LicenseEntry>(
196 onListen: () async {
197 late final String rawLicenses;
198 if (kIsWeb) {
199 // NOTICES for web isn't compressed since we don't have access to
200 // dart:io on the client side and it's already compressed between
201 // the server and client.
202 rawLicenses = await rootBundle.loadString('NOTICES', cache: false);
203 } else {
204 // The compressed version doesn't have a more common .gz extension
205 // because gradle for Android non-transparently manipulates .gz files.
206 final ByteData licenseBytes = await rootBundle.load('NOTICES.Z');
207 final List<int> unzippedBytes = await compute<List<int>, List<int>>(
208 gzip.decode,
209 licenseBytes.buffer.asUint8List(),
210 debugLabel: 'decompressLicenses',
211 );
212 rawLicenses = await compute<List<int>, String>(
213 utf8.decode,
214 unzippedBytes,
215 debugLabel: 'utf8DecodeLicenses',
216 );
217 }
218 final List<LicenseEntry> licenses = await compute<String, List<LicenseEntry>>(
219 _parseLicenses,
220 rawLicenses,
221 debugLabel: 'parseLicenses',
222 );
223 licenses.forEach(controller.add);
224 await controller.close();
225 },
226 );
227 return controller.stream;
228 }
229
230 // This is run in another isolate created by _addLicenses above.
231 static List<LicenseEntry> _parseLicenses(String rawLicenses) {
232 final String licenseSeparator = '\n${'-' * 80}\n';
233 return <LicenseEntry>[
234 for (final String license in rawLicenses.split(licenseSeparator))
235 if (license.indexOf('\n\n') case final int split when split >= 0)
236 LicenseEntryWithLineBreaks(
237 license.substring(0, split).split('\n'),
238 license.substring(split + 2),
239 )
240 else
241 LicenseEntryWithLineBreaks(const <String>[], license),
242 ];
243 }
244
245 @override
246 void initServiceExtensions() {
247 super.initServiceExtensions();
248
249 assert(() {
250 registerStringServiceExtension(
251 name: ServicesServiceExtensions.evict.name,
252 getter: () async => '',
253 setter: (String value) async {
254 evict(value);
255 },
256 );
257 return true;
258 }());
259
260 if (!kReleaseMode) {
261 registerBoolServiceExtension(
262 name: ServicesServiceExtensions.profilePlatformChannels.name,
263 getter: () async => debugProfilePlatformChannels,
264 setter: (bool value) async {
265 debugProfilePlatformChannels = value;
266 },
267 );
268 }
269 }
270
271 /// Called in response to the `ext.flutter.evict` service extension.
272 ///
273 /// This is used by the `flutter` tool during hot reload so that any images
274 /// that have changed on disk get cleared from caches.
275 @protected
276 @mustCallSuper
277 void evict(String asset) {
278 rootBundle.evict(asset);
279 }
280
281 // App life cycle
282
283 /// Initializes the [lifecycleState] with the
284 /// [dart:ui.PlatformDispatcher.initialLifecycleState].
285 ///
286 /// Once the [lifecycleState] is populated through any means (including this
287 /// method), this method will do nothing. This is because the
288 /// [dart:ui.PlatformDispatcher.initialLifecycleState] may already be stale
289 /// and it no longer makes sense to use the initial state at dart vm startup
290 /// as the current state anymore.
291 ///
292 /// The latest state should be obtained by subscribing to
293 /// [WidgetsBindingObserver.didChangeAppLifecycleState].
294 @protected
295 void readInitialLifecycleStateFromNativeWindow() {
296 if (lifecycleState != null || platformDispatcher.initialLifecycleState.isEmpty) {
297 return;
298 }
299 _handleLifecycleMessage(platformDispatcher.initialLifecycleState);
300 }
301
302 Future<String?> _handleLifecycleMessage(String? message) async {
303 final AppLifecycleState? state = _parseAppLifecycleMessage(message!);
304 final List<AppLifecycleState> generated = _generateStateTransitions(lifecycleState, state!);
305 for (final AppLifecycleState stateChange in generated) {
306 handleAppLifecycleStateChanged(stateChange);
307 SystemChrome.handleAppLifecycleStateChanged(stateChange);
308 }
309 return null;
310 }
311
312 List<AppLifecycleState> _generateStateTransitions(
313 AppLifecycleState? previousState,
314 AppLifecycleState state,
315 ) {
316 if (previousState == state) {
317 return const <AppLifecycleState>[];
318 }
319 final List<AppLifecycleState> stateChanges = <AppLifecycleState>[];
320 if (previousState == null) {
321 // If there was no previous state, just jump directly to the new state.
322 stateChanges.add(state);
323 } else {
324 final int previousStateIndex = AppLifecycleState.values.indexOf(previousState);
325 final int stateIndex = AppLifecycleState.values.indexOf(state);
326 assert(previousStateIndex != -1, 'State $previousState missing in stateOrder array');
327 assert(stateIndex != -1, 'State $state missing in stateOrder array');
328 if (state == AppLifecycleState.detached) {
329 for (int i = previousStateIndex + 1; i < AppLifecycleState.values.length; ++i) {
330 stateChanges.add(AppLifecycleState.values[i]);
331 }
332 stateChanges.add(AppLifecycleState.detached);
333 } else if (previousStateIndex > stateIndex) {
334 for (int i = stateIndex; i < previousStateIndex; ++i) {
335 stateChanges.insert(0, AppLifecycleState.values[i]);
336 }
337 } else {
338 for (int i = previousStateIndex + 1; i <= stateIndex; ++i) {
339 stateChanges.add(AppLifecycleState.values[i]);
340 }
341 }
342 }
343 assert(
344 () {
345 AppLifecycleState? starting = previousState;
346 for (final AppLifecycleState ending in stateChanges) {
347 if (!_debugVerifyLifecycleChange(starting, ending)) {
348 return false;
349 }
350 starting = ending;
351 }
352 return true;
353 }(),
354 'Invalid lifecycle state transition generated from $previousState to $state (generated $stateChanges)',
355 );
356 return stateChanges;
357 }
358
359 static bool _debugVerifyLifecycleChange(AppLifecycleState? starting, AppLifecycleState ending) {
360 if (starting == null) {
361 // Any transition from null is fine, since it is initializing the state.
362 return true;
363 }
364 if (starting == ending) {
365 // Any transition to itself shouldn't happen.
366 return false;
367 }
368 return switch (starting) {
369 // Can't go from resumed to detached directly (must go through paused).
370 AppLifecycleState.resumed => ending == AppLifecycleState.inactive,
371 AppLifecycleState.detached =>
372 ending == AppLifecycleState.resumed || ending == AppLifecycleState.paused,
373 AppLifecycleState.inactive =>
374 ending == AppLifecycleState.resumed || ending == AppLifecycleState.hidden,
375 AppLifecycleState.hidden =>
376 ending == AppLifecycleState.paused || ending == AppLifecycleState.inactive,
377 AppLifecycleState.paused =>
378 ending == AppLifecycleState.hidden || ending == AppLifecycleState.detached,
379 };
380 }
381
382 /// Listenable that notifies when the accessibility focus on the system have changed.
383 final ValueNotifier<int?> accessibilityFocus = ValueNotifier<int?>(null);
384
385 Future<void> _handleAccessibilityMessage(Object accessibilityMessage) async {
386 final Map<String, dynamic> message =
387 (accessibilityMessage as Map<Object?, Object?>).cast<String, dynamic>();
388 final String type = message['type'] as String;
389 switch (type) {
390 case 'didGainFocus':
391 accessibilityFocus.value = message['nodeId'] as int;
392 }
393 return;
394 }
395
396 /// Called whenever the [PlatformDispatcher] receives a notification that the
397 /// focus state on a view has changed.
398 ///
399 /// The [event] contains the view ID for the view that changed its focus
400 /// state.
401 ///
402 /// See also:
403 ///
404 /// * [PlatformDispatcher.onViewFocusChange], which calls this method.
405 @protected
406 @mustCallSuper
407 void handleViewFocusChanged(ui.ViewFocusEvent event) {}
408
409 Future<dynamic> _handlePlatformMessage(MethodCall methodCall) async {
410 final String method = methodCall.method;
411 switch (method) {
412 // Called when the system dismisses the system context menu, such as when
413 // the user taps outside the menu. Not called when Flutter shows a new
414 // system context menu while an old one is still visible.
415 case 'ContextMenu.onDismissSystemContextMenu':
416 if (_systemContextMenuClient == null) {
417 assert(
418 false,
419 'Platform sent onDismissSystemContextMenu when no SystemContextMenuClient was registered.',
420 );
421 return;
422 }
423 _systemContextMenuClient!.handleSystemHide();
424 _systemContextMenuClient = null;
425 case 'SystemChrome.systemUIChange':
426 final List<dynamic> args = methodCall.arguments as List<dynamic>;
427 if (_systemUiChangeCallback != null) {
428 await _systemUiChangeCallback!(args[0] as bool);
429 }
430 case 'System.requestAppExit':
431 return <String, dynamic>{'response': (await handleRequestAppExit()).name};
432 default:
433 throw AssertionError('Method "$method" not handled.');
434 }
435 }
436
437 static AppLifecycleState? _parseAppLifecycleMessage(String message) {
438 return switch (message) {
439 'AppLifecycleState.resumed' => AppLifecycleState.resumed,
440 'AppLifecycleState.inactive' => AppLifecycleState.inactive,
441 'AppLifecycleState.hidden' => AppLifecycleState.hidden,
442 'AppLifecycleState.paused' => AppLifecycleState.paused,
443 'AppLifecycleState.detached' => AppLifecycleState.detached,
444 _ => null,
445 };
446 }
447
448 /// Handles any requests for application exit that may be received on the
449 /// [SystemChannels.platform] method channel.
450 ///
451 /// By default, returns [ui.AppExitResponse.exit].
452 ///
453 /// {@template flutter.services.binding.ServicesBinding.requestAppExit}
454 /// Not all exits are cancelable, so not all exits will call this function. Do
455 /// not rely on this function as a place to save critical data, because you
456 /// will be disappointed. There are a number of ways that the application can
457 /// exit without letting the application know first: power can be unplugged,
458 /// the battery removed, the application can be killed in a task manager or
459 /// command line, or the device could have a rapid unplanned disassembly (i.e.
460 /// it could explode). In all of those cases (and probably others), no
461 /// notification will be given to the application that it is about to exit.
462 /// {@endtemplate}
463 ///
464 /// {@tool sample}
465 /// This examples shows how an application can cancel (or not) OS requests for
466 /// quitting an application. Currently this is only supported on macOS and
467 /// Linux.
468 ///
469 /// ** See code in examples/api/lib/services/binding/handle_request_app_exit.0.dart **
470 /// {@end-tool}
471 ///
472 /// See also:
473 ///
474 /// * [WidgetsBindingObserver.didRequestAppExit], which can be overridden to
475 /// respond to this message.
476 /// * [WidgetsBinding.handleRequestAppExit] which overrides this method to
477 /// notify its observers.
478 Future<ui.AppExitResponse> handleRequestAppExit() async {
479 return ui.AppExitResponse.exit;
480 }
481
482 /// Exits the application by calling the native application API method for
483 /// exiting an application cleanly.
484 ///
485 /// This differs from calling `dart:io`'s [exit] function in that it gives the
486 /// engine a chance to clean up resources so that it doesn't crash on exit, so
487 /// calling this is always preferred over calling [exit]. It also optionally
488 /// gives handlers of [handleRequestAppExit] a chance to cancel the
489 /// application exit.
490 ///
491 /// The [exitType] indicates what kind of exit to perform. For
492 /// [ui.AppExitType.cancelable] exits, the application is queried through a
493 /// call to [handleRequestAppExit], where the application can optionally
494 /// cancel the request for exit. If the [exitType] is
495 /// [ui.AppExitType.required], then the application exits immediately without
496 /// querying the application.
497 ///
498 /// For [ui.AppExitType.cancelable] exits, the returned response value is the
499 /// response obtained from the application as to whether the exit was canceled
500 /// or not. Practically, the response will never be [ui.AppExitResponse.exit],
501 /// since the application will have already exited by the time the result
502 /// would have been received.
503 ///
504 /// The optional [exitCode] argument will be used as the application exit code
505 /// on platforms where an exit code is supported. On other platforms it may be
506 /// ignored. It defaults to zero.
507 ///
508 /// See also:
509 ///
510 /// * [WidgetsBindingObserver.didRequestAppExit] for a handler you can
511 /// override on a [WidgetsBindingObserver] to receive exit requests.
512 Future<ui.AppExitResponse> exitApplication(ui.AppExitType exitType, [int exitCode = 0]) async {
513 final Map<String, Object?>? result = await SystemChannels.platform
514 .invokeMethod<Map<String, Object?>>('System.exitApplication', <String, Object?>{
515 'type': exitType.name,
516 'exitCode': exitCode,
517 });
518 if (result == null) {
519 return ui.AppExitResponse.cancel;
520 }
521 switch (result['response']) {
522 case 'cancel':
523 return ui.AppExitResponse.cancel;
524 case 'exit':
525 default:
526 // In practice, this will never get returned, because the application
527 // will have exited before it returns.
528 return ui.AppExitResponse.exit;
529 }
530 }
531
532 /// The [RestorationManager] synchronizes the restoration data between
533 /// engine and framework.
534 ///
535 /// See the docs for [RestorationManager] for a discussion of restoration
536 /// state and how it is organized in Flutter.
537 ///
538 /// To use a different [RestorationManager] subclasses can override
539 /// [createRestorationManager], which is called to create the instance
540 /// returned by this getter.
541 RestorationManager get restorationManager => _restorationManager;
542 late RestorationManager _restorationManager;
543
544 /// Creates the [RestorationManager] instance available via
545 /// [restorationManager].
546 ///
547 /// Can be overridden in subclasses to create a different [RestorationManager].
548 @protected
549 RestorationManager createRestorationManager() {
550 return RestorationManager();
551 }
552
553 SystemUiChangeCallback? _systemUiChangeCallback;
554
555 /// Sets the callback for the `SystemChrome.systemUIChange` method call
556 /// received on the [SystemChannels.platform] channel.
557 ///
558 /// This is typically not called directly. System UI changes that this method
559 /// responds to are associated with [SystemUiMode]s, which are configured
560 /// using [SystemChrome]. Use [SystemChrome.setSystemUIChangeCallback] to configure
561 /// along with other SystemChrome settings.
562 ///
563 /// See also:
564 ///
565 /// * [SystemChrome.setEnabledSystemUIMode], which specifies the
566 /// [SystemUiMode] to have visible when the application is running.
567 // ignore: use_setters_to_change_properties, (API predates enforcing the lint)
568 void setSystemUiChangeCallback(SystemUiChangeCallback? callback) {
569 _systemUiChangeCallback = callback;
570 }
571
572 /// Alert the engine that the binding is registered. This instructs the engine to
573 /// register its top level window handler on Windows. This signals that the app
574 /// is able to process "System.requestAppExit" signals from the engine.
575 @protected
576 Future<void> initializationComplete() async {
577 await SystemChannels.platform.invokeMethod('System.initializationComplete');
578 }
579
580 SystemContextMenuClient? _systemContextMenuClient;
581
582 /// Registers a [SystemContextMenuClient] that will receive system context
583 /// menu calls from the engine.
584 ///
585 /// To unregister, set to null.
586 static set systemContextMenuClient(SystemContextMenuClient? client) {
587 instance._systemContextMenuClient = client;
588 }
589}
590
591/// Signature for listening to changes in the [SystemUiMode].
592///
593/// Set by [SystemChrome.setSystemUIChangeCallback].
594typedef SystemUiChangeCallback = Future<void> Function(bool systemOverlaysAreVisible);
595
596/// The default implementation of [BinaryMessenger].
597///
598/// This messenger sends messages from the app-side to the platform-side and
599/// dispatches incoming messages from the platform-side to the appropriate
600/// handler.
601class _DefaultBinaryMessenger extends BinaryMessenger {
602 const _DefaultBinaryMessenger._();
603
604 @override
605 Future<void> handlePlatformMessage(
606 String channel,
607 ByteData? message,
608 ui.PlatformMessageResponseCallback? callback,
609 ) async {
610 ui.channelBuffers.push(channel, message, (ByteData? data) => callback?.call(data));
611 }
612
613 @override
614 Future<ByteData?> send(String channel, ByteData? message) {
615 final Completer<ByteData?> completer = Completer<ByteData?>();
616 // ui.PlatformDispatcher.instance is accessed directly instead of using
617 // ServicesBinding.instance.platformDispatcher because this method might be
618 // invoked before any binding is initialized. This issue was reported in
619 // #27541. It is not ideal to statically access
620 // ui.PlatformDispatcher.instance because the PlatformDispatcher may be
621 // dependency injected elsewhere with a different instance. However, static
622 // access at this location seems to be the least bad option.
623 // TODO(ianh): Use ServicesBinding.instance once we have better diagnostics
624 // on that getter.
625 ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message, (ByteData? reply) {
626 try {
627 completer.complete(reply);
628 } catch (exception, stack) {
629 FlutterError.reportError(
630 FlutterErrorDetails(
631 exception: exception,
632 stack: stack,
633 library: 'services library',
634 context: ErrorDescription('during a platform message response callback'),
635 ),
636 );
637 }
638 });
639 return completer.future;
640 }
641
642 @override
643 void setMessageHandler(String channel, MessageHandler? handler) {
644 if (handler == null) {
645 ui.channelBuffers.clearListener(channel);
646 } else {
647 ui.channelBuffers.setListener(channel, (
648 ByteData? data,
649 ui.PlatformMessageResponseCallback callback,
650 ) async {
651 ByteData? response;
652 try {
653 response = await handler(data);
654 } catch (exception, stack) {
655 FlutterError.reportError(
656 FlutterErrorDetails(
657 exception: exception,
658 stack: stack,
659 library: 'services library',
660 context: ErrorDescription('during a platform message callback'),
661 ),
662 );
663 } finally {
664 callback(response);
665 }
666 });
667 }
668 }
669}
670
671/// An interface to receive calls related to the system context menu from the
672/// engine.
673///
674/// Currently this is only supported on iOS 16+.
675///
676/// See also:
677/// * [SystemContextMenuController], which uses this to provide a fully
678/// featured way to control the system context menu.
679/// * [ServicesBinding.systemContextMenuClient], which can be set to a
680/// [SystemContextMenuClient] to register it to receive events, or null to
681/// unregister.
682/// * [MediaQuery.maybeSupportsShowingSystemContextMenu], which indicates
683/// whether the system context menu is supported.
684/// * [SystemContextMenu], which provides a widget interface for displaying the
685/// system context menu.
686mixin SystemContextMenuClient {
687 /// Handles the system hiding a context menu.
688 ///
689 /// Called only on the single active instance registered with
690 /// [ServicesBinding.systemContextMenuClient].
691 void handleSystemHide();
692}
693

Provided by KDAB

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