| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| /// @docImport 'dart:ui'; | |
| /// @docImport 'package:flutter/animation.dart'; | |
| /// @docImport 'package:flutter/cupertino.dart'; | |
| /// @docImport 'package:flutter/material.dart'; | |
| /// @docImport 'package:flutter_test/flutter_test.dart'; | |
| /// | |
| /// @docImport 'adapter.dart'; | |
| /// @docImport 'app_lifecycle_listener.dart'; | |
| /// @docImport 'basic.dart'; | |
| /// @docImport 'focus_scope.dart'; | |
| /// @docImport 'media_query.dart'; | |
| /// @docImport 'navigator.dart'; | |
| /// @docImport 'pop_scope.dart'; | |
| library; | |
| import 'dart:async'; | |
| import 'dart:developer' as developer; | |
| import 'dart:ui' | |
| show | |
| AccessibilityFeatures, | |
| AppExitResponse, | |
| AppLifecycleState, | |
| FrameTiming, | |
| Locale, | |
| PlatformDispatcher, | |
| TimingsCallback, | |
| ViewFocusEvent; | |
| import 'package:flutter/foundation.dart'; | |
| import 'package:flutter/gestures.dart'; | |
| import 'package:flutter/rendering.dart'; | |
| import 'package:flutter/scheduler.dart'; | |
| import 'package:flutter/services.dart'; | |
| import '../foundation/_features.dart'; | |
| import '_accessibility_evaluations.dart'; | |
| import '_window.dart'; | |
| import 'accessibility_inspector.dart'; | |
| import 'app.dart'; | |
| import 'debug.dart'; | |
| import 'focus_manager.dart'; | |
| import 'framework.dart'; | |
| import 'platform_menu_bar.dart'; | |
| import 'router.dart'; | |
| import 'service_extensions.dart'; | |
| import 'view.dart'; | |
| import 'widget_inspector.dart'; | |
| export 'dart:ui' show AppLifecycleState, Locale; | |
| // Examples can assume: | |
| // late FlutterView myFlutterView; | |
| // class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) => const Placeholder(); } | |
| /// Interface for classes that register with the Widgets layer binding. | |
| /// | |
| /// This can be used by any class, not just widgets. It provides an interface | |
| /// which is used by [WidgetsBinding.addObserver] and | |
| /// [WidgetsBinding.removeObserver] to notify objects of changes in the | |
| /// environment, such as changes to the device metrics or accessibility | |
| /// settings. It is used to implement features such as [MediaQuery]. | |
| /// | |
| /// This class can be extended directly, or mixed in, to get default behaviors | |
| /// for all of the handlers. Alternatively it can be used with the | |
| /// `implements` keyword, in which case all the handlers must be implemented | |
| /// (and the analyzer will list those that have been omitted). | |
| /// | |
| /// To start receiving notifications, call `WidgetsBinding.instance.addObserver` | |
| /// with a reference to the object implementing the [WidgetsBindingObserver] | |
| /// interface. To avoid memory leaks, call | |
| /// `WidgetsBinding.instance.removeObserver` to unregister the object when it | |
| /// reaches the end of its lifecycle. | |
| /// | |
| /// {@tool dartpad} | |
| /// This sample shows how to implement parts of the [State] and | |
| /// [WidgetsBindingObserver] protocols necessary to react to application | |
| /// lifecycle messages. See [didChangeAppLifecycleState]. | |
| /// | |
| /// To respond to other notifications, replace the [didChangeAppLifecycleState] | |
| /// method in this example with other methods from this class. | |
| /// | |
| /// ** See code in examples/api/lib/widgets/binding/widget_binding_observer.0.dart ** | |
| /// {@end-tool} | |
| abstract mixin class WidgetsBindingObserver { | |
| /// Called when the system tells the app to pop the current route, such as | |
| /// after a system back button press or back gesture. | |
| /// | |
| /// Observers are notified in registration order until one returns | |
| /// true. If none return true, the application quits. | |
| /// | |
| /// Observers are expected to return true if they were able to | |
| /// handle the notification, for example by closing an active dialog | |
| /// box, and false otherwise. The [WidgetsApp] widget uses this | |
| /// mechanism to notify the [Navigator] widget that it should pop | |
| /// its current route if possible. | |
| /// | |
| /// This method exposes the `popRoute` notification from | |
| /// [SystemChannels.navigation]. | |
| /// | |
| /// {@macro flutter.widgets.AndroidPredictiveBack} | |
| Future<bool> didPopRoute() => Future<bool>.value(false); | |
| /// Called at the start of a predictive back gesture. | |
| /// | |
| /// Observers are notified in registration order until one returns true or all | |
| /// observers have been notified. If an observer returns true then that | |
| /// observer, and only that observer, will be notified of subsequent events in | |
| /// this same gesture (for example [handleUpdateBackGestureProgress], etc.). | |
| /// | |
| /// Observers are expected to return true if they were able to handle the | |
| /// notification, for example by starting a predictive back animation, and | |
| /// false otherwise. [PredictiveBackPageTransitionsBuilder] uses this | |
| /// mechanism to listen for predictive back gestures. | |
| /// | |
| /// If all observers indicate they are not handling this back gesture by | |
| /// returning false, then a navigation pop will result when | |
| /// [handleCommitBackGesture] is called, as in a non-predictive system back | |
| /// gesture. | |
| /// | |
| /// Currently, this is only used on Android devices that support the | |
| /// predictive back feature. | |
| bool handleStartBackGesture(PredictiveBackEvent backEvent) => false; | |
| /// Called when a predictive back gesture moves. | |
| /// | |
| /// The observer which was notified of this gesture's [handleStartBackGesture] | |
| /// is the same observer notified for this. | |
| /// | |
| /// Currently, this is only used on Android devices that support the | |
| /// predictive back feature. | |
| void handleUpdateBackGestureProgress(PredictiveBackEvent backEvent) {} | |
| /// Called when a predictive back gesture is finished successfully, indicating | |
| /// that the current route should be popped. | |
| /// | |
| /// The observer which was notified of this gesture's [handleStartBackGesture] | |
| /// is the same observer notified for this. If there is none, then a | |
| /// navigation pop will result, as in a non-predictive system back gesture. | |
| /// | |
| /// Currently, this is only used on Android devices that support the | |
| /// predictive back feature. | |
| void handleCommitBackGesture() {} | |
| /// Called when a predictive back gesture is canceled, indicating that no | |
| /// navigation should occur. | |
| /// | |
| /// The observer which was notified of this gesture's [handleStartBackGesture] | |
| /// is the same observer notified for this. | |
| /// | |
| /// Currently, this is only used on Android devices that support the | |
| /// predictive back feature. | |
| void handleCancelBackGesture() {} | |
| /// Called when the user taps the status bar on iOS, to scroll a scroll | |
| /// view to the top. | |
| /// | |
| /// This event should usually only be handled by at most one scroll view, so | |
| /// implementer(s) of this callback must coordinate to determine the most | |
| /// suitable scroll view for handling this event. | |
| /// | |
| /// This callback is only called on iOS. The default implementation provided by | |
| /// [WidgetsBindingObserver] does nothing. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [Scaffold] and [CupertinoPageScaffold] which use this callback to implement | |
| /// iOS scroll-to-top. | |
| void handleStatusBarTap() {} | |
| /// Called when the host tells the application to push a new route onto the | |
| /// navigator. | |
| /// | |
| /// Observers are expected to return true if they were able to | |
| /// handle the notification. Observers are notified in registration | |
| /// order until one returns true. | |
| /// | |
| /// This method exposes the `pushRoute` notification from | |
| /// [SystemChannels.navigation]. | |
| @Deprecated( | |
| 'Use didPushRouteInformation instead. ' | |
| 'This feature was deprecated after v3.8.0-14.0.pre.', | |
| ) | |
| Future<bool> didPushRoute(String route) => Future<bool>.value(false); | |
| /// Called when the host tells the application to push a new | |
| /// [RouteInformation] and a restoration state onto the router. | |
| /// | |
| /// Observers are expected to return true if they were able to | |
| /// handle the notification. Observers are notified in registration | |
| /// order until one returns true. | |
| /// | |
| /// This method exposes the `pushRouteInformation` notification from | |
| /// [SystemChannels.navigation]. | |
| /// | |
| /// The default implementation is to call the [didPushRoute] directly with the | |
| /// string constructed from [RouteInformation.uri]'s path and query parameters. | |
| // TODO(chunhtai): remove the default implementation once `didPushRoute` is | |
| // removed. | |
| Future<bool> didPushRouteInformation(RouteInformation routeInformation) { | |
| final Uri uri = routeInformation.uri; | |
| return didPushRoute( | |
| Uri.decodeComponent( | |
| Uri( | |
| path: uri.path.isEmpty ? '/' : uri.path, | |
| queryParameters: uri.queryParametersAll.isEmpty ? null : uri.queryParametersAll, | |
| fragment: uri.fragment.isEmpty ? null : uri.fragment, | |
| ).toString(), | |
| ), | |
| ); | |
| } | |
| /// Called when the application's dimensions change. For example, | |
| /// when a phone is rotated. | |
| /// | |
| /// This method exposes notifications from | |
| /// [dart:ui.PlatformDispatcher.onMetricsChanged]. | |
| /// | |
| /// {@tool snippet} | |
| /// | |
| /// This [StatefulWidget] implements the parts of the [State] and | |
| /// [WidgetsBindingObserver] protocols necessary to react when the device is | |
| /// rotated (or otherwise changes dimensions). | |
| /// | |
| /// ```dart | |
| /// class MetricsReactor extends StatefulWidget { | |
| /// const MetricsReactor({ super.key }); | |
| /// | |
| /// @override | |
| /// State<MetricsReactor> createState() => _MetricsReactorState(); | |
| /// } | |
| /// | |
| /// class _MetricsReactorState extends State<MetricsReactor> with WidgetsBindingObserver { | |
| /// late Size _lastSize; | |
| /// | |
| /// @override | |
| /// void initState() { | |
| /// super.initState(); | |
| /// WidgetsBinding.instance.addObserver(this); | |
| /// } | |
| /// | |
| /// @override | |
| /// void didChangeDependencies() { | |
| /// super.didChangeDependencies(); | |
| /// // [View.of] exposes the view from `WidgetsBinding.instance.platformDispatcher.views` | |
| /// // into which this widget is drawn. | |
| /// _lastSize = View.of(context).physicalSize; | |
| /// } | |
| /// | |
| /// @override | |
| /// void dispose() { | |
| /// WidgetsBinding.instance.removeObserver(this); | |
| /// super.dispose(); | |
| /// } | |
| /// | |
| /// @override | |
| /// void didChangeMetrics() { | |
| /// setState(() { _lastSize = View.of(context).physicalSize; }); | |
| /// } | |
| /// | |
| /// @override | |
| /// Widget build(BuildContext context) { | |
| /// return Text('Current size: $_lastSize'); | |
| /// } | |
| /// } | |
| /// ``` | |
| /// {@end-tool} | |
| /// | |
| /// In general, this is unnecessary as the layout system takes care of | |
| /// automatically recomputing the application geometry when the application | |
| /// size changes. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [MediaQuery.sizeOf], which provides a similar service with less | |
| /// boilerplate. | |
| void didChangeMetrics() {} | |
| /// Called when the platform's text scale factor changes. | |
| /// | |
| /// This typically happens as the result of the user changing system | |
| /// preferences, and it should affect all of the text sizes in the | |
| /// application. | |
| /// | |
| /// This method exposes notifications from | |
| /// [dart:ui.PlatformDispatcher.onTextScaleFactorChanged]. | |
| /// | |
| /// {@tool snippet} | |
| /// | |
| /// ```dart | |
| /// class TextScaleFactorReactor extends StatefulWidget { | |
| /// const TextScaleFactorReactor({super.key}); | |
| /// | |
| /// @override | |
| /// State<TextScaleFactorReactor> createState() => _TextScaleFactorReactorState(); | |
| /// } | |
| /// | |
| /// class _TextScaleFactorReactorState extends State<TextScaleFactorReactor> | |
| /// with WidgetsBindingObserver { | |
| /// double _lastTextScaleFactor = | |
| /// WidgetsBinding.instance.platformDispatcher.textScaleFactor; | |
| /// | |
| /// @override | |
| /// void initState() { | |
| /// super.initState(); | |
| /// WidgetsBinding.instance.addObserver(this); | |
| /// } | |
| /// | |
| /// @override | |
| /// void dispose() { | |
| /// WidgetsBinding.instance.removeObserver(this); | |
| /// super.dispose(); | |
| /// } | |
| /// | |
| /// @override | |
| /// void didChangeTextScaleFactor() { | |
| /// setState(() { | |
| /// _lastTextScaleFactor = | |
| /// WidgetsBinding.instance.platformDispatcher.textScaleFactor; | |
| /// }); | |
| /// } | |
| /// | |
| /// @override | |
| /// Widget build(BuildContext context) { | |
| /// return Text('Current scale factor: $_lastTextScaleFactor'); | |
| /// } | |
| /// } | |
| /// ``` | |
| /// {@end-tool} | |
| /// | |
| /// See also: | |
| /// | |
| /// * [MediaQuery.textScaleFactorOf], which provides a similar service with less | |
| /// boilerplate. | |
| void didChangeTextScaleFactor() {} | |
| /// Called when the platform brightness changes. | |
| /// | |
| /// This method exposes notifications from | |
| /// [dart:ui.PlatformDispatcher.onPlatformBrightnessChanged]. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [MediaQuery.platformBrightnessOf], which provides a similar service with | |
| /// less boilerplate. | |
| void didChangePlatformBrightness() {} | |
| /// Called when the system tells the app that the user's locale has | |
| /// changed. For example, if the user changes the system language | |
| /// settings. | |
| /// | |
| /// This method exposes notifications from | |
| /// [dart:ui.PlatformDispatcher.onLocaleChanged]. | |
| void didChangeLocales(List<Locale>? locales) {} | |
| /// Called when the system puts the app in the background or returns | |
| /// the app to the foreground. | |
| /// | |
| /// An example of implementing this method is provided in the class-level | |
| /// documentation for the [WidgetsBindingObserver] class. | |
| /// | |
| /// This method exposes notifications from [SystemChannels.lifecycle]. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [AppLifecycleListener], an alternative API for responding to | |
| /// application lifecycle changes. | |
| void didChangeAppLifecycleState(AppLifecycleState state) {} | |
| /// Called whenever the [PlatformDispatcher] receives a notification that the | |
| /// focus state on a view has changed. | |
| /// | |
| /// The [event] contains the view ID for the view that changed its focus | |
| /// state. | |
| /// | |
| /// The view ID of the [FlutterView] in which a particular [BuildContext] | |
| /// resides can be retrieved with `View.of(context).viewId`, so that it may be | |
| /// compared with the view ID in the `event` to see if the event pertains to | |
| /// the given context. | |
| void didChangeViewFocus(ViewFocusEvent event) {} | |
| /// Called when a request is received from the system to exit the application. | |
| /// | |
| /// If any observer responds with [AppExitResponse.cancel], it will cancel the | |
| /// exit. All observers will be asked before exiting. | |
| /// | |
| /// {@macro flutter.services.binding.ServicesBinding.requestAppExit} | |
| /// | |
| /// See also: | |
| /// | |
| /// * [ServicesBinding.exitApplication] for a function to call that will request | |
| /// that the application exits. | |
| Future<AppExitResponse> didRequestAppExit() async { | |
| return AppExitResponse.exit; | |
| } | |
| /// Called when the system is running low on memory. | |
| /// | |
| /// This method exposes the `memoryPressure` notification from | |
| /// [SystemChannels.system]. | |
| void didHaveMemoryPressure() {} | |
| /// Called when the system changes the set of currently active accessibility | |
| /// features. | |
| /// | |
| /// This method exposes notifications from | |
| /// [dart:ui.PlatformDispatcher.onAccessibilityFeaturesChanged]. | |
| void didChangeAccessibilityFeatures() {} | |
| } | |
| /// The glue between the widgets layer and the Flutter engine. | |
| /// | |
| /// The [WidgetsBinding] manages a single [Element] tree rooted at [rootElement]. | |
| /// Calling [runApp] (which indirectly calls [attachRootWidget]) bootstraps that | |
| /// element tree. | |
| /// | |
| /// ## Relationship to render trees | |
| /// | |
| /// Multiple render trees may be associated with the element tree. Those are | |
| /// managed by the underlying [RendererBinding]. | |
| /// | |
| /// The element tree is segmented into two types of zones: rendering zones and | |
| /// non-rendering zones. | |
| /// | |
| /// A rendering zone is a part of the element tree that is backed by a render | |
| /// tree and it describes the pixels that are drawn on screen. For elements in | |
| /// this zone, [Element.renderObject] never returns null because the elements | |
| /// are all associated with [RenderObject]s. Almost all widgets can be placed in | |
| /// a rendering zone; notable exceptions are the [View] widget, [ViewCollection] | |
| /// widget, and [RootWidget]. | |
| /// | |
| /// A non-rendering zone is a part of the element tree that is not backed by a | |
| /// render tree. For elements in this zone, [Element.renderObject] returns null | |
| /// because the elements are not associated with any [RenderObject]s. Only | |
| /// widgets that do not produce a [RenderObject] can be used in this zone | |
| /// because there is no render tree to attach the render object to. In other | |
| /// words, [RenderObjectWidget]s cannot be used in this zone. Typically, one | |
| /// would find [InheritedWidget]s, [View]s, and [ViewCollection]s in this zone | |
| /// to inject data across rendering zones into the tree and to organize the | |
| /// rendering zones (and by extension their associated render trees) into a | |
| /// unified element tree. | |
| /// | |
| /// The root of the element tree at [rootElement] starts a non-rendering zone. | |
| /// Within a non-rendering zone, the [View] widget is used to start a rendering | |
| /// zone by bootstrapping a render tree. Within a rendering zone, the | |
| /// [ViewAnchor] can be used to start a new non-rendering zone. | |
| /// | |
| // TODO(goderbauer): Include an example graph showcasing the different zones. | |
| /// | |
| /// To figure out if an element is in a rendering zone it may walk up the tree | |
| /// calling [Element.debugExpectsRenderObjectForSlot] on its ancestors. If it | |
| /// reaches an element that returns false, it is in a non-rendering zone. If it | |
| /// reaches a [RenderObjectElement] ancestor it is in a rendering zone. | |
| mixin WidgetsBinding | |
| on | |
| BindingBase, | |
| ServicesBinding, | |
| SchedulerBinding, | |
| GestureBinding, | |
| RendererBinding, | |
| SemanticsBinding { | |
| @override | |
| void initInstances() { | |
| super.initInstances(); | |
| _instance = this; | |
| assert(() { | |
| _debugAddStackFilters(); | |
| return true; | |
| }()); | |
| // Initialization of [_buildOwner] has to be done after | |
| // [super.initInstances] is called, as it requires [ServicesBinding] to | |
| // properly setup the [defaultBinaryMessenger] instance. | |
| _buildOwner = BuildOwner(); | |
| buildOwner!.onBuildScheduled = _handleBuildScheduled; | |
| platformDispatcher.onLocaleChanged = handleLocaleChanged; | |
| SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation); | |
| SystemChannels.backGesture.setMethodCallHandler(_handleBackGestureInvocation); | |
| SystemChannels.statusBar.setMethodCallHandler(_handleStatusBarActions); | |
| assert(() { | |
| FlutterErrorDetails.propertiesTransformers.add(debugTransformDebugCreator); | |
| return true; | |
| }()); | |
| platformMenuDelegate = DefaultPlatformMenuDelegate(); | |
| _windowingOwner = createDefaultWindowingOwner(); | |
| } | |
| /// The current [WidgetsBinding], if one has been created. | |
| /// | |
| /// Provides access to the features exposed by this mixin. The binding must | |
| /// be initialized before using this getter; this is typically done by calling | |
| /// [runApp] or [WidgetsFlutterBinding.ensureInitialized]. | |
| static WidgetsBinding get instance => BindingBase.checkInstance(_instance); | |
| static WidgetsBinding? _instance; | |
| /// If true, forces the widget inspector to be visible. | |
| /// | |
| /// Overrides the `debugShowWidgetInspector` value set in [WidgetsApp]. | |
| /// | |
| /// Used by the `debugShowWidgetInspector` debugging extension. | |
| /// | |
| /// The inspector allows the selection of a location on your device or emulator | |
| /// and view what widgets and render objects associated with it. An outline of | |
| /// the selected widget and some summary information is shown on device and | |
| /// more detailed information is shown in the IDE or DevTools. | |
| bool get debugShowWidgetInspectorOverride { | |
| return debugShowWidgetInspectorOverrideNotifier.value; | |
| } | |
| set debugShowWidgetInspectorOverride(bool value) { | |
| debugShowWidgetInspectorOverrideNotifier.value = value; | |
| } | |
| /// Notifier for [debugShowWidgetInspectorOverride]. | |
| ValueNotifier<bool> get debugShowWidgetInspectorOverrideNotifier => | |
| _debugShowWidgetInspectorOverrideNotifierObject ??= ValueNotifier<bool>(false); | |
| ValueNotifier<bool>? _debugShowWidgetInspectorOverrideNotifierObject; | |
| /// The notifier for whether or not taps on the device are treated as widget | |
| /// selections when the widget inspector is enabled. | |
| /// | |
| /// - If true, taps in the app are intercepted by the widget inspector. | |
| /// - If false, taps in the app are not intercepted by the widget inspector. | |
| ValueNotifier<bool> get debugWidgetInspectorSelectionOnTapEnabled => | |
| _debugWidgetInspectorSelectionOnTapEnabledNotifierObject ??= ValueNotifier<bool>(true); | |
| ValueNotifier<bool>? _debugWidgetInspectorSelectionOnTapEnabledNotifierObject; | |
| /// If true, [WidgetInspector] will not be automatically injected into the | |
| /// widget tree. | |
| /// | |
| /// This overrides the behavior where [WidgetInspector] is added to the | |
| /// widget tree created by [WidgetsApp] when the `debugShowWidgetInspector` | |
| /// value is set in [WidgetsApp] or [debugShowWidgetInspectorOverride] is set | |
| /// to true. | |
| /// | |
| /// Used by tools that want more control over which widgets can be selected | |
| /// and highlighted by the widget inspector by manually injecting instances of | |
| /// [WidgetInspector]. | |
| bool get debugExcludeRootWidgetInspector => _debugExcludeRootWidgetInspector; | |
| set debugExcludeRootWidgetInspector(bool value) { | |
| _debugExcludeRootWidgetInspector = value; | |
| } | |
| bool _debugExcludeRootWidgetInspector = false; | |
| @visibleForTesting | |
| @override | |
| void resetInternalState() { | |
| // ignore: invalid_use_of_visible_for_testing_member, https://github.com/dart-lang/sdk/issues/41998 | |
| super.resetInternalState(); | |
| _debugShowWidgetInspectorOverrideNotifierObject?.dispose(); | |
| _debugShowWidgetInspectorOverrideNotifierObject = null; | |
| _debugWidgetInspectorSelectionOnTapEnabledNotifierObject?.dispose(); | |
| _debugWidgetInspectorSelectionOnTapEnabledNotifierObject = null; | |
| } | |
| void _debugAddStackFilters() { | |
| const elementInflateWidget = PartialStackFrame( | |
| package: 'package:flutter/src/widgets/framework.dart', | |
| className: 'Element', | |
| method: 'inflateWidget', | |
| ); | |
| const elementUpdateChild = PartialStackFrame( | |
| package: 'package:flutter/src/widgets/framework.dart', | |
| className: 'Element', | |
| method: 'updateChild', | |
| ); | |
| const elementRebuild = PartialStackFrame( | |
| package: 'package:flutter/src/widgets/framework.dart', | |
| className: 'Element', | |
| method: 'rebuild', | |
| ); | |
| const componentElementPerformRebuild = PartialStackFrame( | |
| package: 'package:flutter/src/widgets/framework.dart', | |
| className: 'ComponentElement', | |
| method: 'performRebuild', | |
| ); | |
| const componentElementFirstBuild = PartialStackFrame( | |
| package: 'package:flutter/src/widgets/framework.dart', | |
| className: 'ComponentElement', | |
| method: '_firstBuild', | |
| ); | |
| const componentElementMount = PartialStackFrame( | |
| package: 'package:flutter/src/widgets/framework.dart', | |
| className: 'ComponentElement', | |
| method: 'mount', | |
| ); | |
| const statefulElementFirstBuild = PartialStackFrame( | |
| package: 'package:flutter/src/widgets/framework.dart', | |
| className: 'StatefulElement', | |
| method: '_firstBuild', | |
| ); | |
| const singleChildMount = PartialStackFrame( | |
| package: 'package:flutter/src/widgets/framework.dart', | |
| className: 'SingleChildRenderObjectElement', | |
| method: 'mount', | |
| ); | |
| const statefulElementRebuild = PartialStackFrame( | |
| package: 'package:flutter/src/widgets/framework.dart', | |
| className: 'StatefulElement', | |
| method: 'performRebuild', | |
| ); | |
| const replacementString = '... Normal element mounting'; | |
| // ComponentElement variations | |
| FlutterError.addDefaultStackFilter( | |
| const RepetitiveStackFrameFilter( | |
| frames: <PartialStackFrame>[ | |
| elementInflateWidget, | |
| elementUpdateChild, | |
| componentElementPerformRebuild, | |
| elementRebuild, | |
| componentElementFirstBuild, | |
| componentElementMount, | |
| ], | |
| replacement: replacementString, | |
| ), | |
| ); | |
| FlutterError.addDefaultStackFilter( | |
| const RepetitiveStackFrameFilter( | |
| frames: <PartialStackFrame>[ | |
| elementUpdateChild, | |
| componentElementPerformRebuild, | |
| elementRebuild, | |
| componentElementFirstBuild, | |
| componentElementMount, | |
| ], | |
| replacement: replacementString, | |
| ), | |
| ); | |
| // StatefulElement variations | |
| FlutterError.addDefaultStackFilter( | |
| const RepetitiveStackFrameFilter( | |
| frames: <PartialStackFrame>[ | |
| elementInflateWidget, | |
| elementUpdateChild, | |
| componentElementPerformRebuild, | |
| statefulElementRebuild, | |
| elementRebuild, | |
| componentElementFirstBuild, | |
| statefulElementFirstBuild, | |
| componentElementMount, | |
| ], | |
| replacement: replacementString, | |
| ), | |
| ); | |
| FlutterError.addDefaultStackFilter( | |
| const RepetitiveStackFrameFilter( | |
| frames: <PartialStackFrame>[ | |
| elementUpdateChild, | |
| componentElementPerformRebuild, | |
| statefulElementRebuild, | |
| elementRebuild, | |
| componentElementFirstBuild, | |
| statefulElementFirstBuild, | |
| componentElementMount, | |
| ], | |
| replacement: replacementString, | |
| ), | |
| ); | |
| // SingleChildRenderObjectElement variations | |
| FlutterError.addDefaultStackFilter( | |
| const RepetitiveStackFrameFilter( | |
| frames: <PartialStackFrame>[elementInflateWidget, elementUpdateChild, singleChildMount], | |
| replacement: replacementString, | |
| ), | |
| ); | |
| FlutterError.addDefaultStackFilter( | |
| const RepetitiveStackFrameFilter( | |
| frames: <PartialStackFrame>[elementUpdateChild, singleChildMount], | |
| replacement: replacementString, | |
| ), | |
| ); | |
| } | |
| @override | |
| void initServiceExtensions() { | |
| super.initServiceExtensions(); | |
| if (!kReleaseMode) { | |
| registerServiceExtension( | |
| name: WidgetsServiceExtensions.debugDumpApp.name, | |
| callback: (Map<String, String> parameters) async { | |
| final String data = _debugDumpAppString(); | |
| return <String, Object>{'data': data}; | |
| }, | |
| ); | |
| registerServiceExtension( | |
| name: WidgetsServiceExtensions.debugDumpFocusTree.name, | |
| callback: (Map<String, String> parameters) async { | |
| final String data = focusManager.toStringDeep(); | |
| return <String, Object>{'data': data}; | |
| }, | |
| ); | |
| if (!kIsWeb) { | |
| registerBoolServiceExtension( | |
| name: WidgetsServiceExtensions.showPerformanceOverlay.name, | |
| getter: () => Future<bool>.value(WidgetsApp.showPerformanceOverlayOverride), | |
| setter: (bool value) { | |
| if (WidgetsApp.showPerformanceOverlayOverride == value) { | |
| return Future<void>.value(); | |
| } | |
| WidgetsApp.showPerformanceOverlayOverride = value; | |
| return _forceRebuild(); | |
| }, | |
| ); | |
| } | |
| registerServiceExtension( | |
| name: WidgetsServiceExtensions.didSendFirstFrameEvent.name, | |
| callback: (_) async { | |
| return <String, dynamic>{ | |
| // This is defined to return a STRING, not a boolean. | |
| // Devtools, the Intellij plugin, and the flutter tool all depend | |
| // on it returning a string and not a boolean. | |
| 'enabled': _needToReportFirstFrame ? 'false' : 'true', | |
| }; | |
| }, | |
| ); | |
| registerServiceExtension( | |
| name: WidgetsServiceExtensions.didSendFirstFrameRasterizedEvent.name, | |
| callback: (_) async { | |
| return <String, dynamic>{ | |
| // This is defined to return a STRING, not a boolean. | |
| // Devtools, the Intellij plugin, and the flutter tool all depend | |
| // on it returning a string and not a boolean. | |
| 'enabled': firstFrameRasterized ? 'true' : 'false', | |
| }; | |
| }, | |
| ); | |
| // Expose the ability to send Widget rebuilds as [Timeline] events. | |
| registerBoolServiceExtension( | |
| name: WidgetsServiceExtensions.profileWidgetBuilds.name, | |
| getter: () async => debugProfileBuildsEnabled, | |
| setter: (bool value) async { | |
| debugProfileBuildsEnabled = value; | |
| }, | |
| ); | |
| registerBoolServiceExtension( | |
| name: WidgetsServiceExtensions.profileUserWidgetBuilds.name, | |
| getter: () async => debugProfileBuildsEnabledUserWidgets, | |
| setter: (bool value) async { | |
| debugProfileBuildsEnabledUserWidgets = value; | |
| }, | |
| ); | |
| registerServiceExtension( | |
| name: WidgetsServiceExtensions.accessibilityEvaluations.name, | |
| callback: (Map<String, String> parameters) async { | |
| final String? type = parameters['type']; | |
| if (type == null) { | |
| throw Exception('type parameter is required'); | |
| } | |
| switch (type) { | |
| case 'MinimumTextContrastEvaluation': | |
| if (parameters case { | |
| 'minNormalTextContrastRatio': final String minNormalTextContrastRatio, | |
| 'minLargeTextContrastRatio': final String minLargeTextContrastRatio, | |
| }) { | |
| final EvaluationResult result = await MinimumTextContrastEvaluation( | |
| minNormalTextContrastRatio: double.parse(minNormalTextContrastRatio), | |
| minLargeTextContrastRatio: double.parse(minLargeTextContrastRatio), | |
| ).evaluate(this); | |
| return _formatEvaluationResult(result.violations); | |
| } | |
| throw Exception('Invalid arguments'); | |
| case 'MinimumTapTargetEvaluation': | |
| if (parameters case {'targetSize': final String targetSize}) { | |
| final EvaluationResult result = await MinimumTapTargetEvaluation( | |
| size: Size.square(double.parse(targetSize)), | |
| ).evaluate(this); | |
| return _formatEvaluationResult(result.violations); | |
| } | |
| throw Exception('Invalid arguments'); | |
| case 'LabeledTapTargetEvaluation': | |
| final EvaluationResult result = await const LabeledTapTargetEvaluation().evaluate( | |
| this, | |
| ); | |
| return _formatEvaluationResult(result.violations); | |
| default: | |
| throw Exception('unknown type: $type'); | |
| } | |
| }, | |
| ); | |
| } | |
| assert(() { | |
| registerBoolServiceExtension( | |
| name: WidgetsServiceExtensions.debugAllowBanner.name, | |
| getter: () => Future<bool>.value(WidgetsApp.debugAllowBannerOverride), | |
| setter: (bool value) { | |
| if (WidgetsApp.debugAllowBannerOverride == value) { | |
| return Future<void>.value(); | |
| } | |
| WidgetsApp.debugAllowBannerOverride = value; | |
| return _forceRebuild(); | |
| }, | |
| ); | |
| AccessibilityInspector.instance.initServiceExtensions(registerServiceExtension); | |
| WidgetInspectorService.instance.initServiceExtensions(registerServiceExtension); | |
| return true; | |
| }()); | |
| } | |
| Map<String, List<Map<String, String>>> _formatEvaluationResult(List<Violation> violations) { | |
| return <String, List<Map<String, String>>>{ | |
| 'result': violations.map((Violation violation) { | |
| return <String, String>{ | |
| 'nodeId': violation.node.id.toString(), | |
| 'message': violation.reason, | |
| }; | |
| }).toList(), | |
| }; | |
| } | |
| Future<void> _forceRebuild() { | |
| if (rootElement != null) { | |
| buildOwner!.reassemble(rootElement!); | |
| return endOfFrame; | |
| } | |
| return Future<void>.value(); | |
| } | |
| /// The [BuildOwner] in charge of executing the build pipeline for the | |
| /// widget tree rooted at this binding. | |
| BuildOwner? get buildOwner => _buildOwner; | |
| // Initialization of [_buildOwner] has to be done within the [initInstances] | |
| // method, as it requires [ServicesBinding] to properly setup the | |
| // [defaultBinaryMessenger] instance. | |
| BuildOwner? _buildOwner; | |
| /// The object in charge of the focus tree. | |
| /// | |
| /// Rarely used directly. Instead, consider using [FocusScope.of] to obtain | |
| /// the [FocusScopeNode] for a given [BuildContext]. | |
| /// | |
| /// See [FocusManager] for more details. | |
| FocusManager get focusManager => _buildOwner!.focusManager; | |
| /// A delegate that communicates with a platform plugin for serializing and | |
| /// managing platform-rendered menu bars created by [PlatformMenuBar]. | |
| /// | |
| /// This is set by default to a [DefaultPlatformMenuDelegate] instance in | |
| /// [initInstances]. | |
| late PlatformMenuDelegate platformMenuDelegate; | |
| final List<WidgetsBindingObserver> _observers = <WidgetsBindingObserver>[]; | |
| /// Registers the given object as a binding observer. Binding | |
| /// observers are notified when various application events occur, | |
| /// for example when the system locale changes. Generally, one | |
| /// widget in the widget tree registers itself as a binding | |
| /// observer, and converts the system state into inherited widgets. | |
| /// | |
| /// For example, the [WidgetsApp] widget registers as a binding | |
| /// observer and passes the screen size to a [MediaQuery] widget | |
| /// each time it is built, which enables other widgets to use the | |
| /// [MediaQuery.sizeOf] static method and (implicitly) the | |
| /// [InheritedWidget] mechanism to be notified whenever the screen | |
| /// size changes (e.g. whenever the screen rotates). | |
| /// | |
| /// See also: | |
| /// | |
| /// * [removeObserver], to release the resources reserved by this method. | |
| /// * [WidgetsBindingObserver], which has an example of using this method. | |
| void addObserver(WidgetsBindingObserver observer) => _observers.add(observer); | |
| /// Unregisters the given observer. This should be used sparingly as | |
| /// it is relatively expensive (O(N) in the number of registered | |
| /// observers). | |
| /// | |
| /// See also: | |
| /// | |
| /// * [addObserver], for the method that adds observers in the first place. | |
| /// * [WidgetsBindingObserver], which has an example of using this method. | |
| bool removeObserver(WidgetsBindingObserver observer) { | |
| _backGestureObservers.remove(observer); | |
| return _observers.remove(observer); | |
| } | |
| @override | |
| Future<AppExitResponse> handleRequestAppExit() async { | |
| var didCancel = false; | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| if (!_observers.contains(observer)) { | |
| continue; | |
| } | |
| try { | |
| if ((await observer.didRequestAppExit()) == AppExitResponse.cancel) { | |
| didCancel = true; | |
| // Don't early return. For the case where someone is just using the | |
| // observer to know when exit happens, we want to call all the | |
| // observers, even if we already know we're going to cancel. | |
| } | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.didRequestAppExit', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| return didCancel ? AppExitResponse.cancel : AppExitResponse.exit; | |
| } | |
| @override | |
| void handleMetricsChanged() { | |
| super.handleMetricsChanged(); | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| try { | |
| observer.didChangeMetrics(); | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.didChangeMetrics', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| @override | |
| void handleTextScaleFactorChanged() { | |
| super.handleTextScaleFactorChanged(); | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| try { | |
| observer.didChangeTextScaleFactor(); | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.didChangeTextScaleFactor', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| @override | |
| void handlePlatformBrightnessChanged() { | |
| super.handlePlatformBrightnessChanged(); | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| try { | |
| observer.didChangePlatformBrightness(); | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.didChangePlatformBrightness', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| @override | |
| void handleAccessibilityFeaturesChanged() { | |
| super.handleAccessibilityFeaturesChanged(); | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| try { | |
| observer.didChangeAccessibilityFeatures(); | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.didChangeAccessibilityFeatures', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| /// Called when the system locale changes. | |
| /// | |
| /// Calls [dispatchLocalesChanged] to notify the binding observers. | |
| /// | |
| /// See [dart:ui.PlatformDispatcher.onLocaleChanged]. | |
| @protected | |
| @mustCallSuper | |
| @visibleForTesting | |
| void handleLocaleChanged() { | |
| dispatchLocalesChanged(platformDispatcher.locales); | |
| } | |
| /// Notify all the observers that the locale has changed (using | |
| /// [WidgetsBindingObserver.didChangeLocales]), giving them the | |
| /// `locales` argument. | |
| /// | |
| /// This is called by [handleLocaleChanged] when the | |
| /// [PlatformDispatcher.onLocaleChanged] notification is received. | |
| @protected | |
| @mustCallSuper | |
| void dispatchLocalesChanged(List<Locale>? locales) { | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| try { | |
| observer.didChangeLocales(locales); | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.didChangeLocales', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| /// Notify all the observers that the active set of [AccessibilityFeatures] | |
| /// has changed (using [WidgetsBindingObserver.didChangeAccessibilityFeatures]), | |
| /// giving them the `features` argument. | |
| /// | |
| /// This is called by [handleAccessibilityFeaturesChanged] when the | |
| /// [PlatformDispatcher.onAccessibilityFeaturesChanged] notification is received. | |
| @protected | |
| @mustCallSuper | |
| void dispatchAccessibilityFeaturesChanged() { | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| observer.didChangeAccessibilityFeatures(); | |
| } | |
| } | |
| Future<void> _handleStatusBarActions(MethodCall call) async { | |
| assert(call.method == 'handleScrollToTop'); | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| try { | |
| observer.handleStatusBarTap(); | |
| } catch (exception, stack) { | |
| final details = FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription('handling status bar action'), | |
| ); | |
| FlutterError.reportError(details); | |
| // No error widget possible here since it wouldn't have a view to render into. | |
| } | |
| } | |
| } | |
| /// Called when the system pops the current route. | |
| /// | |
| /// This first notifies the binding observers (using | |
| /// [WidgetsBindingObserver.didPopRoute]), in registration order, until one | |
| /// returns true, meaning that it was able to handle the request (e.g. by | |
| /// closing a dialog box). If none return true, then the application is shut | |
| /// down by calling [SystemNavigator.pop]. | |
| /// | |
| /// [WidgetsApp] uses this in conjunction with a [Navigator] to | |
| /// cause the back button to close dialog boxes, return from modal | |
| /// pages, and so forth. | |
| /// | |
| /// This method exposes the `popRoute` notification from | |
| /// [SystemChannels.navigation]. | |
| /// | |
| /// {@template flutter.widgets.AndroidPredictiveBack} | |
| /// ## Handling backs ahead of time | |
| /// | |
| /// Not all system backs will result in a call to this method. Some are | |
| /// handled entirely by the system without informing the Flutter framework. | |
| /// | |
| /// Android API 33+ introduced a feature called predictive back, which allows | |
| /// the user to peek behind the current app or route during a back gesture and | |
| /// then decide to cancel or commit the back. Flutter enables or disables this | |
| /// feature ahead of time, before a back gesture occurs, and back gestures | |
| /// that trigger predictive back are handled entirely by the system and do not | |
| /// trigger this method here in the framework. | |
| /// | |
| /// By default, the framework communicates when it would like to handle system | |
| /// back gestures using [SystemNavigator.setFrameworkHandlesBack] in | |
| /// [WidgetsApp]. This is done automatically based on the status of the | |
| /// [Navigator] stack and the state of any [PopScope] widgets present. | |
| /// Developers can manually set this by calling the method directly or by | |
| /// using [NavigationNotification]. | |
| /// {@endtemplate} | |
| @protected | |
| @visibleForTesting | |
| Future<bool> handlePopRoute() async { | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| try { | |
| if (await observer.didPopRoute()) { | |
| return true; | |
| } | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.didPopRoute', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| unawaited( | |
| SystemNavigator.pop().catchError((Object exception, StackTrace stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription('while popping route'), | |
| ), | |
| ); | |
| }), | |
| ); | |
| return false; | |
| } | |
| // The observers that are currently handling an active predictive back gesture. | |
| final List<WidgetsBindingObserver> _backGestureObservers = <WidgetsBindingObserver>[]; | |
| bool _handleStartBackGesture(Map<String?, Object?> arguments) { | |
| _backGestureObservers.clear(); | |
| final backEvent = PredictiveBackEvent.fromMap(arguments); | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| try { | |
| if (observer.handleStartBackGesture(backEvent)) { | |
| _backGestureObservers.add(observer); | |
| } | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.handleStartBackGesture', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| return _backGestureObservers.isNotEmpty; | |
| } | |
| void _handleUpdateBackGestureProgress(Map<String?, Object?> arguments) { | |
| if (_backGestureObservers.isEmpty) { | |
| return; | |
| } | |
| final backEvent = PredictiveBackEvent.fromMap(arguments); | |
| for (final WidgetsBindingObserver observer in _backGestureObservers) { | |
| try { | |
| observer.handleUpdateBackGestureProgress(backEvent); | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.handleUpdateBackGestureProgress', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| Future<void> _handleCommitBackGesture() async { | |
| if (_backGestureObservers.isEmpty) { | |
| // If the predictive back was not handled, then the route should be popped | |
| // like a normal, non-predictive back. For example, this will happen if a | |
| // back gesture occurs but no predictive back route transition exists to | |
| // handle it. The back gesture should still cause normal pop even if it | |
| // doesn't cause a predictive transition. | |
| await handlePopRoute(); | |
| return; | |
| } | |
| for (final WidgetsBindingObserver observer in _backGestureObservers) { | |
| try { | |
| observer.handleCommitBackGesture(); | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.handleCommitBackGesture', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| void _handleCancelBackGesture() { | |
| for (final WidgetsBindingObserver observer in _backGestureObservers) { | |
| try { | |
| observer.handleCancelBackGesture(); | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.handleCancelBackGesture', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| /// Called when the host tells the app to push a new route onto the | |
| /// navigator. | |
| /// | |
| /// This notifies the binding observers (using | |
| /// [WidgetsBindingObserver.didPushRoute]), in registration order, until one | |
| /// returns true, meaning that it was able to handle the request (e.g. by | |
| /// opening a dialog box). If none return true, then nothing happens. | |
| /// | |
| /// This method exposes the `pushRoute` notification from | |
| /// [SystemChannels.navigation]. | |
| @protected | |
| @mustCallSuper | |
| @visibleForTesting | |
| Future<bool> handlePushRoute(String route) async { | |
| final routeInformation = RouteInformation(uri: Uri.parse(route)); | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| try { | |
| if (await observer.didPushRouteInformation(routeInformation)) { | |
| return true; | |
| } | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.didPushRouteInformation', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| return false; | |
| } | |
| Future<bool> _handlePushRouteInformation(Map<dynamic, dynamic> routeArguments) async { | |
| final routeInformation = RouteInformation( | |
| uri: Uri.parse(routeArguments['location'] as String), | |
| state: routeArguments['state'] as Object?, | |
| ); | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| try { | |
| if (await observer.didPushRouteInformation(routeInformation)) { | |
| return true; | |
| } | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.didPushRouteInformation', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| return false; | |
| } | |
| Future<bool> _handleNavigationInvocation(MethodCall methodCall) { | |
| return switch (methodCall.method) { | |
| 'popRoute' => handlePopRoute(), | |
| 'pushRoute' => handlePushRoute(methodCall.arguments as String), | |
| 'pushRouteInformation' => _handlePushRouteInformation( | |
| methodCall.arguments as Map<dynamic, dynamic>, | |
| ), | |
| // Return false for unhandled method. | |
| _ => Future<bool>.value(false), | |
| }; | |
| } | |
| Future<dynamic> _handleBackGestureInvocation(MethodCall methodCall) async { | |
| final Map<String?, Object?>? arguments = (methodCall.arguments as Map<Object?, Object?>?) | |
| ?.cast<String?, Object?>(); | |
| return switch (methodCall.method) { | |
| 'startBackGesture' => _handleStartBackGesture(arguments!), | |
| 'updateBackGestureProgress' => _handleUpdateBackGestureProgress(arguments!), | |
| 'commitBackGesture' => _handleCommitBackGesture(), | |
| 'cancelBackGesture' => _handleCancelBackGesture(), | |
| _ => throw MissingPluginException(), | |
| }; | |
| } | |
| @override | |
| void handleAppLifecycleStateChanged(AppLifecycleState state) { | |
| super.handleAppLifecycleStateChanged(state); | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| try { | |
| observer.didChangeAppLifecycleState(state); | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.didChangeAppLifecycleState', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| @override | |
| void handleViewFocusChanged(ViewFocusEvent event) { | |
| super.handleViewFocusChanged(event); | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| try { | |
| observer.didChangeViewFocus(event); | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.didChangeViewFocus', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| @override | |
| void handleMemoryPressure() { | |
| super.handleMemoryPressure(); | |
| for (final observer in List<WidgetsBindingObserver>.of(_observers)) { | |
| try { | |
| observer.didHaveMemoryPressure(); | |
| } catch (exception, stack) { | |
| FlutterError.reportError( | |
| FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription( | |
| 'while dispatching notifications for WidgetsBindingObserver.didHaveMemoryPressure', | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| } | |
| bool _needToReportFirstFrame = true; | |
| final Completer<void> _firstFrameCompleter = Completer<void>(); | |
| /// Whether the Flutter engine has rasterized the first frame. | |
| /// | |
| /// Usually, the time that a frame is rasterized is very close to the time that | |
| /// it gets presented on the display. Specifically, rasterization is the last | |
| /// expensive phase of a frame that's still in Flutter's control. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [waitUntilFirstFrameRasterized], the future when [firstFrameRasterized] | |
| /// becomes true. | |
| bool get firstFrameRasterized => _firstFrameCompleter.isCompleted; | |
| /// A future that completes when the Flutter engine has rasterized the first | |
| /// frame. | |
| /// | |
| /// Usually, the time that a frame is rasterized is very close to the time that | |
| /// it gets presented on the display. Specifically, rasterization is the last | |
| /// expensive phase of a frame that's still in Flutter's control. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [firstFrameRasterized], whether this future has completed or not. | |
| Future<void> get waitUntilFirstFrameRasterized => _firstFrameCompleter.future; | |
| /// Whether the first frame has finished building. | |
| /// | |
| /// This value can also be obtained over the VM service protocol as | |
| /// `ext.flutter.didSendFirstFrameEvent`. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [firstFrameRasterized], whether the first frame has finished rendering. | |
| bool get debugDidSendFirstFrameEvent => !_needToReportFirstFrame; | |
| void _handleBuildScheduled() { | |
| // If we're in the process of building dirty elements, then changes | |
| // should not trigger a new frame. | |
| assert(() { | |
| if (debugBuildingDirtyElements) { | |
| throw FlutterError.fromParts(<DiagnosticsNode>[ | |
| ErrorSummary('Build scheduled during frame.'), | |
| ErrorDescription( | |
| 'While the widget tree was being built, laid out, and painted, ' | |
| 'a new frame was scheduled to rebuild the widget tree.', | |
| ), | |
| ErrorHint( | |
| 'This might be because setState() was called from a layout or ' | |
| 'paint callback. ' | |
| 'If a change is needed to the widget tree, it should be applied ' | |
| 'as the tree is being built. Scheduling a change for the subsequent ' | |
| 'frame instead results in an interface that lags behind by one frame. ' | |
| 'If this was done to make your build dependent on a size measured at ' | |
| 'layout time, consider using a LayoutBuilder, CustomSingleChildLayout, ' | |
| 'or CustomMultiChildLayout. If, on the other hand, the one frame delay ' | |
| 'is the desired effect, for example because this is an ' | |
| 'animation, consider scheduling the frame in a post-frame callback ' | |
| 'using SchedulerBinding.addPostFrameCallback or ' | |
| 'using an AnimationController to trigger the animation.', | |
| ), | |
| ]); | |
| } | |
| return true; | |
| }()); | |
| ensureVisualUpdate(); | |
| } | |
| /// Whether we are currently in a frame. This is used to verify | |
| /// that frames are not scheduled redundantly. | |
| /// | |
| /// This is public so that test frameworks can change it. | |
| /// | |
| /// This flag is not used in release builds. | |
| @protected | |
| bool debugBuildingDirtyElements = false; | |
| /// Pump the build and rendering pipeline to generate a frame. | |
| /// | |
| /// This method is called by [handleDrawFrame], which itself is called | |
| /// automatically by the engine when it is time to lay out and paint a | |
| /// frame. | |
| /// | |
| /// Each frame consists of the following phases: | |
| /// | |
| /// 1. The animation phase: The [handleBeginFrame] method, which is registered | |
| /// with [PlatformDispatcher.onBeginFrame], invokes all the transient frame | |
| /// callbacks registered with [scheduleFrameCallback], in registration order. | |
| /// This includes all the [Ticker] instances that are driving | |
| /// [AnimationController] objects, which means all of the active [Animation] | |
| /// objects tick at this point. | |
| /// | |
| /// 2. Microtasks: After [handleBeginFrame] returns, any microtasks that got | |
| /// scheduled by transient frame callbacks get to run. This typically includes | |
| /// callbacks for futures from [Ticker]s and [AnimationController]s that | |
| /// completed this frame. | |
| /// | |
| /// After [handleBeginFrame], [handleDrawFrame], which is registered with | |
| /// [PlatformDispatcher.onDrawFrame], is called, which invokes all the | |
| /// persistent frame callbacks, of which the most notable is this method, | |
| /// [drawFrame], which proceeds as follows: | |
| /// | |
| /// 3. The build phase: All the dirty [Element]s in the widget tree are | |
| /// rebuilt (see [State.build]). See [State.setState] for further details on | |
| /// marking a widget dirty for building. See [BuildOwner] for more information | |
| /// on this step. | |
| /// | |
| /// 4. The layout phase: All the dirty [RenderObject]s in the system are laid | |
| /// out (see [RenderObject.performLayout]). See [RenderObject.markNeedsLayout] | |
| /// for further details on marking an object dirty for layout. | |
| /// | |
| /// 5. The compositing bits phase: The compositing bits on any dirty | |
| /// [RenderObject] objects are updated. See | |
| /// [RenderObject.markNeedsCompositingBitsUpdate]. | |
| /// | |
| /// 6. The paint phase: All the dirty [RenderObject]s in the system are | |
| /// repainted (see [RenderObject.paint]). This generates the [Layer] tree. See | |
| /// [RenderObject.markNeedsPaint] for further details on marking an object | |
| /// dirty for paint. | |
| /// | |
| /// 7. The compositing phase: The layer tree is turned into a [Scene] and | |
| /// sent to the GPU. | |
| /// | |
| /// 8. The semantics phase: All the dirty [RenderObject]s in the system have | |
| /// their semantics updated (see [RenderObject.assembleSemanticsNode]). This | |
| /// generates the [SemanticsNode] tree. See | |
| /// [RenderObject.markNeedsSemanticsUpdate] for further details on marking an | |
| /// object dirty for semantics. | |
| /// | |
| /// For more details on steps 4-8, see [PipelineOwner]. | |
| /// | |
| /// 9. The finalization phase in the widgets layer: The widgets tree is | |
| /// finalized. This causes [State.dispose] to be invoked on any objects that | |
| /// were removed from the widgets tree this frame. See | |
| /// [BuildOwner.finalizeTree] for more details. | |
| /// | |
| /// 10. The finalization phase in the scheduler layer: After [drawFrame] | |
| /// returns, [handleDrawFrame] then invokes post-frame callbacks (registered | |
| /// with [addPostFrameCallback]). | |
| // | |
| // When editing the above, also update rendering/binding.dart's copy. | |
| @override | |
| void drawFrame() { | |
| assert(!debugBuildingDirtyElements); | |
| assert(() { | |
| debugBuildingDirtyElements = true; | |
| return true; | |
| }()); | |
| TimingsCallback? firstFrameCallback; | |
| var debugFrameWasSentToEngine = false; | |
| if (_needToReportFirstFrame) { | |
| assert(!_firstFrameCompleter.isCompleted); | |
| firstFrameCallback = (List<FrameTiming> timings) { | |
| assert(debugFrameWasSentToEngine); | |
| if (!kReleaseMode) { | |
| // Change the current user tag back to the default tag. At this point, | |
| // the user tag should be set to "AppStartUp" (originally set in the | |
| // engine), so we need to change it back to the default tag to mark | |
| // the end of app start up for CPU profiles. | |
| developer.UserTag.defaultTag.makeCurrent(); | |
| developer.Timeline.instantSync('Rasterized first useful frame'); | |
| developer.postEvent('Flutter.FirstFrame', <String, dynamic>{}); | |
| } | |
| SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback!); | |
| firstFrameCallback = null; | |
| _firstFrameCompleter.complete(); | |
| }; | |
| // Callback is only invoked when FlutterView.render is called. When | |
| // sendFramesToEngine is set to false during the frame, it will not be | |
| // called and we need to remove the callback (see below). | |
| SchedulerBinding.instance.addTimingsCallback(firstFrameCallback!); | |
| } | |
| try { | |
| if (rootElement != null) { | |
| buildOwner!.buildScope(rootElement!); | |
| } | |
| super.drawFrame(); | |
| assert(() { | |
| debugFrameWasSentToEngine = sendFramesToEngine; | |
| return true; | |
| }()); | |
| buildOwner!.finalizeTree(); | |
| } finally { | |
| assert(() { | |
| debugBuildingDirtyElements = false; | |
| return true; | |
| }()); | |
| } | |
| if (!kReleaseMode) { | |
| if (_needToReportFirstFrame && sendFramesToEngine) { | |
| developer.Timeline.instantSync('Widgets built first useful frame'); | |
| } | |
| } | |
| _needToReportFirstFrame = false; | |
| if (firstFrameCallback != null && !sendFramesToEngine) { | |
| // This frame is deferred and not the first frame sent to the engine that | |
| // should be reported. | |
| _needToReportFirstFrame = true; | |
| SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback!); | |
| } | |
| } | |
| /// The [Element] that is at the root of the element tree hierarchy. | |
| /// | |
| /// This is initialized the first time [runApp] is called. | |
| Element? get rootElement => _rootElement; | |
| Element? _rootElement; | |
| /// Deprecated. Will be removed in a future version of Flutter. | |
| /// | |
| /// Use [rootElement] instead. | |
| @Deprecated( | |
| 'Use rootElement instead. ' | |
| 'This feature was deprecated after v3.9.0-16.0.pre.', | |
| ) | |
| Element? get renderViewElement => rootElement; | |
| bool _readyToProduceFrames = false; | |
| @override | |
| bool get framesEnabled => super.framesEnabled && _readyToProduceFrames; | |
| /// Used by [runApp] to wrap the provided `rootWidget` in the default [View]. | |
| /// | |
| /// The [View] determines into what [FlutterView] the app is rendered into. | |
| /// This is currently [PlatformDispatcher.implicitView] from [platformDispatcher]. | |
| /// This method will throw a [StateError] if the [PlatformDispatcher.implicitView] | |
| /// is null. | |
| /// | |
| /// The `rootWidget` widget provided to this method must not already be | |
| /// wrapped in a [View]. | |
| Widget wrapWithDefaultView(Widget rootWidget) { | |
| if (platformDispatcher.implicitView == null) { | |
| throw StateError( | |
| 'The app requested a view, but the platform did not provide one.\n' | |
| 'This is likely because the app called `runApp` to render its root ' | |
| 'widget, which expects the platform to provide a default view to ' | |
| 'render into (the "implicit" view).\n' | |
| 'However, the platform likely has multi-view mode enabled, which does ' | |
| 'not create this default "implicit" view.\n' | |
| 'Try using `runWidget` instead of `runApp` to start your app.\n' | |
| '`runWidget` allows you to provide a `View` widget, without requiring ' | |
| 'a default view.' | |
| '${kIsWeb ? "\nSee: https://flutter.dev/to/web-multiview-runwidget" : ""}', | |
| ); | |
| } | |
| return View( | |
| view: platformDispatcher.implicitView!, | |
| deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner: pipelineOwner, | |
| deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView: renderView, | |
| child: rootWidget, | |
| ); | |
| } | |
| /// Schedules a [Timer] for attaching the root widget. | |
| /// | |
| /// This is called by [runApp] to configure the widget tree. Consider using | |
| /// [attachRootWidget] if you want to build the widget tree synchronously. | |
| @protected | |
| void scheduleAttachRootWidget(Widget rootWidget) { | |
| Timer.run(() { | |
| attachRootWidget(rootWidget); | |
| }); | |
| } | |
| /// Takes a widget and attaches it to the [rootElement], creating it if | |
| /// necessary. | |
| /// | |
| /// This is called by [runApp] to configure the widget tree. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [RenderObjectToWidgetAdapter.attachToRenderTree], which inflates a | |
| /// widget and attaches it to the render tree. | |
| void attachRootWidget(Widget rootWidget) { | |
| attachToBuildOwner(RootWidget(debugShortDescription: '[root]', child: rootWidget)); | |
| } | |
| /// Called by [attachRootWidget] to attach the provided [RootWidget] to the | |
| /// [buildOwner]. | |
| /// | |
| /// This creates the [rootElement], if necessary, or re-uses an existing one. | |
| /// | |
| /// This method is rarely called directly, but it can be useful in tests to | |
| /// restore the element tree to a previous version by providing the | |
| /// [RootWidget] of that version (see [WidgetTester.restartAndRestore] for an | |
| /// exemplary use case). | |
| void attachToBuildOwner(RootWidget widget) { | |
| final isBootstrapFrame = rootElement == null; | |
| _readyToProduceFrames = true; | |
| _rootElement = widget.attach(buildOwner!, rootElement as RootElement?); | |
| if (isBootstrapFrame) { | |
| SchedulerBinding.instance.ensureVisualUpdate(); | |
| } | |
| } | |
| /// Whether the [rootElement] has been initialized. | |
| /// | |
| /// This will be false until [runApp] is called (or [WidgetTester.pumpWidget] | |
| /// is called in the context of a [TestWidgetsFlutterBinding]). | |
| bool get isRootWidgetAttached => _rootElement != null; | |
| @override | |
| Future<void> performReassemble() { | |
| assert(() { | |
| WidgetInspectorService.instance.performReassemble(); | |
| return true; | |
| }()); | |
| if (rootElement != null) { | |
| buildOwner!.reassemble(rootElement!); | |
| } | |
| return super.performReassemble(); | |
| } | |
| /// Computes the locale the current platform would resolve to. | |
| /// | |
| /// This method is meant to be used as part of a | |
| /// [WidgetsApp.localeListResolutionCallback]. Since this method may return | |
| /// null, a Flutter/dart algorithm should still be provided as a fallback in | |
| /// case a native resolved locale cannot be determined or if the native | |
| /// resolved locale is undesirable. | |
| /// | |
| /// This method may return a null [Locale] if the platform does not support | |
| /// native locale resolution, or if the resolution failed. | |
| /// | |
| /// The first `supportedLocale` is treated as the default locale and will be returned | |
| /// if no better match is found. | |
| /// | |
| /// Android and iOS are currently supported. | |
| /// | |
| /// On Android, the algorithm described in | |
| /// https://developer.android.com/guide/topics/resources/multilingual-support | |
| /// is used to determine the resolved locale. Depending on the android version | |
| /// of the device, either the modern (>= API 24) or legacy (< API 24) algorithm | |
| /// will be used. | |
| /// | |
| /// On iOS, the result of `preferredLocalizationsFromArray` method of `NSBundle` | |
| /// is returned. See: | |
| /// https://developer.apple.com/documentation/foundation/nsbundle/1417249-preferredlocalizationsfromarray?language=objc | |
| /// for details on the used method. | |
| /// | |
| /// iOS treats script code as necessary for a match, so a user preferred locale of | |
| /// `zh_Hans_CN` will not resolve to a supported locale of `zh_CN`. | |
| /// | |
| /// Since implementation may vary by platform and has potential to be heavy, | |
| /// it is recommended to cache the results of this method if the value is | |
| /// used multiple times. | |
| /// | |
| /// Second-best (and n-best) matching locales should be obtained by calling this | |
| /// method again with the matched locale of the first call omitted from | |
| /// `supportedLocales`. | |
| Locale? computePlatformResolvedLocale(List<Locale> supportedLocales) { | |
| return platformDispatcher.computePlatformResolvedLocale(supportedLocales); | |
| } | |
| /// The [WindowingOwner] is responsible for creating and managing [BaseWindowController]s. | |
| /// | |
| /// The default [WindowingOwner] supports macOS, Linux, and Windows. | |
| /// | |
| /// A custom [WindowingOwner] can be provided by the setter. | |
| /// | |
| /// {@template flutter.widgets.binding.window.experimental} | |
| /// Do not use this API in production applications or packages published to | |
| /// pub.dev. Flutter will make breaking changes to this API, even in patch | |
| /// versions. | |
| /// | |
| /// This API throws an [UnsupportedError] error unless Flutter’s windowing | |
| /// feature is enabled by [isWindowingEnabled]. | |
| /// | |
| /// See: https://github.com/flutter/flutter/issues/30701. | |
| /// {@endtemplate} | |
| @internal | |
| WindowingOwner get windowingOwner { | |
| if (!isWindowingEnabled) { | |
| throw UnsupportedError(''' | |
| Windowing APIs are not enabled. | |
| Windowing APIs are currently experimental. Do not use windowing APIs in | |
| production applications or plugins published to pub.dev. | |
| To try experimental windowing APIs: | |
| 1. Switch to Flutter's main release channel. | |
| 2. Turn on the windowing feature flag. | |
| See: https://github.com/flutter/flutter/issues/30701. | |
| '''); | |
| } | |
| return _windowingOwner; | |
| } | |
| /// Sets the [WindowingOwner]. | |
| /// | |
| /// The default [WindowingOwner] supports macOS, Linux, and Windows. | |
| /// | |
| /// This setter can be used to provide a custom [WindowingOwner]. | |
| /// | |
| /// {@macro flutter.widgets.binding.window.experimental} | |
| @internal | |
| set windowingOwner(WindowingOwner owner) { | |
| if (!isWindowingEnabled) { | |
| throw UnsupportedError(''' | |
| Windowing APIs are not enabled. | |
| Windowing APIs are currently experimental. Do not use windowing APIs in | |
| production applications or plugins published to pub.dev. | |
| To try experimental windowing APIs: | |
| 1. Switch to Flutter's main release channel. | |
| 2. Turn on the windowing feature flag. | |
| See: https://github.com/flutter/flutter/issues/30701. | |
| '''); | |
| } | |
| _windowingOwner = owner; | |
| } | |
| late WindowingOwner _windowingOwner; | |
| } | |
| /// Inflate the given widget and attach it to the view. | |
| /// | |
| // TODO(goderbauer): Update the paragraph below to include the Window widget once that exists. | |
| /// The [runApp] method renders the provided `app` widget into the | |
| /// [PlatformDispatcher.implicitView] by wrapping it in a [View] widget, which | |
| /// will bootstrap the render tree for the app. Apps that want to control which | |
| /// [FlutterView] they render into can use [runWidget] instead. | |
| /// | |
| /// The widget is given constraints during layout that force it to fill the | |
| /// entire view. If you wish to align your widget to one side of the view | |
| /// (e.g., the top), consider using the [Align] widget. If you wish to center | |
| /// your widget, you can also use the [Center] widget. | |
| /// | |
| /// Calling [runApp] again will detach the previous root widget from the view | |
| /// and attach the given widget in its place. The new widget tree is compared | |
| /// against the previous widget tree and any differences are applied to the | |
| /// underlying render tree, similar to what happens when a [StatefulWidget] | |
| /// rebuilds after calling [State.setState]. | |
| /// | |
| /// Initializes the binding using [WidgetsFlutterBinding] if necessary. | |
| /// | |
| /// {@template flutter.widgets.runApp.shutdown} | |
| /// ## Application shutdown | |
| /// | |
| /// This widget tree is not torn down when the application shuts down, because | |
| /// there is no way to predict when that will happen. For example, a user could | |
| /// physically remove power from their device, or the application could crash | |
| /// unexpectedly, or the malware on the device could forcibly terminate the | |
| /// process. | |
| /// | |
| /// Applications are responsible for ensuring that they are well-behaved | |
| /// even in the face of a rapid unscheduled termination. | |
| /// | |
| /// To listen for platform shutdown messages (and other lifecycle changes), | |
| /// consider the [AppLifecycleListener] API. | |
| /// {@endtemplate} | |
| /// | |
| /// To artificially cause the entire widget tree to be disposed, consider | |
| /// calling [runApp] with a widget such as [SizedBox.shrink]. | |
| /// | |
| /// {@template flutter.widgets.runApp.dismissal} | |
| /// ## Dismissing Flutter UI via platform native methods | |
| /// | |
| /// An application may have both Flutter and non-Flutter UI in it. If the | |
| /// application calls non-Flutter methods to remove Flutter based UI such as | |
| /// platform native API to manipulate the platform native navigation stack, | |
| /// the framework does not know if the developer intends to eagerly free | |
| /// resources or not. The widget tree remains mounted and ready to render | |
| /// as soon as it is displayed again. | |
| /// {@endtemplate} | |
| /// | |
| /// To release resources more eagerly, establish a [platform channel](https://flutter.dev/to/platform-channels) | |
| /// and use it to call [runApp] with a widget such as [SizedBox.shrink] when | |
| /// the framework should dispose of the active widget tree. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [runWidget], which bootstraps a widget tree without assuming the | |
| /// [FlutterView] into which it will be rendered. | |
| /// * [WidgetsBinding.attachRootWidget], which creates the root widget for the | |
| /// widget hierarchy. | |
| /// * [RenderObjectToWidgetAdapter.attachToRenderTree], which creates the root | |
| /// element for the element hierarchy. | |
| /// * [WidgetsBinding.handleBeginFrame], which pumps the widget pipeline to | |
| /// ensure the widget, element, and render trees are all built. | |
| void runApp(Widget app) { | |
| final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); | |
| _runWidget(binding.wrapWithDefaultView(app), binding, 'runApp'); | |
| } | |
| /// Inflate the given widget and bootstrap the widget tree. | |
| /// | |
| // TODO(goderbauer): Update the paragraph below to include the Window widget once that exists. | |
| /// Unlike [runApp], this method does not define a [FlutterView] into which the | |
| /// provided `app` widget is rendered into. It is up to the caller to include at | |
| /// least one [View] widget in the provided `app` widget that will bootstrap a | |
| /// render tree and define the [FlutterView] into which content is rendered. | |
| /// [RenderObjectWidget]s without an ancestor [View] widget will result in an | |
| /// exception. Apps that want to render into the default view without dealing | |
| /// with view management should consider calling [runApp] instead. | |
| /// | |
| /// {@tool snippet} | |
| /// The sample shows how to utilize [runWidget] to specify the [FlutterView] | |
| /// into which the `MyApp` widget will be drawn: | |
| /// | |
| /// ```dart | |
| /// runWidget( | |
| /// View( | |
| /// view: myFlutterView, | |
| /// child: const MyApp(), | |
| /// ), | |
| /// ); | |
| /// ``` | |
| /// {@end-tool} | |
| /// | |
| /// Calling [runWidget] again will detach the previous root widget and attach | |
| /// the given widget in its place. The new widget tree is compared against the | |
| /// previous widget tree and any differences are applied to the underlying | |
| /// render tree, similar to what happens when a [StatefulWidget] rebuilds after | |
| /// calling [State.setState]. | |
| /// | |
| /// Initializes the binding using [WidgetsFlutterBinding] if necessary. | |
| /// | |
| /// {@macro flutter.widgets.runApp.shutdown} | |
| /// | |
| /// To artificially cause the entire widget tree to be disposed, consider | |
| /// calling [runWidget] with a [ViewCollection] that does not specify any | |
| /// [ViewCollection.views]. | |
| /// | |
| /// {@macro flutter.widgets.runApp.dismissal} | |
| /// | |
| /// To release resources more eagerly, establish a [platform channel](https://flutter.dev/to/platform-channels) | |
| /// and use it to remove the [View] whose widget resources should be released | |
| /// from the `app` widget tree provided to [runWidget]. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [runApp], which bootstraps a widget tree and renders it into a default | |
| /// [FlutterView]. | |
| /// * [WidgetsBinding.attachRootWidget], which creates the root widget for the | |
| /// widget hierarchy. | |
| /// * [RenderObjectToWidgetAdapter.attachToRenderTree], which creates the root | |
| /// element for the element hierarchy. | |
| /// * [WidgetsBinding.handleBeginFrame], which pumps the widget pipeline to | |
| /// ensure the widget, element, and render trees are all built. | |
| void runWidget(Widget app) { | |
| final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); | |
| _runWidget(app, binding, 'runWidget'); | |
| } | |
| void _runWidget(Widget app, WidgetsBinding binding, String debugEntryPoint) { | |
| assert(binding.debugCheckZone(debugEntryPoint)); | |
| binding | |
| ..scheduleAttachRootWidget(app) | |
| ..scheduleWarmUpFrame(); | |
| } | |
| String _debugDumpAppString() { | |
| const mode = kDebugMode | |
| ? 'DEBUG MODE' | |
| : kReleaseMode | |
| ? 'RELEASE MODE' | |
| : 'PROFILE MODE'; | |
| final buffer = StringBuffer(); | |
| buffer.writeln('${WidgetsBinding.instance.runtimeType} - $mode'); | |
| if (WidgetsBinding.instance.rootElement != null) { | |
| buffer.writeln(WidgetsBinding.instance.rootElement!.toStringDeep()); | |
| } else { | |
| buffer.writeln('<no tree currently mounted>'); | |
| } | |
| return buffer.toString(); | |
| } | |
| /// Print a string representation of the currently running app. | |
| void debugDumpApp() { | |
| debugPrint(_debugDumpAppString()); | |
| } | |
| /// A widget for the root of the widget tree. | |
| /// | |
| /// Exposes an [attach] method to attach the widget tree to a [BuildOwner]. That | |
| /// method also bootstraps the element tree. | |
| /// | |
| /// Used by [WidgetsBinding.attachRootWidget] (which is indirectly called by | |
| /// [runApp]) to bootstrap applications. | |
| class RootWidget extends Widget { | |
| /// Creates a [RootWidget]. | |
| const RootWidget({super.key, this.child, this.debugShortDescription}); | |
| /// The widget below this widget in the tree. | |
| /// | |
| /// {@macro flutter.widgets.ProxyWidget.child} | |
| final Widget? child; | |
| /// A short description of this widget used by debugging aids. | |
| final String? debugShortDescription; | |
| @override | |
| RootElement createElement() => RootElement(this); | |
| /// Inflate this widget and attaches it to the provided [BuildOwner]. | |
| /// | |
| /// If `element` is null, this function will create a new element. Otherwise, | |
| /// the given element will have an update scheduled to switch to this widget. | |
| /// | |
| /// Used by [WidgetsBinding.attachToBuildOwner] (which is indirectly called by | |
| /// [runApp]) to bootstrap applications. | |
| RootElement attach(BuildOwner owner, [RootElement? element]) { | |
| if (element == null) { | |
| owner.lockState(() { | |
| element = createElement(); | |
| assert(element != null); | |
| element!.assignOwner(owner); | |
| }); | |
| owner.buildScope(element!, () { | |
| element!.mount(/* parent */ null, /* slot */ null); | |
| }); | |
| } else { | |
| element._newWidget = this; | |
| element.markNeedsBuild(); | |
| } | |
| return element!; | |
| } | |
| @override | |
| String toStringShort() => debugShortDescription ?? super.toStringShort(); | |
| } | |
| /// The root of the element tree. | |
| /// | |
| /// This element class is the instantiation of a [RootWidget]. It can be used | |
| /// only as the root of an [Element] tree (it cannot be mounted into another | |
| /// [Element]; its parent must be null). | |
| /// | |
| /// In typical usage, it will be instantiated for a [RootWidget] by calling | |
| /// [RootWidget.attach]. In this usage, it is normally instantiated by the | |
| /// bootstrapping logic in the [WidgetsFlutterBinding] singleton created by | |
| /// [runApp]. | |
| class RootElement extends Element with RootElementMixin { | |
| /// Creates a [RootElement] for the provided [RootWidget]. | |
| RootElement(RootWidget super.widget); | |
| Element? _child; | |
| @override | |
| void visitChildren(ElementVisitor visitor) { | |
| if (_child != null) { | |
| visitor(_child!); | |
| } | |
| } | |
| @override | |
| void forgetChild(Element child) { | |
| assert(child == _child); | |
| _child = null; | |
| super.forgetChild(child); | |
| } | |
| @override | |
| void mount(Element? parent, Object? newSlot) { | |
| assert(parent == null); // We are the root! | |
| super.mount(parent, newSlot); | |
| _rebuild(); | |
| assert(_child != null); | |
| super.performRebuild(); // clears the "dirty" flag | |
| } | |
| @override | |
| void update(RootWidget newWidget) { | |
| super.update(newWidget); | |
| assert(widget == newWidget); | |
| _rebuild(); | |
| } | |
| // When we are assigned a new widget, we store it here | |
| // until we are ready to update to it. | |
| RootWidget? _newWidget; | |
| @override | |
| void performRebuild() { | |
| if (_newWidget != null) { | |
| // _newWidget can be null if, for instance, we were rebuilt | |
| // due to a reassemble. | |
| final RootWidget newWidget = _newWidget!; | |
| _newWidget = null; | |
| update(newWidget); | |
| } | |
| super.performRebuild(); | |
| assert(_newWidget == null); | |
| } | |
| void _rebuild() { | |
| try { | |
| _child = updateChild(_child, (widget as RootWidget).child, /* slot */ null); | |
| } catch (exception, stack) { | |
| final details = FlutterErrorDetails( | |
| exception: exception, | |
| stack: stack, | |
| library: 'widgets library', | |
| context: ErrorDescription('attaching to the render tree'), | |
| ); | |
| FlutterError.reportError(details); | |
| // No error widget possible here since it wouldn't have a view to render into. | |
| _child = null; | |
| } | |
| } | |
| @override | |
| bool get debugDoingBuild => false; // This element doesn't have a build phase. | |
| @override | |
| // There is no ancestor RenderObjectElement that the render object could be attached to. | |
| bool debugExpectsRenderObjectForSlot(Object? slot) => false; | |
| } | |
| /// A concrete binding for applications based on the Widgets framework. | |
| /// | |
| /// This is the glue that binds the framework to the Flutter engine. | |
| /// | |
| /// When using the widgets framework, this binding, or one that | |
| /// implements the same interfaces, must be used. The following | |
| /// mixins are used to implement this binding: | |
| /// | |
| /// * [GestureBinding], which implements the basics of hit testing. | |
| /// * [SchedulerBinding], which introduces the concepts of frames. | |
| /// * [ServicesBinding], which provides access to the plugin subsystem. | |
| /// * [PaintingBinding], which enables decoding images. | |
| /// * [SemanticsBinding], which supports accessibility. | |
| /// * [RendererBinding], which handles the render tree. | |
| /// * [WidgetsBinding], which handles the widget tree. | |
| class WidgetsFlutterBinding extends BindingBase | |
| with | |
| GestureBinding, | |
| SchedulerBinding, | |
| ServicesBinding, | |
| PaintingBinding, | |
| SemanticsBinding, | |
| RendererBinding, | |
| WidgetsBinding { | |
| /// Returns an instance of the binding that implements | |
| /// [WidgetsBinding]. If no binding has yet been initialized, the | |
| /// [WidgetsFlutterBinding] class is used to create and initialize | |
| /// one. | |
| /// | |
| /// You only need to call this method if you need the binding to be | |
| /// initialized before calling [runApp]. | |
| /// | |
| /// In the `flutter_test` framework, [testWidgets] initializes the | |
| /// binding instance to a [TestWidgetsFlutterBinding], not a | |
| /// [WidgetsFlutterBinding]. See | |
| /// [TestWidgetsFlutterBinding.ensureInitialized]. | |
| static WidgetsBinding ensureInitialized() { | |
| if (WidgetsBinding._instance == null) { | |
| WidgetsFlutterBinding(); | |
| } | |
| return WidgetsBinding.instance; | |
| } | |
| } |