// 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. import 'dart:async'; import 'dart:developer' as developer; import 'dart:ui' show AccessibilityFeatures, AppExitResponse, AppLifecycleState, FrameTiming, Locale, PlatformDispatcher, TimingsCallback; 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 '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; /// 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 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 { /// @override /// void initState() { /// super.initState(); /// WidgetsBinding.instance.addObserver(this); /// } /// /// @override /// void dispose() { /// WidgetsBinding.instance.removeObserver(this); /// super.dispose(); /// } /// /// late double _lastTextScaleFactor; /// /// @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 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); assert(() { FlutterErrorDetails.propertiesTransformers.add(debugTransformDebugCreator); return true; }()); platformMenuDelegate = DefaultPlatformMenuDelegate(); } /// 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; void _debugAddStackFilters() { const PartialStackFrame elementInflateWidget = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'Element', method: 'inflateWidget'); const PartialStackFrame elementUpdateChild = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'Element', method: 'updateChild'); const PartialStackFrame elementRebuild = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'Element', method: 'rebuild'); const PartialStackFrame componentElementPerformRebuild = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'ComponentElement', method: 'performRebuild'); const PartialStackFrame componentElementFirstBuild = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'ComponentElement', method: '_firstBuild'); const PartialStackFrame componentElementMount = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'ComponentElement', method: 'mount'); const PartialStackFrame statefulElementFirstBuild = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'StatefulElement', method: '_firstBuild'); const PartialStackFrame singleChildMount = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'SingleChildRenderObjectElement', method: 'mount'); const PartialStackFrame statefulElementRebuild = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'StatefulElement', method: 'performRebuild'); const String 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; } ); } 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(); }, ); WidgetInspectorService.instance.initServiceExtensions(registerServiceExtension); return true; }()); } 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) => _observers.remove(observer); @override Future<AppExitResponse> handleRequestAppExit() async { bool didCancel = false; for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) { 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. } } return didCancel ? AppExitResponse.cancel : AppExitResponse.exit; } @override void handleMetricsChanged() { super.handleMetricsChanged(); for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) { observer.didChangeMetrics(); } } @override void handleTextScaleFactorChanged() { super.handleTextScaleFactorChanged(); for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) { observer.didChangeTextScaleFactor(); } } @override void handlePlatformBrightnessChanged() { super.handlePlatformBrightnessChanged(); for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) { observer.didChangePlatformBrightness(); } } @override void handleAccessibilityFeaturesChanged() { super.handleAccessibilityFeaturesChanged(); for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) { observer.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 WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) { observer.didChangeLocales(locales); } } /// 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 WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) { observer.didChangeAccessibilityFeatures(); } } /// 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<void> handlePopRoute() async { for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) { if (await observer.didPopRoute()) { return; } } SystemNavigator.pop(); } /// 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<void> handlePushRoute(String route) async { final RouteInformation routeInformation = RouteInformation(uri: Uri.parse(route)); for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) { if (await observer.didPushRouteInformation(routeInformation)) { return; } } } Future<void> _handlePushRouteInformation(Map<dynamic, dynamic> routeArguments) async { final RouteInformation routeInformation = RouteInformation( uri: Uri.parse(routeArguments['location'] as String), state: routeArguments['state'] as Object?, ); for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) { if (await observer.didPushRouteInformation(routeInformation)) { return; } } } Future<dynamic> _handleNavigationInvocation(MethodCall methodCall) { switch (methodCall.method) { case 'popRoute': return handlePopRoute(); case 'pushRoute': return handlePushRoute(methodCall.arguments as String); case 'pushRouteInformation': return _handlePushRouteInformation(methodCall.arguments as Map<dynamic, dynamic>); } return Future<dynamic>.value(); } @override void handleAppLifecycleStateChanged(AppLifecycleState state) { super.handleAppLifecycleStateChanged(state); for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) { observer.didChangeAppLifecycleState(state); } } @override void handleMemoryPressure() { super.handleMemoryPressure(); for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.of(_observers)) { observer.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; if (_needToReportFirstFrame) { assert(!_firstFrameCompleter.isCompleted); firstFrameCallback = (List<FrameTiming> timings) { assert(sendFramesToEngine); 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(); 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]. /// /// The `rootWidget` widget provided to this method must not already be /// wrapped in a [View]. Widget wrapWithDefaultView(Widget rootWidget) { 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 bool 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); } } /// Inflate the given widget and attach it to the screen. /// /// The widget is given constraints during layout that force it to fill the /// entire screen. If you wish to align your widget to one side of the screen /// (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 screen /// 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. /// /// ## 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 artificially cause the entire widget tree to be disposed, consider /// calling [runApp] with a widget such as [SizedBox.shrink]. /// /// To listen for platform shutdown messages (and other lifecycle changes), /// consider the [AppLifecycleListener] API. /// /// ## Dismissing Flutter UI via platform native methods /// /// {@template flutter.widgets.runApp.dismissal} /// 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. /// /// To release resources more eagerly, establish a [platform channel](https://flutter.dev/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. /// {@endtemplate} /// /// See also: /// /// * [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(); assert(binding.debugCheckZone('runApp')); binding ..scheduleAttachRootWidget(binding.wrapWithDefaultView(app)) ..scheduleWarmUpFrame(); } String _debugDumpAppString() { const String mode = kDebugMode ? 'DEBUG MODE' : kReleaseMode ? 'RELEASE MODE' : 'PROFILE MODE'; final StringBuffer 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 FlutterErrorDetails 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; } }