Override MediaQuery for WidgetsApp (#81295)
diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart
index 7947faa..a4489ef 100644
--- a/packages/flutter/lib/src/widgets/app.dart
+++ b/packages/flutter/lib/src/widgets/app.dart
@@ -236,6 +236,9 @@
/// It is used by both [MaterialApp] and [CupertinoApp] to implement base
/// functionality for an app.
///
+/// If a [MediaQuery] is not available above [WidgetsApp], a [MediaQuery] is
+/// built using [MediaQuery.fromWindow].
+///
/// Find references to many of the widgets that [WidgetsApp] wraps in the "See
/// also" section.
///
@@ -247,6 +250,8 @@
/// without an explicit style.
/// * [MediaQuery], which establishes a subtree in which media queries resolve
/// to a [MediaQueryData].
+/// * [MediaQuery.fromWindow], which builds a [MediaQuery] with data derived
+/// from [WidgetsBinding.window].
/// * [Localizations], which defines the [Locale] for its `child`.
/// * [Title], a widget that describes this app in the operating system.
/// * [Navigator], a widget that manages a set of child widgets with a stack
@@ -1635,6 +1640,19 @@
assert(_debugCheckLocalizations(appLocale));
+ Widget child = Localizations(
+ locale: appLocale,
+ delegates: _localizationsDelegates.toList(),
+ child: title,
+ );
+
+ final MediaQueryData? data = MediaQuery.maybeOf(context);
+ if (data == null) {
+ child = MediaQuery.fromWindow(
+ child: child,
+ );
+ }
+
return RootRestorationScope(
restorationId: widget.restorationScopeId,
child: Shortcuts(
@@ -1648,13 +1666,7 @@
child: DefaultTextEditingActions(
child: FocusTraversalGroup(
policy: ReadingOrderTraversalPolicy(),
- child: _MediaQueryFromWindow(
- child: Localizations(
- locale: appLocale,
- delegates: _localizationsDelegates.toList(),
- child: title,
- ),
- ),
+ child: child,
),
),
),
@@ -1663,81 +1675,3 @@
);
}
}
-
-/// Builds [MediaQuery] from `window` by listening to [WidgetsBinding].
-///
-/// It is performed in a standalone widget to rebuild **only** [MediaQuery] and
-/// its dependents when `window` changes, instead of rebuilding the entire widget tree.
-class _MediaQueryFromWindow extends StatefulWidget {
- const _MediaQueryFromWindow({Key? key, required this.child}) : super(key: key);
-
- final Widget child;
-
- @override
- _MediaQueryFromWindowsState createState() => _MediaQueryFromWindowsState();
-}
-
-class _MediaQueryFromWindowsState extends State<_MediaQueryFromWindow> with WidgetsBindingObserver {
- @override
- void initState() {
- super.initState();
- WidgetsBinding.instance!.addObserver(this);
- }
-
- // ACCESSIBILITY
-
- @override
- void didChangeAccessibilityFeatures() {
- setState(() {
- // The properties of window have changed. We use them in our build
- // function, so we need setState(), but we don't cache anything locally.
- });
- }
-
- // METRICS
-
- @override
- void didChangeMetrics() {
- setState(() {
- // The properties of window have changed. We use them in our build
- // function, so we need setState(), but we don't cache anything locally.
- });
- }
-
- @override
- void didChangeTextScaleFactor() {
- setState(() {
- // The textScaleFactor property of window has changed. We reference
- // window in our build function, so we need to call setState(), but
- // we don't need to cache anything locally.
- });
- }
-
- // RENDERING
- @override
- void didChangePlatformBrightness() {
- setState(() {
- // The platformBrightness property of window has changed. We reference
- // window in our build function, so we need to call setState(), but
- // we don't need to cache anything locally.
- });
- }
-
- @override
- Widget build(BuildContext context) {
- MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
- if (!kReleaseMode) {
- data = data.copyWith(platformBrightness: debugBrightnessOverride);
- }
- return MediaQuery(
- data: data,
- child: widget.child,
- );
- }
-
- @override
- void dispose() {
- WidgetsBinding.instance!.removeObserver(this);
- super.dispose();
- }
-}
diff --git a/packages/flutter/lib/src/widgets/media_query.dart b/packages/flutter/lib/src/widgets/media_query.dart
index ffbdf8f..bcbf903 100644
--- a/packages/flutter/lib/src/widgets/media_query.dart
+++ b/packages/flutter/lib/src/widgets/media_query.dart
@@ -9,6 +9,7 @@
import 'package:flutter/foundation.dart';
import 'basic.dart';
+import 'binding.dart';
import 'debug.dart';
import 'framework.dart';
@@ -787,6 +788,32 @@
);
}
+ /// Creates a [_MediaQueryFromWindow] which builds and updates a
+ /// [MediaQuery] using the latest [WidgetsBinding.window] values.
+ ///
+ /// The [MediaQuery] is wrapped in a separate widget to ensure that only it
+ /// and its dependents are updated when `window` changes, instead of
+ /// rebuilding the whole widget tree.
+ ///
+ /// This should be inserted into the widget tree when the [MediaQuery] view
+ /// padding is consumed by a widget in such a way that the view padding is no
+ /// longer exposed to the widget's descendants or siblings.
+ ///
+ /// The [child] argument is required and must not be null.
+ ///
+ /// See also:
+ ///
+ /// * [_MediaQueryFromWindow], the underlying widget.
+ static Widget fromWindow({
+ Key? key,
+ required Widget child,
+ }) {
+ return _MediaQueryFromWindow(
+ key: key,
+ child: child,
+ );
+ }
+
/// Contains information about the current media.
///
/// For example, the [MediaQueryData.size] property contains the width and
@@ -922,3 +949,100 @@
/// focus (although they remain disabled) when traversed.
directional,
}
+
+/// Provides a [MediaQuery] which is built and updated using the latest
+/// [WidgetsBinding.window] values.
+///
+/// Receives `window` updates by listening to [WidgetsBinding].
+///
+/// The standalone widget ensures that it rebuilds **only** [MediaQuery] and
+/// its dependents when `window` changes, instead of rebuilding the entire
+/// widget tree.
+///
+/// It is used by [WidgetsApp] if no other [MediaQuery] is available above it.
+///
+/// See also:
+///
+/// * [MediaQuery], which establishes a subtree in which media queries resolve
+/// to a [MediaQueryData].
+class _MediaQueryFromWindow extends StatefulWidget {
+ /// Creates a [_MediaQueryFromWindow] that provides a [MediaQuery] to its
+ /// descendants using the `window` to keep [MediaQueryData] up to date.
+ ///
+ /// The [child] must not be null.
+ const _MediaQueryFromWindow({
+ Key? key,
+ required this.child,
+ }) : super(key: key);
+
+ /// {@macro flutter.widgets.ProxyWidget.child}
+ final Widget child;
+
+ @override
+ State<_MediaQueryFromWindow> createState() => _MediaQueryFromWindowState();
+}
+
+class _MediaQueryFromWindowState extends State<_MediaQueryFromWindow> with WidgetsBindingObserver {
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance!.addObserver(this);
+ }
+
+ // ACCESSIBILITY
+
+ @override
+ void didChangeAccessibilityFeatures() {
+ setState(() {
+ // The properties of window have changed. We use them in our build
+ // function, so we need setState(), but we don't cache anything locally.
+ });
+ }
+
+ // METRICS
+
+ @override
+ void didChangeMetrics() {
+ setState(() {
+ // The properties of window have changed. We use them in our build
+ // function, so we need setState(), but we don't cache anything locally.
+ });
+ }
+
+ @override
+ void didChangeTextScaleFactor() {
+ setState(() {
+ // The textScaleFactor property of window has changed. We reference
+ // window in our build function, so we need to call setState(), but
+ // we don't need to cache anything locally.
+ });
+ }
+
+ // RENDERING
+ @override
+ void didChangePlatformBrightness() {
+ setState(() {
+ // The platformBrightness property of window has changed. We reference
+ // window in our build function, so we need to call setState(), but
+ // we don't need to cache anything locally.
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
+ if (!kReleaseMode) {
+ data = data.copyWith(platformBrightness: debugBrightnessOverride);
+ }
+ return MediaQuery(
+ data: data,
+ child: widget.child,
+ );
+ }
+
+ @override
+ void dispose() {
+ WidgetsBinding.instance!.removeObserver(this);
+ super.dispose();
+ }
+}
diff --git a/packages/flutter/test/widgets/app_test.dart b/packages/flutter/test/widgets/app_test.dart
index d0c6778..c423d44 100644
--- a/packages/flutter/test/widgets/app_test.dart
+++ b/packages/flutter/test/widgets/app_test.dart
@@ -455,6 +455,39 @@
const Locale('zh'),
);
});
+
+ testWidgets('WidgetsApp creates a MediaQuery', (WidgetTester tester) async {
+ late BuildContext capturedContext;
+ await tester.pumpWidget(
+ WidgetsApp(
+ builder: (BuildContext context, Widget? child) {
+ capturedContext = context;
+ return const Placeholder();
+ },
+ color: const Color(0xFF123456),
+ ),
+ );
+ expect(MediaQuery.of(capturedContext), isNotNull);
+ });
+
+ testWidgets('WidgetsApp does not create MediaQuery if one is already available', (WidgetTester tester) async {
+ late BuildContext capturedContext;
+ final UniqueKey uniqueKey = UniqueKey();
+ await tester.pumpWidget(
+ MediaQuery(
+ key: uniqueKey,
+ data: const MediaQueryData(),
+ child: WidgetsApp(
+ builder: (BuildContext context, Widget? child) {
+ capturedContext = context;
+ return const Placeholder();
+ },
+ color: const Color(0xFF123456),
+ ),
+ ),
+ );
+ expect(capturedContext.dependOnInheritedWidgetOfExactType<MediaQuery>()?.key, uniqueKey);
+ });
}
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation);
diff --git a/packages/flutter/test/widgets/media_query_test.dart b/packages/flutter/test/widgets/media_query_test.dart
index 7b71949..d5608c1 100644
--- a/packages/flutter/test/widgets/media_query_test.dart
+++ b/packages/flutter/test/widgets/media_query_test.dart
@@ -627,4 +627,49 @@
expect(outsideBoldTextOverride, false);
expect(insideBoldTextOverride, true);
});
+
+ testWidgets('MediaQuery.fromWindow creates a MediaQuery', (WidgetTester tester) async {
+ bool hasMediaQueryAsParentOutside = false;
+ bool hasMediaQueryAsParentInside = false;
+
+ await tester.pumpWidget(
+ Builder(
+ builder: (BuildContext context) {
+ hasMediaQueryAsParentOutside =
+ context.findAncestorWidgetOfExactType<MediaQuery>() != null;
+ return MediaQuery.fromWindow(
+ child: Builder(
+ builder: (BuildContext context) {
+ hasMediaQueryAsParentInside =
+ context.findAncestorWidgetOfExactType<MediaQuery>() != null;
+ return const SizedBox();
+ },
+ ),
+ );
+ },
+ ),
+ );
+
+ expect(hasMediaQueryAsParentOutside, false);
+ expect(hasMediaQueryAsParentInside, true);
+ });
+
+ testWidgets('MediaQueryData.fromWindow is created using window values', (WidgetTester tester)
+ async {
+ final MediaQueryData windowData = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
+ late MediaQueryData fromWindowData;
+
+ await tester.pumpWidget(
+ MediaQuery.fromWindow(
+ child: Builder(
+ builder: (BuildContext context) {
+ fromWindowData = MediaQuery.of(context);
+ return const SizedBox();
+ },
+ ),
+ ),
+ );
+
+ expect(windowData, equals(fromWindowData));
+ });
}