Unverified Commit e65c882e authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Provide a navigatorKey property on MaterialApp and WidgetsApp (#13591)

This lets people poke at navigators without having to get their
BuildContext from a build function or State first.
parent 2c0c9ba9
...@@ -79,6 +79,7 @@ class MaterialApp extends StatefulWidget { ...@@ -79,6 +79,7 @@ class MaterialApp extends StatefulWidget {
/// The boolean arguments, [routes], and [navigatorObservers], must not be null. /// The boolean arguments, [routes], and [navigatorObservers], must not be null.
MaterialApp({ // can't be const because the asserts use methods on Map :-( MaterialApp({ // can't be const because the asserts use methods on Map :-(
Key key, Key key,
this.navigatorKey,
this.title: '', this.title: '',
this.onGenerateTitle, this.onGenerateTitle,
this.color, this.color,
...@@ -128,6 +129,19 @@ class MaterialApp extends StatefulWidget { ...@@ -128,6 +129,19 @@ class MaterialApp extends StatefulWidget {
), ),
super(key: key); super(key: key);
/// A key to use when building the [Navigator].
///
/// If a [navigatorKey] is specified, the [Navigator] can be directly
/// manipulated without first obtaining it from a [BuildContext] via
/// [Navigator.of]: from the [navigatorKey], use the [GlobalKey.currentState]
/// getter.
///
/// If this is changed, a new [Navigator] will be created, losing all the
/// application state in the process; in that case, the [navigatorObservers]
/// must also be changed, since the previous observers will be attached to the
/// previous navigator.
final GlobalKey<NavigatorState> navigatorKey;
/// A one-line description used by the device to identify the app for the user. /// A one-line description used by the device to identify the app for the user.
/// ///
/// On Android the titles appear above the task manager's app snapshots which are /// On Android the titles appear above the task manager's app snapshots which are
...@@ -403,6 +417,9 @@ class MaterialApp extends StatefulWidget { ...@@ -403,6 +417,9 @@ class MaterialApp extends StatefulWidget {
final bool debugShowCheckedModeBanner; final bool debugShowCheckedModeBanner;
/// The list of observers for the [Navigator] created for this app. /// The list of observers for the [Navigator] created for this app.
///
/// This list must be replaced by a list of newly-created observers if the
/// [navigatorKey] is changed.
final List<NavigatorObserver> navigatorObservers; final List<NavigatorObserver> navigatorObservers;
/// Turns on a [GridPaper] overlay that paints a baseline grid /// Turns on a [GridPaper] overlay that paints a baseline grid
...@@ -453,6 +470,18 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -453,6 +470,18 @@ class _MaterialAppState extends State<MaterialApp> {
_heroController = new HeroController(createRectTween: _createRectTween); _heroController = new HeroController(createRectTween: _createRectTween);
} }
@override
void didUpdateWidget(MaterialApp oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.navigatorKey != oldWidget.navigatorKey) {
// If the Navigator changes, we have to create a new observer, because the
// old Navigator won't be disposed (and thus won't unregister with its
// observers) until after the new one has been created (because the
// Navigator has a GlobalKey).
_heroController = new HeroController(createRectTween: _createRectTween);
}
}
// Combine the Localizations for Material with the ones contributed // Combine the Localizations for Material with the ones contributed
// by the localizationsDelegates parameter, if any. Only the first delegate // by the localizationsDelegates parameter, if any. Only the first delegate
// of a particular LocalizationsDelegate.type is loaded so the // of a particular LocalizationsDelegate.type is loaded so the
...@@ -526,6 +555,7 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -526,6 +555,7 @@ class _MaterialAppState extends State<MaterialApp> {
isMaterialAppTheme: true, isMaterialAppTheme: true,
child: new WidgetsApp( child: new WidgetsApp(
key: new GlobalObjectKey(this), key: new GlobalObjectKey(this),
navigatorKey: widget.navigatorKey,
title: widget.title, title: widget.title,
onGenerateTitle: widget.onGenerateTitle, onGenerateTitle: widget.onGenerateTitle,
textStyle: _errorTextStyle, textStyle: _errorTextStyle,
......
...@@ -67,6 +67,7 @@ class WidgetsApp extends StatefulWidget { ...@@ -67,6 +67,7 @@ class WidgetsApp extends StatefulWidget {
/// By default supportedLocales is `[const Locale('en', 'US')]`. /// By default supportedLocales is `[const Locale('en', 'US')]`.
WidgetsApp({ // can't be const because the asserts use methods on Iterable :-( WidgetsApp({ // can't be const because the asserts use methods on Iterable :-(
Key key, Key key,
this.navigatorKey,
@required this.onGenerateRoute, @required this.onGenerateRoute,
this.onUnknownRoute, this.onUnknownRoute,
this.title: '', this.title: '',
...@@ -99,6 +100,19 @@ class WidgetsApp extends StatefulWidget { ...@@ -99,6 +100,19 @@ class WidgetsApp extends StatefulWidget {
assert(debugShowWidgetInspector != null), assert(debugShowWidgetInspector != null),
super(key: key); super(key: key);
/// A key to use when building the [Navigator].
///
/// If a [navigatorKey] is specified, the [Navigator] can be directly
/// manipulated without first obtaining it from a [BuildContext] via
/// [Navigator.of]: from the [navigatorKey], use the [GlobalKey.currentState]
/// getter.
///
/// If this is changed, a new [Navigator] will be created, losing all the
/// application state in the process; in that case, the [navigatorObservers]
/// must also be changed, since the previous observers will be attached to the
/// previous navigator.
final GlobalKey<NavigatorState> navigatorKey;
/// A one-line description used by the device to identify the app for the user. /// A one-line description used by the device to identify the app for the user.
/// ///
/// On Android the titles appear above the task manager's app snapshots which are /// On Android the titles appear above the task manager's app snapshots which are
...@@ -285,6 +299,9 @@ class WidgetsApp extends StatefulWidget { ...@@ -285,6 +299,9 @@ class WidgetsApp extends StatefulWidget {
final bool debugShowCheckedModeBanner; final bool debugShowCheckedModeBanner;
/// The list of observers for the [Navigator] created for this app. /// The list of observers for the [Navigator] created for this app.
///
/// This list must be replaced by a list of newly-created observers if the
/// [navigatorKey] is changed.
final List<NavigatorObserver> navigatorObservers; final List<NavigatorObserver> navigatorObservers;
/// If true, forces the performance overlay to be visible in all instances. /// If true, forces the performance overlay to be visible in all instances.
...@@ -315,7 +332,7 @@ class WidgetsApp extends StatefulWidget { ...@@ -315,7 +332,7 @@ class WidgetsApp extends StatefulWidget {
} }
class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserver { class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserver {
GlobalObjectKey<NavigatorState> _navigator; GlobalKey<NavigatorState> _navigator;
Locale _locale; Locale _locale;
Locale _resolveLocale(Locale newLocale, Iterable<Locale> supportedLocales) { Locale _resolveLocale(Locale newLocale, Iterable<Locale> supportedLocales) {
...@@ -338,11 +355,22 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv ...@@ -338,11 +355,22 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_navigator = new GlobalObjectKey<NavigatorState>(this); _updateNavigator();
_locale = _resolveLocale(ui.window.locale, widget.supportedLocales); _locale = _resolveLocale(ui.window.locale, widget.supportedLocales);
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
} }
@override
void didUpdateWidget(WidgetsApp oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.navigatorKey != oldWidget.navigatorKey)
_updateNavigator();
}
void _updateNavigator() {
_navigator = widget.navigatorKey ?? new GlobalObjectKey<NavigatorState>(this);
}
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
......
...@@ -317,4 +317,25 @@ void main() { ...@@ -317,4 +317,25 @@ void main() {
expect(textScaleFactor, isNotNull); expect(textScaleFactor, isNotNull);
expect(textScaleFactor, equals(1.0)); expect(textScaleFactor, equals(1.0));
}); });
testWidgets('MaterialApp.navigatorKey', (WidgetTester tester) async {
final GlobalKey<NavigatorState> key = new GlobalKey<NavigatorState>();
await tester.pumpWidget(new MaterialApp(
navigatorKey: key,
color: const Color(0xFF112233),
home: const Placeholder(),
));
expect(key.currentState, const isInstanceOf<NavigatorState>());
await tester.pumpWidget(new MaterialApp(
color: const Color(0xFF112233),
home: const Placeholder(),
));
expect(key.currentState, isNull);
await tester.pumpWidget(new MaterialApp(
navigatorKey: key,
color: const Color(0xFF112233),
home: const Placeholder(),
));
expect(key.currentState, const isInstanceOf<NavigatorState>());
});
} }
// Copyright 2017 The Chromium 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 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
final RouteFactory generateRoute = (RouteSettings settings) => new PageRouteBuilder<Null>(
settings: settings,
pageBuilder: (BuildContext context, Animation<double> animation1, Animation<double> animation2) {
return const Placeholder();
},
);
void main() {
testWidgets('WidgetsApp.navigatorKey', (WidgetTester tester) async {
final GlobalKey<NavigatorState> key = new GlobalKey<NavigatorState>();
await tester.pumpWidget(new WidgetsApp(
navigatorKey: key,
color: const Color(0xFF112233),
onGenerateRoute: generateRoute,
));
expect(key.currentState, const isInstanceOf<NavigatorState>());
await tester.pumpWidget(new WidgetsApp(
color: const Color(0xFF112233),
onGenerateRoute: generateRoute,
));
expect(key.currentState, isNull);
await tester.pumpWidget(new WidgetsApp(
navigatorKey: key,
color: const Color(0xFF112233),
onGenerateRoute: generateRoute,
));
expect(key.currentState, const isInstanceOf<NavigatorState>());
});
}
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