Commit 79cbe25b authored by Remi Rousselet's avatar Remi Rousselet Committed by Michael Goderbauer

No longer rebuild Routes when MediaQuery updates (#41763)

* No longer rebuild Routes when MediaQuery updates

fixes #37878

* Fix tests

* PR feedback

* Add missing endline
parent e3195c26
...@@ -706,8 +706,7 @@ class WidgetsApp extends StatefulWidget { ...@@ -706,8 +706,7 @@ class WidgetsApp extends StatefulWidget {
_WidgetsAppState createState() => _WidgetsAppState(); _WidgetsAppState createState() => _WidgetsAppState();
} }
class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserver { class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
// STATE LIFECYCLE // STATE LIFECYCLE
@override @override
...@@ -731,13 +730,6 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv ...@@ -731,13 +730,6 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
super.dispose(); super.dispose();
} }
@override
void didChangeAppLifecycleState(AppLifecycleState state) { }
@override
void didHaveMemoryPressure() { }
// NAVIGATOR // NAVIGATOR
GlobalKey<NavigatorState> _navigator; GlobalKey<NavigatorState> _navigator;
...@@ -995,46 +987,6 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv ...@@ -995,46 +987,6 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
yield DefaultWidgetsLocalizations.delegate; yield DefaultWidgetsLocalizations.delegate;
} }
// 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.
});
}
// BUILDER // BUILDER
bool _debugCheckLocalizations(Locale appLocale) { bool _debugCheckLocalizations(Locale appLocale) {
...@@ -1201,8 +1153,7 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv ...@@ -1201,8 +1153,7 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
}, },
child: DefaultFocusTraversal( child: DefaultFocusTraversal(
policy: ReadingOrderTraversalPolicy(), policy: ReadingOrderTraversalPolicy(),
child: MediaQuery( child: _MediaQueryFromWindow(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
child: Localizations( child: Localizations(
locale: appLocale, locale: appLocale,
delegates: _localizationsDelegates.toList(), delegates: _localizationsDelegates.toList(),
...@@ -1213,3 +1164,77 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv ...@@ -1213,3 +1164,77 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
); );
} }
} }
/// 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, 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) {
return MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
child: widget.child,
);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}
...@@ -2,9 +2,11 @@ ...@@ -2,9 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/semantics.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mockito/mockito.dart';
class StateMarker extends StatefulWidget { class StateMarker extends StatefulWidget {
const StateMarker({ Key key, this.child }) : super(key: key); const StateMarker({ Key key, this.child }) : super(key: key);
...@@ -392,6 +394,67 @@ void main() { ...@@ -392,6 +394,67 @@ void main() {
); );
}); });
testWidgets("WidgetsApp don't rebuild routes when MediaQuery updates", (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/37878
int routeBuildCount = 0;
int dependentBuildCount = 0;
await tester.pumpWidget(WidgetsApp(
color: const Color.fromARGB(255, 255, 255, 255),
onGenerateRoute: (_) {
return PageRouteBuilder<void>(pageBuilder: (_, __, ___) {
routeBuildCount++;
return Builder(
builder: (BuildContext context) {
dependentBuildCount++;
MediaQuery.of(context);
return Container();
},
);
});
},
));
expect(routeBuildCount, equals(1));
expect(dependentBuildCount, equals(1));
// didChangeMetrics
tester.binding.window.physicalSizeTestValue = const Size(42, 42);
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
await tester.pump();
expect(routeBuildCount, equals(1));
expect(dependentBuildCount, equals(2));
// didChangeTextScaleFactor
tester.binding.window.textScaleFactorTestValue = 42;
addTearDown(tester.binding.window.clearTextScaleFactorTestValue);
await tester.pump();
expect(routeBuildCount, equals(1));
expect(dependentBuildCount, equals(3));
// didChangePlatformBrightness
tester.binding.window.platformBrightnessTestValue = Brightness.dark;
addTearDown(tester.binding.window.clearPlatformBrightnessTestValue);
await tester.pump();
expect(routeBuildCount, equals(1));
expect(dependentBuildCount, equals(4));
// didChangeAccessibilityFeatures
tester.binding.window.accessibilityFeaturesTestValue = MockAccessibilityFeature();
addTearDown(tester.binding.window.clearAccessibilityFeaturesTestValue);
await tester.pump();
expect(routeBuildCount, equals(1));
expect(dependentBuildCount, equals(5));
});
testWidgets('Can get text scale from media query', (WidgetTester tester) async { testWidgets('Can get text scale from media query', (WidgetTester tester) async {
double textScaleFactor; double textScaleFactor;
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
...@@ -726,3 +789,5 @@ void main() { ...@@ -726,3 +789,5 @@ void main() {
expect(themeAfterBrightnessChange.brightness, Brightness.dark); expect(themeAfterBrightnessChange.brightness, Brightness.dark);
}); });
} }
class MockAccessibilityFeature extends Mock implements AccessibilityFeatures {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment