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 {
/// The boolean arguments, [routes], and [navigatorObservers], must not be null.
MaterialApp({ // can't be const because the asserts use methods on Map :-(
Key key,
this.navigatorKey,
this.title: '',
this.onGenerateTitle,
this.color,
......@@ -128,6 +129,19 @@ class MaterialApp extends StatefulWidget {
),
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.
///
/// On Android the titles appear above the task manager's app snapshots which are
......@@ -403,6 +417,9 @@ class MaterialApp extends StatefulWidget {
final bool debugShowCheckedModeBanner;
/// 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;
/// Turns on a [GridPaper] overlay that paints a baseline grid
......@@ -453,6 +470,18 @@ class _MaterialAppState extends State<MaterialApp> {
_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
// by the localizationsDelegates parameter, if any. Only the first delegate
// of a particular LocalizationsDelegate.type is loaded so the
......@@ -526,6 +555,7 @@ class _MaterialAppState extends State<MaterialApp> {
isMaterialAppTheme: true,
child: new WidgetsApp(
key: new GlobalObjectKey(this),
navigatorKey: widget.navigatorKey,
title: widget.title,
onGenerateTitle: widget.onGenerateTitle,
textStyle: _errorTextStyle,
......
......@@ -67,6 +67,7 @@ class WidgetsApp extends StatefulWidget {
/// By default supportedLocales is `[const Locale('en', 'US')]`.
WidgetsApp({ // can't be const because the asserts use methods on Iterable :-(
Key key,
this.navigatorKey,
@required this.onGenerateRoute,
this.onUnknownRoute,
this.title: '',
......@@ -99,6 +100,19 @@ class WidgetsApp extends StatefulWidget {
assert(debugShowWidgetInspector != null),
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.
///
/// On Android the titles appear above the task manager's app snapshots which are
......@@ -285,6 +299,9 @@ class WidgetsApp extends StatefulWidget {
final bool debugShowCheckedModeBanner;
/// 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;
/// If true, forces the performance overlay to be visible in all instances.
......@@ -315,7 +332,7 @@ class WidgetsApp extends StatefulWidget {
}
class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserver {
GlobalObjectKey<NavigatorState> _navigator;
GlobalKey<NavigatorState> _navigator;
Locale _locale;
Locale _resolveLocale(Locale newLocale, Iterable<Locale> supportedLocales) {
......@@ -338,11 +355,22 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
@override
void initState() {
super.initState();
_navigator = new GlobalObjectKey<NavigatorState>(this);
_updateNavigator();
_locale = _resolveLocale(ui.window.locale, widget.supportedLocales);
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
void dispose() {
WidgetsBinding.instance.removeObserver(this);
......
......@@ -317,4 +317,25 @@ void main() {
expect(textScaleFactor, isNotNull);
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