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:convert';
6/// @docImport 'dart:ui';
7///
8/// @docImport 'package:flutter/rendering.dart';
9/// @docImport 'package:flutter/scheduler.dart';
10/// @docImport 'package:flutter/services.dart';
11/// @docImport 'package:flutter/widgets.dart';
12/// @docImport 'package:flutter_test/flutter_test.dart';
13library;
14
15import 'dart:async';
16import 'dart:convert' show json;
17import 'dart:developer' as developer;
18import 'dart:io' show exit;
19import 'dart:ui' as ui show Brightness, PlatformDispatcher, SingletonFlutterWindow, window;
20
21// Before adding any more dart:ui imports, please read the README.
22
23import 'package:meta/meta.dart';
24
25import 'assertions.dart';
26import 'basic_types.dart';
27import 'constants.dart';
28import 'debug.dart';
29import 'object.dart';
30import 'platform.dart';
31import 'print.dart';
32import 'service_extensions.dart';
33import 'timeline.dart';
34
35export 'dart:ui' show PlatformDispatcher, SingletonFlutterWindow, clampDouble;
36
37export 'basic_types.dart' show AsyncCallback, AsyncValueGetter, AsyncValueSetter;
38
39// Examples can assume:
40// mixin BarBinding on BindingBase { }
41
42/// Signature for service extensions.
43///
44/// The returned map must not contain the keys "type" or "method", as
45/// they will be replaced before the value is sent to the client. The
46/// "type" key will be set to the string `_extensionType` to indicate
47/// that this is a return value from a service extension, and the
48/// "method" key will be set to the full name of the method.
49typedef ServiceExtensionCallback =
50 Future<Map<String, dynamic>> Function(Map<String, String> parameters);
51
52/// Base class for mixins that provide singleton services.
53///
54/// The Flutter engine ([dart:ui]) exposes some low-level services,
55/// but these are typically not suitable for direct use, for example
56/// because they only provide a single callback which an application
57/// may wish to multiplex to allow multiple listeners.
58///
59/// Bindings provide the glue between these low-level APIs and the
60/// higher-level framework APIs. They _bind_ the two together, whence
61/// the name.
62///
63/// ## Implementing a binding mixin
64///
65/// A library would typically create a new binding mixin to expose a
66/// feature in [dart:ui]. This is rare in general, but it is something
67/// that an alternative framework would do, e.g. if a framework were
68/// to replace the [widgets] library with an alternative API but still
69/// wished to leverage the [services] and [foundation] libraries.
70///
71/// To create a binding mixin, declare a mixin `on` the [BindingBase] class
72/// and whatever other bindings the concrete binding must implement for
73/// this binding mixin to be useful.
74///
75/// The mixin is guaranteed to only be constructed once in the
76/// lifetime of the app; this is handled by [initInstances].
77///
78/// A binding mixin must at a minimum implement the following features:
79///
80/// * The [initInstances] method, which must call `super.initInstances` and
81/// set an `_instance` static field to `this`.
82/// * An `instance` static getter, which must return that field using [checkInstance].
83///
84/// In addition, it should implement whatever singleton features the library needs.
85///
86/// As a general rule, the less can be placed in the binding, the
87/// better. Prefer having APIs that takes objects rather than having
88/// them refer to global singletons. Bindings are best limited to
89/// exposing features that literally only exist once, for example, the
90/// APIs in [dart:ui].
91///
92/// {@tool snippet}
93///
94/// Here is a basic example of a binding that implements these features. It relies on
95/// another fictional binding called `BarBinding`.
96///
97/// ```dart
98/// mixin FooBinding on BindingBase, BarBinding {
99/// @override
100/// void initInstances() {
101/// super.initInstances();
102/// _instance = this;
103/// // ...binding initialization...
104/// }
105///
106/// static FooBinding get instance => BindingBase.checkInstance(_instance);
107/// static FooBinding? _instance;
108///
109/// // ...binding features...
110/// }
111/// ```
112/// {@end-tool}
113///
114/// ## Implementing a binding class
115///
116/// The top-most layer used to write the application (e.g. the Flutter
117/// [widgets] library) will have a concrete class that inherits from
118/// [BindingBase] and uses all the various [BindingBase] mixins (such
119/// as [ServicesBinding]). The [widgets] library in Flutter introduces
120/// a binding called [WidgetsFlutterBinding].
121///
122/// A binding _class_ should mix in the relevant bindings from each
123/// layer that it wishes to expose, and should have an
124/// `ensureInitialized` method that constructs the class if that
125/// layer's mixin's `_instance` field is null. This allows the binding
126/// to be overridden by developers who have more specific needs, while
127/// still allowing other code to call `ensureInitialized` when a binding
128/// is needed.
129///
130/// {@tool snippet}
131///
132/// A typical binding class is shown below. The `ensureInitialized` method's
133/// return type is the library's binding mixin, rather than the concrete
134/// class.
135///
136/// ```dart
137/// // continuing from previous example...
138/// class FooLibraryBinding extends BindingBase with BarBinding, FooBinding {
139/// static FooBinding ensureInitialized() {
140/// if (FooBinding._instance == null) {
141/// FooLibraryBinding();
142/// }
143/// return FooBinding.instance;
144/// }
145/// }
146/// ```
147/// {@end-tool}
148abstract class BindingBase {
149 /// Default abstract constructor for bindings.
150 ///
151 /// First calls [initInstances] to have bindings initialize their
152 /// instance pointers and other state, then calls
153 /// [initServiceExtensions] to have bindings initialize their
154 /// VM service extensions, if any.
155 BindingBase() {
156 if (!kReleaseMode) {
157 FlutterTimeline.startSync('Framework initialization');
158 }
159 assert(() {
160 _debugConstructed = true;
161 return true;
162 }());
163
164 assert(
165 _debugInitializedType == null,
166 'Binding is already initialized to $_debugInitializedType',
167 );
168 initInstances();
169 assert(_debugInitializedType != null);
170
171 assert(!_debugServiceExtensionsRegistered);
172 initServiceExtensions();
173 assert(_debugServiceExtensionsRegistered);
174
175 if (!kReleaseMode) {
176 developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
177 FlutterTimeline.finishSync();
178 }
179 }
180
181 bool _debugConstructed = false;
182 static Type? _debugInitializedType;
183 static bool _debugServiceExtensionsRegistered = false;
184
185 /// Deprecated. Will be removed in a future version of Flutter.
186 ///
187 /// This property has been deprecated to prepare for Flutter's upcoming
188 /// support for multiple views and multiple windows.
189 ///
190 /// It represents the main view for applications where there is only one
191 /// view, such as applications designed for single-display mobile devices.
192 /// If the embedder supports multiple views, it points to the first view
193 /// created which is assumed to be the main view. It throws if no view has
194 /// been created yet or if the first view has been removed again.
195 ///
196 /// The following options exists to migrate code that relies on accessing
197 /// this deprecated property:
198 ///
199 /// If a [BuildContext] is available, consider looking up the current
200 /// [FlutterView] associated with that context via [View.of]. It gives access
201 /// to the same functionality as this deprecated property. However, the
202 /// platform-specific functionality has moved to the [PlatformDispatcher],
203 /// which may be accessed from the view returned by [View.of] via
204 /// [FlutterView.platformDispatcher]. Using [View.of] with a [BuildContext] is
205 /// the preferred option to migrate away from this deprecated [window]
206 /// property.
207 ///
208 /// If no context is available to look up a [FlutterView], the
209 /// [platformDispatcher] exposed by this binding can be used directly for
210 /// platform-specific functionality. It also maintains a list of all available
211 /// [FlutterView]s in [PlatformDispatcher.views] to access view-specific
212 /// functionality without a context.
213 ///
214 /// See also:
215 ///
216 /// * [View.of] to access view-specific functionality on the [FlutterView]
217 /// associated with the provided [BuildContext].
218 /// * [FlutterView.platformDispatcher] to access platform-specific
219 /// functionality from a given [FlutterView].
220 /// * [platformDispatcher] on this binding to access the [PlatformDispatcher],
221 /// which provides platform-specific functionality.
222 @Deprecated(
223 'Look up the current FlutterView from the context via View.of(context) or consult the PlatformDispatcher directly instead. '
224 'Deprecated to prepare for the upcoming multi-window support. '
225 'This feature was deprecated after v3.7.0-32.0.pre.',
226 )
227 ui.SingletonFlutterWindow get window => ui.window;
228
229 /// The [ui.PlatformDispatcher] to which this binding is bound.
230 ///
231 /// A number of additional bindings are defined as extensions of
232 /// [BindingBase], e.g., [ServicesBinding], [RendererBinding], and
233 /// [WidgetsBinding]. Each of these bindings define behaviors that interact
234 /// with a [ui.PlatformDispatcher], e.g., [ServicesBinding] registers
235 /// listeners with the [ChannelBuffers], [RendererBinding]
236 /// registers [ui.PlatformDispatcher.onMetricsChanged],
237 /// [ui.PlatformDispatcher.onTextScaleFactorChanged], and [SemanticsBinding]
238 /// registers [ui.PlatformDispatcher.onSemanticsEnabledChanged],
239 /// [ui.PlatformDispatcher.onSemanticsActionEvent], and
240 /// [ui.PlatformDispatcher.onAccessibilityFeaturesChanged] handlers.
241 ///
242 /// Each of these other bindings could individually access a
243 /// [ui.PlatformDispatcher] statically, but that would preclude the ability to
244 /// test these behaviors with a fake platform dispatcher for verification
245 /// purposes. Therefore, [BindingBase] exposes this [ui.PlatformDispatcher]
246 /// for use by other bindings. A subclass of [BindingBase], such as
247 /// [TestWidgetsFlutterBinding], can override this accessor to return a
248 /// different [ui.PlatformDispatcher] implementation.
249 ui.PlatformDispatcher get platformDispatcher => ui.PlatformDispatcher.instance;
250
251 /// The initialization method. Subclasses override this method to hook into
252 /// the platform and otherwise configure their services. Subclasses must call
253 /// "super.initInstances()".
254 ///
255 /// The binding is not fully initialized when this method runs (for
256 /// example, other binding mixins may not yet have run their
257 /// [initInstances] method). For this reason, code in this method
258 /// should avoid invoking callbacks or synchronously triggering any
259 /// code that would normally assume that the bindings are ready.
260 ///
261 /// {@tool snippet}
262 ///
263 /// By convention, if the service is to be provided as a singleton,
264 /// it should be exposed as `MixinClassName.instance`, a static
265 /// getter with a non-nullable return type that returns
266 /// `MixinClassName._instance`, a static field that is set by
267 /// `initInstances()`. To improve the developer experience, the
268 /// return value should actually be
269 /// `BindingBase.checkInstance(_instance)` (see [checkInstance]), as
270 /// in the example below.
271 ///
272 /// ```dart
273 /// mixin BazBinding on BindingBase {
274 /// @override
275 /// void initInstances() {
276 /// super.initInstances();
277 /// _instance = this;
278 /// // ...binding initialization...
279 /// }
280 ///
281 /// static BazBinding get instance => BindingBase.checkInstance(_instance);
282 /// static BazBinding? _instance;
283 ///
284 /// // ...binding features...
285 /// }
286 /// ```
287 /// {@end-tool}
288 @protected
289 @mustCallSuper
290 void initInstances() {
291 assert(_debugInitializedType == null);
292 assert(() {
293 _debugInitializedType = runtimeType;
294 _debugBindingZone = Zone.current;
295 return true;
296 }());
297 }
298
299 /// A method that shows a useful error message if the given binding
300 /// instance is not initialized.
301 ///
302 /// See [initInstances] for advice on using this method.
303 ///
304 /// This method either returns the argument or throws an exception.
305 /// In release mode it always returns the argument.
306 ///
307 /// The type argument `T` should be the kind of binding mixin (e.g.
308 /// `SchedulerBinding`) that is calling the method. It is used in
309 /// error messages.
310 @protected
311 static T checkInstance<T extends BindingBase>(T? instance) {
312 assert(() {
313 if (_debugInitializedType == null && instance == null) {
314 throw FlutterError.fromParts(<DiagnosticsNode>[
315 ErrorSummary('Binding has not yet been initialized.'),
316 ErrorDescription(
317 'The "instance" getter on the $T binding mixin is only available once that binding has been initialized.',
318 ),
319 ErrorHint(
320 'Typically, this is done by calling "WidgetsFlutterBinding.ensureInitialized()" or "runApp()" (the '
321 'latter calls the former). Typically this call is done in the "void main()" method. The "ensureInitialized" method '
322 'is idempotent; calling it multiple times is not harmful. After calling that method, the "instance" getter will '
323 'return the binding.',
324 ),
325 ErrorHint(
326 'In a test, one can call "TestWidgetsFlutterBinding.ensureInitialized()" as the first line in the test\'s "main()" method '
327 'to initialize the binding.',
328 ),
329 ErrorHint(
330 'If $T is a custom binding mixin, there must also be a custom binding class, like WidgetsFlutterBinding, '
331 'but that mixes in the selected binding, and that is the class that must be constructed before using the "instance" getter.',
332 ),
333 ]);
334 }
335 if (instance == null) {
336 assert(_debugInitializedType == null);
337 throw FlutterError.fromParts(<DiagnosticsNode>[
338 ErrorSummary('Binding mixin instance is null but bindings are already initialized.'),
339 ErrorDescription(
340 'The "instance" property of the $T binding mixin was accessed, but that binding was not initialized when '
341 'the "initInstances()" method was called.',
342 ),
343 ErrorHint(
344 'This probably indicates that the $T mixin was not mixed into the class that was used to initialize the binding. '
345 'If this is a custom binding mixin, there must also be a custom binding class, like WidgetsFlutterBinding, '
346 'but that mixes in the selected binding. If this is a test binding, check that the binding being initialized '
347 'is the same as the one into which the test binding is mixed.',
348 ),
349 ErrorHint(
350 'It is also possible that $T does not implement "initInstances()" to assign a value to "instance". See the '
351 'documentation of the BindingBase class for more details.',
352 ),
353 ErrorHint('The binding that was initialized was of the type "$_debugInitializedType". '),
354 ]);
355 }
356 try {
357 if (instance._debugConstructed && _debugInitializedType == null) {
358 throw FlutterError.fromParts(<DiagnosticsNode>[
359 ErrorSummary('Binding initialized without calling initInstances.'),
360 ErrorDescription(
361 'An instance of $T is non-null, but BindingBase.initInstances() has not yet been called.',
362 ),
363 ErrorHint(
364 'This could happen because a binding mixin was somehow used outside of the normal binding mechanisms, or because '
365 'the binding\'s initInstances() method did not call "super.initInstances()".',
366 ),
367 ErrorHint(
368 'This could also happen if some code was invoked that used the binding while the binding was initializing, '
369 'for example if the "initInstances" method invokes a callback. Bindings should not invoke callbacks before '
370 '"initInstances" has completed.',
371 ),
372 ]);
373 }
374 if (!instance._debugConstructed) {
375 // The state of _debugInitializedType doesn't matter in this failure mode.
376 throw FlutterError.fromParts(<DiagnosticsNode>[
377 ErrorSummary('Binding did not complete initialization.'),
378 ErrorDescription(
379 'An instance of $T is non-null, but the BindingBase() constructor has not yet been called.',
380 ),
381 ErrorHint(
382 'This could also happen if some code was invoked that used the binding while the binding was initializing, '
383 "for example if the binding's constructor itself invokes a callback. Bindings should not invoke callbacks "
384 'before "initInstances" has completed.',
385 ),
386 ]);
387 }
388 } on NoSuchMethodError {
389 throw FlutterError.fromParts(<DiagnosticsNode>[
390 ErrorSummary('Binding does not extend BindingBase'),
391 ErrorDescription(
392 'An instance of $T was created but the BindingBase constructor was not called.',
393 ),
394 ErrorHint(
395 'This could happen because the binding was implemented using "implements" rather than "extends" or "with". '
396 'Concrete binding classes must extend or mix in BindingBase.',
397 ),
398 ]);
399 }
400 return true;
401 }());
402 return instance!;
403 }
404
405 /// In debug builds, the type of the current binding, if any, or else null.
406 ///
407 /// This may be useful in asserts to verify that the binding has not been initialized
408 /// before the point in the application code that wants to initialize the binding, or
409 /// to verify that the binding is the one that is expected.
410 ///
411 /// For example, if an application uses [Zone]s to report uncaught exceptions, it may
412 /// need to ensure that `ensureInitialized()` has not yet been invoked on any binding
413 /// at the point where it configures the zone and initializes the binding.
414 ///
415 /// If this returns null, the binding has not been initialized.
416 ///
417 /// If this returns a non-null value, it returns the type of the binding instance.
418 ///
419 /// To obtain the binding itself, consider the `instance` getter on the [BindingBase]
420 /// subclass or mixin.
421 ///
422 /// This method only returns a useful value in debug builds. In release builds, the
423 /// return value is always null; to improve startup performance, the type of the
424 /// binding is not tracked in release builds.
425 ///
426 /// See also:
427 ///
428 /// * [BindingBase], whose class documentation describes the conventions for dealing
429 /// with bindings.
430 /// * [initInstances], whose documentation details how to create a binding mixin.
431 static Type? debugBindingType() {
432 return _debugInitializedType;
433 }
434
435 Zone? _debugBindingZone;
436
437 /// Whether [debugCheckZone] should throw (true) or just report the error (false).
438 ///
439 /// Setting this to true makes it easier to catch cases where the zones are
440 /// misconfigured, by allowing debuggers to stop at the point of error.
441 ///
442 /// Currently this defaults to false, to avoid suddenly breaking applications
443 /// that are affected by this check but appear to be working today. Applications
444 /// are encouraged to resolve any issues that cause the [debugCheckZone] message
445 /// to appear, as even if they appear to be working today, they are likely to be
446 /// hiding hard-to-find bugs, and are more brittle (likely to collect bugs in
447 /// the future).
448 ///
449 /// To silence the message displayed by [debugCheckZone], ensure that the same
450 /// zone is used when calling `ensureInitialized()` as when calling the framework
451 /// in any other context (e.g. via [runApp]).
452 static bool debugZoneErrorsAreFatal = false;
453
454 /// Checks that the current [Zone] is the same as that which was used
455 /// to initialize the binding.
456 ///
457 /// If the current zone ([Zone.current]) is not the zone that was active when
458 /// the binding was initialized, then this method generates a [FlutterError]
459 /// exception with detailed information. The exception is either thrown
460 /// directly, or reported via [FlutterError.reportError], depending on the
461 /// value of [BindingBase.debugZoneErrorsAreFatal].
462 ///
463 /// To silence the message displayed by [debugCheckZone], ensure that the same
464 /// zone is used when calling `ensureInitialized()` as when calling the
465 /// framework in any other context (e.g. via [runApp]). For example, consider
466 /// keeping a reference to the zone used to initialize the binding, and using
467 /// [Zone.run] to use it again when calling into the framework.
468 ///
469 /// ## Usage
470 ///
471 /// The binding is considered initialized once [BindingBase.initInstances] has
472 /// run; if this is called before then, it will throw an [AssertionError].
473 ///
474 /// The `entryPoint` parameter is the name of the API that is checking the
475 /// zones are consistent, for example, `'runApp'`.
476 ///
477 /// This function always returns true (if it does not throw). It is expected
478 /// to be invoked via the binding instance, e.g.:
479 ///
480 /// ```dart
481 /// void startup() {
482 /// WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
483 /// assert(binding.debugCheckZone('startup'));
484 /// // ...
485 /// }
486 /// ```
487 ///
488 /// If the binding expects to be used with multiple zones, it should override
489 /// this method to return true always without throwing. (For example, the
490 /// bindings used with [flutter_test] do this as they make heavy use of zones
491 /// to drive the framework with an artificial clock and to catch errors and
492 /// report them as test failures.)
493 bool debugCheckZone(String entryPoint) {
494 assert(() {
495 assert(
496 _debugBindingZone != null,
497 'debugCheckZone can only be used after the binding is fully initialized.',
498 );
499 if (Zone.current != _debugBindingZone) {
500 final Error message = FlutterError(
501 'Zone mismatch.\n'
502 'The Flutter bindings were initialized in a different zone than is now being used. '
503 'This will likely cause confusion and bugs as any zone-specific configuration will '
504 'inconsistently use the configuration of the original binding initialization zone '
505 'or this zone based on hard-to-predict factors such as which zone was active when '
506 'a particular callback was set.\n'
507 'It is important to use the same zone when calling `ensureInitialized` on the binding '
508 'as when calling `$entryPoint` later.\n'
509 'To make this ${debugZoneErrorsAreFatal ? 'error non-fatal' : 'warning fatal'}, '
510 'set BindingBase.debugZoneErrorsAreFatal to ${!debugZoneErrorsAreFatal} before the '
511 'bindings are initialized (i.e. as the first statement in `void main() { }`).',
512 );
513 if (debugZoneErrorsAreFatal) {
514 throw message;
515 }
516 FlutterError.reportError(
517 FlutterErrorDetails(
518 exception: message,
519 stack: StackTrace.current,
520 context: ErrorDescription('during $entryPoint'),
521 ),
522 );
523 }
524 return true;
525 }());
526 return true;
527 }
528
529 /// Called when the binding is initialized, to register service
530 /// extensions.
531 ///
532 /// Bindings that want to expose service extensions should overload
533 /// this method to register them using calls to
534 /// [registerSignalServiceExtension],
535 /// [registerBoolServiceExtension],
536 /// [registerNumericServiceExtension], and
537 /// [registerServiceExtension] (in increasing order of complexity).
538 ///
539 /// Implementations of this method must call their superclass
540 /// implementation.
541 ///
542 /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
543 ///
544 /// See also:
545 ///
546 /// * <https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#rpcs-requests-and-responses>
547 @protected
548 @mustCallSuper
549 void initServiceExtensions() {
550 assert(!_debugServiceExtensionsRegistered);
551
552 assert(() {
553 registerSignalServiceExtension(
554 name: FoundationServiceExtensions.reassemble.name,
555 callback: reassembleApplication,
556 );
557 return true;
558 }());
559
560 if (!kReleaseMode) {
561 if (!kIsWeb) {
562 registerSignalServiceExtension(
563 name: FoundationServiceExtensions.exit.name,
564 callback: _exitApplication,
565 );
566 }
567 // These service extensions are used in profile mode applications.
568 registerStringServiceExtension(
569 name: FoundationServiceExtensions.connectedVmServiceUri.name,
570 getter: () async => connectedVmServiceUri ?? '',
571 setter: (String uri) async {
572 connectedVmServiceUri = uri;
573 },
574 );
575 registerStringServiceExtension(
576 name: FoundationServiceExtensions.activeDevToolsServerAddress.name,
577 getter: () async => activeDevToolsServerAddress ?? '',
578 setter: (String serverAddress) async {
579 activeDevToolsServerAddress = serverAddress;
580 },
581 );
582 }
583
584 assert(() {
585 registerServiceExtension(
586 name: FoundationServiceExtensions.platformOverride.name,
587 callback: (Map<String, String> parameters) async {
588 if (parameters.containsKey('value')) {
589 final String value = parameters['value']!;
590 debugDefaultTargetPlatformOverride = null;
591 for (final TargetPlatform candidate in TargetPlatform.values) {
592 if (candidate.name == value) {
593 debugDefaultTargetPlatformOverride = candidate;
594 break;
595 }
596 }
597 _postExtensionStateChangedEvent(
598 FoundationServiceExtensions.platformOverride.name,
599 defaultTargetPlatform.name,
600 );
601 await reassembleApplication();
602 }
603 return <String, dynamic>{'value': defaultTargetPlatform.name};
604 },
605 );
606
607 registerServiceExtension(
608 name: FoundationServiceExtensions.brightnessOverride.name,
609 callback: (Map<String, String> parameters) async {
610 if (parameters.containsKey('value')) {
611 debugBrightnessOverride = switch (parameters['value']) {
612 'Brightness.light' => ui.Brightness.light,
613 'Brightness.dark' => ui.Brightness.dark,
614 _ => null,
615 };
616 _postExtensionStateChangedEvent(
617 FoundationServiceExtensions.brightnessOverride.name,
618 (debugBrightnessOverride ?? platformDispatcher.platformBrightness).toString(),
619 );
620 await reassembleApplication();
621 }
622 return <String, dynamic>{
623 'value': (debugBrightnessOverride ?? platformDispatcher.platformBrightness).toString(),
624 };
625 },
626 );
627 return true;
628 }());
629 assert(() {
630 _debugServiceExtensionsRegistered = true;
631 return true;
632 }());
633 }
634
635 /// Whether [lockEvents] is currently locking events.
636 ///
637 /// Binding subclasses that fire events should check this first, and if it is
638 /// set, queue events instead of firing them.
639 ///
640 /// Events should be flushed when [unlocked] is called.
641 @protected
642 bool get locked => _lockCount > 0;
643 int _lockCount = 0;
644
645 /// Locks the dispatching of asynchronous events and callbacks until the
646 /// callback's future completes.
647 ///
648 /// This causes input lag and should therefore be avoided when possible. It is
649 /// primarily intended for use during non-user-interactive time such as to
650 /// allow [reassembleApplication] to block input while it walks the tree
651 /// (which it partially does asynchronously).
652 ///
653 /// The [Future] returned by the `callback` argument is returned by [lockEvents].
654 ///
655 /// The [gestures] binding wraps [PlatformDispatcher.onPointerDataPacket] in
656 /// logic that honors this event locking mechanism. Similarly, tasks queued
657 /// using [SchedulerBinding.scheduleTask] will only start when events are not
658 /// [locked].
659 @protected
660 Future<void> lockEvents(Future<void> Function() callback) {
661 developer.TimelineTask? debugTimelineTask;
662 if (!kReleaseMode) {
663 debugTimelineTask = developer.TimelineTask()..start('Lock events');
664 }
665
666 _lockCount += 1;
667 final Future<void> future = callback();
668 future.whenComplete(() {
669 _lockCount -= 1;
670 if (!locked) {
671 if (!kReleaseMode) {
672 debugTimelineTask!.finish();
673 }
674 try {
675 unlocked();
676 } catch (error, stack) {
677 FlutterError.reportError(
678 FlutterErrorDetails(
679 exception: error,
680 stack: stack,
681 library: 'foundation',
682 context: ErrorDescription('while handling pending events'),
683 ),
684 );
685 }
686 }
687 });
688 return future;
689 }
690
691 /// Called by [lockEvents] when events get unlocked.
692 ///
693 /// This should flush any events that were queued while [locked] was true.
694 @protected
695 @mustCallSuper
696 void unlocked() {
697 assert(!locked);
698 }
699
700 /// Cause the entire application to redraw, e.g. after a hot reload.
701 ///
702 /// This is used by development tools when the application code has changed,
703 /// to cause the application to pick up any changed code. It can be triggered
704 /// manually by sending the `ext.flutter.reassemble` service extension signal.
705 ///
706 /// This method is very computationally expensive and should not be used in
707 /// production code. There is never a valid reason to cause the entire
708 /// application to repaint in production. All aspects of the Flutter framework
709 /// know how to redraw when necessary. It is only necessary in development
710 /// when the code is literally changed on the fly (e.g. in hot reload) or when
711 /// debug flags are being toggled.
712 ///
713 /// While this method runs, events are locked (e.g. pointer events are not
714 /// dispatched).
715 ///
716 /// Subclasses (binding classes) should override [performReassemble] to react
717 /// to this method being called. This method itself should not be overridden.
718 Future<void> reassembleApplication() {
719 return lockEvents(performReassemble);
720 }
721
722 /// This method is called by [reassembleApplication] to actually cause the
723 /// application to reassemble, e.g. after a hot reload.
724 ///
725 /// Bindings are expected to use this method to re-register anything that uses
726 /// closures, so that they do not keep pointing to old code, and to flush any
727 /// caches of previously computed values, in case the new code would compute
728 /// them differently. For example, the rendering layer triggers the entire
729 /// application to repaint when this is called.
730 ///
731 /// Do not call this method directly. Instead, use [reassembleApplication].
732 @mustCallSuper
733 @protected
734 Future<void> performReassemble() {
735 FlutterError.resetErrorCount();
736 return Future<void>.value();
737 }
738
739 /// Registers a service extension method with the given name (full
740 /// name "ext.flutter.name"), which takes no arguments and returns
741 /// no value.
742 ///
743 /// Calls the `callback` callback when the service extension is called.
744 ///
745 /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
746 @protected
747 void registerSignalServiceExtension({required String name, required AsyncCallback callback}) {
748 registerServiceExtension(
749 name: name,
750 callback: (Map<String, String> parameters) async {
751 await callback();
752 return <String, dynamic>{};
753 },
754 );
755 }
756
757 /// Registers a service extension method with the given name (full
758 /// name "ext.flutter.name"), which takes a single argument
759 /// "enabled" which can have the value "true" or the value "false"
760 /// or can be omitted to read the current value. (Any value other
761 /// than "true" is considered equivalent to "false". Other arguments
762 /// are ignored.)
763 ///
764 /// Calls the `getter` callback to obtain the value when
765 /// responding to the service extension method being called.
766 ///
767 /// Calls the `setter` callback with the new value when the
768 /// service extension method is called with a new value.
769 ///
770 /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
771 @protected
772 void registerBoolServiceExtension({
773 required String name,
774 required AsyncValueGetter<bool> getter,
775 required AsyncValueSetter<bool> setter,
776 }) {
777 registerServiceExtension(
778 name: name,
779 callback: (Map<String, String> parameters) async {
780 if (parameters.containsKey('enabled')) {
781 await setter(parameters['enabled'] == 'true');
782 _postExtensionStateChangedEvent(name, await getter() ? 'true' : 'false');
783 }
784 return <String, dynamic>{'enabled': await getter() ? 'true' : 'false'};
785 },
786 );
787 }
788
789 /// Registers a service extension method with the given name (full
790 /// name "ext.flutter.name"), which takes a single argument with the
791 /// same name as the method which, if present, must have a value
792 /// that can be parsed by [double.parse], and can be omitted to read
793 /// the current value. (Other arguments are ignored.)
794 ///
795 /// Calls the `getter` callback to obtain the value when
796 /// responding to the service extension method being called.
797 ///
798 /// Calls the `setter` callback with the new value when the
799 /// service extension method is called with a new value.
800 ///
801 /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
802 @protected
803 void registerNumericServiceExtension({
804 required String name,
805 required AsyncValueGetter<double> getter,
806 required AsyncValueSetter<double> setter,
807 }) {
808 registerServiceExtension(
809 name: name,
810 callback: (Map<String, String> parameters) async {
811 if (parameters.containsKey(name)) {
812 await setter(double.parse(parameters[name]!));
813 _postExtensionStateChangedEvent(name, (await getter()).toString());
814 }
815 return <String, dynamic>{name: (await getter()).toString()};
816 },
817 );
818 }
819
820 /// Sends an event when a service extension's state is changed.
821 ///
822 /// Clients should listen for this event to stay aware of the current service
823 /// extension state. Any service extension that manages a state should call
824 /// this method on state change.
825 ///
826 /// `value` reflects the newly updated service extension value.
827 ///
828 /// This will be called automatically for service extensions registered via
829 /// [registerBoolServiceExtension], [registerNumericServiceExtension], or
830 /// [registerStringServiceExtension].
831 void _postExtensionStateChangedEvent(String name, dynamic value) {
832 postEvent('Flutter.ServiceExtensionStateChanged', <String, dynamic>{
833 'extension': 'ext.flutter.$name',
834 'value': value,
835 });
836 }
837
838 /// All events dispatched by a [BindingBase] use this method instead of
839 /// calling [developer.postEvent] directly so that tests for [BindingBase]
840 /// can track which events were dispatched by overriding this method.
841 ///
842 /// This is unrelated to the events managed by [lockEvents].
843 @protected
844 void postEvent(String eventKind, Map<String, dynamic> eventData) {
845 developer.postEvent(eventKind, eventData);
846 }
847
848 /// Registers a service extension method with the given name (full name
849 /// "ext.flutter.name"), which optionally takes a single argument with the
850 /// name "value". If the argument is omitted, the value is to be read,
851 /// otherwise it is to be set. Returns the current value.
852 ///
853 /// Calls the `getter` callback to obtain the value when
854 /// responding to the service extension method being called.
855 ///
856 /// Calls the `setter` callback with the new value when the
857 /// service extension method is called with a new value.
858 ///
859 /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
860 @protected
861 void registerStringServiceExtension({
862 required String name,
863 required AsyncValueGetter<String> getter,
864 required AsyncValueSetter<String> setter,
865 }) {
866 registerServiceExtension(
867 name: name,
868 callback: (Map<String, String> parameters) async {
869 if (parameters.containsKey('value')) {
870 await setter(parameters['value']!);
871 _postExtensionStateChangedEvent(name, await getter());
872 }
873 return <String, dynamic>{'value': await getter()};
874 },
875 );
876 }
877
878 /// Registers a service extension method with the given name (full name
879 /// "ext.flutter.name").
880 ///
881 /// The given callback is called when the extension method is called. The
882 /// callback must return a [Future] that either eventually completes to a
883 /// return value in the form of a name/value map where the values can all be
884 /// converted to JSON using `json.encode()` (see [JsonEncoder]), or fails. In
885 /// case of failure, the failure is reported to the remote caller and is
886 /// dumped to the logs.
887 ///
888 /// The returned map will be mutated.
889 ///
890 /// {@template flutter.foundation.BindingBase.registerServiceExtension}
891 /// A registered service extension can only be activated if the vm-service
892 /// is included in the build, which only happens in debug and profile mode.
893 /// Although a service extension cannot be used in release mode its code may
894 /// still be included in the Dart snapshot and blow up binary size if it is
895 /// not wrapped in a guard that allows the tree shaker to remove it (see
896 /// sample code below).
897 ///
898 /// {@tool snippet}
899 /// The following code registers a service extension that is only included in
900 /// debug builds.
901 ///
902 /// ```dart
903 /// void myRegistrationFunction() {
904 /// assert(() {
905 /// // Register your service extension here.
906 /// return true;
907 /// }());
908 /// }
909 /// ```
910 /// {@end-tool}
911 ///
912 /// {@tool snippet}
913 /// A service extension registered with the following code snippet is
914 /// available in debug and profile mode.
915 ///
916 /// ```dart
917 /// void myOtherRegistrationFunction() {
918 /// // kReleaseMode is defined in the 'flutter/foundation.dart' package.
919 /// if (!kReleaseMode) {
920 /// // Register your service extension here.
921 /// }
922 /// }
923 /// ```
924 /// {@end-tool}
925 ///
926 /// Both guards ensure that Dart's tree shaker can remove the code for the
927 /// service extension in release builds.
928 /// {@endtemplate}
929 @protected
930 void registerServiceExtension({
931 required String name,
932 required ServiceExtensionCallback callback,
933 }) {
934 final String methodName = 'ext.flutter.$name';
935 developer.registerExtension(methodName, (String method, Map<String, String> parameters) async {
936 assert(method == methodName);
937 assert(() {
938 if (debugInstrumentationEnabled) {
939 debugPrint('service extension method received: $method($parameters)');
940 }
941 return true;
942 }());
943
944 // VM service extensions are handled as "out of band" messages by the VM,
945 // which means they are handled at various times, generally ASAP.
946 // Notably, this includes being handled in the middle of microtask loops.
947 // While this makes sense for some service extensions (e.g. "dump current
948 // stack trace", which explicitly doesn't want to wait for a loop to
949 // complete), Flutter extensions need not be handled with such high
950 // priority. Further, handling them with such high priority exposes us to
951 // the possibility that they're handled in the middle of a frame, which
952 // breaks many assertions. As such, we ensure they we run the callbacks
953 // on the outer event loop here.
954 await debugInstrumentAction<void>('Wait for outer event loop', () {
955 return Future<void>.delayed(Duration.zero);
956 });
957
958 late Map<String, dynamic> result;
959 try {
960 result = await callback(parameters);
961 } catch (exception, stack) {
962 FlutterError.reportError(
963 FlutterErrorDetails(
964 exception: exception,
965 stack: stack,
966 context: ErrorDescription('during a service extension callback for "$method"'),
967 ),
968 );
969 return developer.ServiceExtensionResponse.error(
970 developer.ServiceExtensionResponse.extensionError,
971 json.encode(<String, String>{
972 'exception': exception.toString(),
973 'stack': stack.toString(),
974 'method': method,
975 }),
976 );
977 }
978 result['type'] = '_extensionType';
979 result['method'] = method;
980 return developer.ServiceExtensionResponse.result(json.encode(result));
981 });
982 }
983
984 @override
985 String toString() => '<${objectRuntimeType(this, 'BindingBase')}>';
986}
987
988/// Terminate the Flutter application.
989Future<void> _exitApplication() async {
990 exit(0);
991}
992

Provided by KDAB

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