mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
## Description This updates the documentation for `MediaQuery` and `Overlay` to include the following: - That `MediaQueryData.size` is set asynchronously, and doesn't necessarily reflect the size of the current frame. - That the specific `MediaQuery.sizeOf` and related methods are preferred over the more generic `MediaQuery.of`. - That the size of the `Overlay` isn't necessarily the size returned by `MediaQueryData.size` As well as adding some symbol links, and fixing some error in documentation. ## Tests - Documentation only
1397 lines
53 KiB
Dart
1397 lines
53 KiB
Dart
// 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 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.
|
|
///
|
|
/// 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;
|
|
}
|
|
}
|