Widgets app refactor (#22269)

* Refactor of cupertino/material/widgets app

* update docs 

* Update tests for const
diff --git a/dev/integration_tests/ui/lib/commands.dart b/dev/integration_tests/ui/lib/commands.dart
index e15bbad..f5d758f 100644
--- a/dev/integration_tests/ui/lib/commands.dart
+++ b/dev/integration_tests/ui/lib/commands.dart
@@ -15,7 +15,7 @@
     await WidgetsBinding.instance.reassembleApplication();
     return log;
   });
-  runApp(MaterialApp(home: const Test()));
+  runApp(const MaterialApp(home: Test()));
 }
 
 class Test extends SingleChildRenderObjectWidget {
diff --git a/dev/manual_tests/lib/animated_icons.dart b/dev/manual_tests/lib/animated_icons.dart
index 718dba8..3cf6f10 100644
--- a/dev/manual_tests/lib/animated_icons.dart
+++ b/dev/manual_tests/lib/animated_icons.dart
@@ -7,9 +7,9 @@
 class AnimatedIconsTestApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
-    return MaterialApp(
+    return const MaterialApp(
       title: 'Animated Icons Test',
-      home: const Scaffold(
+      home: Scaffold(
         body: IconsList(),
       ),
     );
diff --git a/dev/manual_tests/lib/material_arc.dart b/dev/manual_tests/lib/material_arc.dart
index 06cdf9f..5b7fada 100644
--- a/dev/manual_tests/lib/material_arc.dart
+++ b/dev/manual_tests/lib/material_arc.dart
@@ -474,7 +474,7 @@
 }
 
 void main() {
-  runApp(MaterialApp(
-    home: const AnimationDemo(),
+  runApp(const MaterialApp(
+    home: AnimationDemo(),
   ));
 }
diff --git a/examples/flutter_gallery/test/accessibility_test.dart b/examples/flutter_gallery/test/accessibility_test.dart
index 423469c..5668126 100644
--- a/examples/flutter_gallery/test/accessibility_test.dart
+++ b/examples/flutter_gallery/test/accessibility_test.dart
@@ -91,7 +91,7 @@
 
     testWidgets('grid_list_demo', (WidgetTester tester) async {
       final SemanticsHandle handle = tester.ensureSemantics();
-      await tester.pumpWidget(MaterialApp(home: const GridListDemo()));
+      await tester.pumpWidget(const MaterialApp(home: GridListDemo()));
       expect(tester, meetsGuideline(androidTapTargetGuideline));
       handle.dispose();
     });
@@ -105,21 +105,21 @@
 
     testWidgets('leave_behind_demo', (WidgetTester tester) async {
      final SemanticsHandle handle = tester.ensureSemantics();
-      await tester.pumpWidget(MaterialApp(home: const LeaveBehindDemo()));
+      await tester.pumpWidget(const MaterialApp(home: LeaveBehindDemo()));
       expect(tester, meetsGuideline(androidTapTargetGuideline));
       handle.dispose();
     });
 
     testWidgets('list_demo', (WidgetTester tester) async {
      final SemanticsHandle handle = tester.ensureSemantics();
-      await tester.pumpWidget(MaterialApp(home: const ListDemo()));
+      await tester.pumpWidget(const MaterialApp(home: ListDemo()));
       expect(tester, meetsGuideline(androidTapTargetGuideline));
       handle.dispose();
     });
 
     testWidgets('menu_demo', (WidgetTester tester) async {
      final SemanticsHandle handle = tester.ensureSemantics();
-      await tester.pumpWidget(MaterialApp(home: const MenuDemo()));
+      await tester.pumpWidget(const MaterialApp(home: MenuDemo()));
       expect(tester, meetsGuideline(androidTapTargetGuideline));
       handle.dispose();
     });
@@ -133,7 +133,7 @@
 
     testWidgets('overscroll_demo', (WidgetTester tester) async {
      final SemanticsHandle handle = tester.ensureSemantics();
-      await tester.pumpWidget(MaterialApp(home: const OverscrollDemo()));
+      await tester.pumpWidget(const MaterialApp(home: OverscrollDemo()));
       expect(tester, meetsGuideline(androidTapTargetGuideline));
       handle.dispose();
     });
@@ -161,7 +161,7 @@
 
     testWidgets('reorderable_list_demo', (WidgetTester tester) async {
      final SemanticsHandle handle = tester.ensureSemantics();
-      await tester.pumpWidget(MaterialApp(home: const ReorderableListDemo()));
+      await tester.pumpWidget(const MaterialApp(home: ReorderableListDemo()));
       expect(tester, meetsGuideline(androidTapTargetGuideline));
       handle.dispose();
     });
@@ -196,7 +196,7 @@
 
     testWidgets('snack_bar_demo', (WidgetTester tester) async {
       final SemanticsHandle handle = tester.ensureSemantics();
-      await tester.pumpWidget(MaterialApp(home: const SnackBarDemo()));
+      await tester.pumpWidget(const MaterialApp(home: SnackBarDemo()));
       expect(tester, meetsGuideline(androidTapTargetGuideline));
       handle.dispose();
     });
@@ -217,7 +217,7 @@
 
     testWidgets('text_form_field_demo', (WidgetTester tester) async {
       final SemanticsHandle handle = tester.ensureSemantics();
-      await tester.pumpWidget(MaterialApp(home: const TextFormFieldDemo()));
+      await tester.pumpWidget(const MaterialApp(home: TextFormFieldDemo()));
       expect(tester, meetsGuideline(androidTapTargetGuideline));
       handle.dispose();
     });
@@ -380,12 +380,12 @@
         handle.dispose();
       });
 
-      testWidgets('overscroll_demo $themeName', (WidgetTester tester) async {
-        final SemanticsHandle handle = tester.ensureSemantics();
-        await tester.pumpWidget(MaterialApp(theme: theme, home: const OverscrollDemo()));
-        await expectLater(tester, meetsGuideline(textContrastGuideline));
-        handle.dispose();
-      });
+    testWidgets('overscroll_demo', (WidgetTester tester) async {
+      final SemanticsHandle handle = tester.ensureSemantics();
+      await tester.pumpWidget(const MaterialApp(home: OverscrollDemo()));
+      await expectLater(tester, meetsGuideline(textContrastGuideline));
+      handle.dispose();
+    });
 
       testWidgets('page_selector_demo $themeName', (WidgetTester tester) async {
         final SemanticsHandle handle = tester.ensureSemantics();
diff --git a/examples/flutter_gallery/test/calculator/smoke_test.dart b/examples/flutter_gallery/test/calculator/smoke_test.dart
index 177ac3d..8f4b2e5 100644
--- a/examples/flutter_gallery/test/calculator/smoke_test.dart
+++ b/examples/flutter_gallery/test/calculator/smoke_test.dart
@@ -14,7 +14,7 @@
   // We press the "1" and the "2" buttons and check that the display
   // reads "12".
   testWidgets('Flutter calculator app smoke test', (WidgetTester tester) async {
-    await tester.pumpWidget(MaterialApp(home: const CalculatorDemo()));
+    await tester.pumpWidget(const MaterialApp(home: CalculatorDemo()));
 
     final Finder oneButton = find.widgetWithText(InkResponse, '1');
     expect(oneButton, findsOneWidget);
diff --git a/examples/flutter_gallery/test/demo/material/text_form_field_demo_test.dart b/examples/flutter_gallery/test/demo/material/text_form_field_demo_test.dart
index 8ca1a27..7a79303 100644
--- a/examples/flutter_gallery/test/demo/material/text_form_field_demo_test.dart
+++ b/examples/flutter_gallery/test/demo/material/text_form_field_demo_test.dart
@@ -8,7 +8,7 @@
 
 void main() {
   testWidgets('validates name field correctly', (WidgetTester tester) async {
-    await tester.pumpWidget(MaterialApp(home: const TextFormFieldDemo()));
+    await tester.pumpWidget(const MaterialApp(home: TextFormFieldDemo()));
 
     final Finder submitButton = find.widgetWithText(RaisedButton, 'SUBMIT');
     expect(submitButton, findsOneWidget);
diff --git a/packages/flutter/lib/src/cupertino/app.dart b/packages/flutter/lib/src/cupertino/app.dart
index 1bb7d6a..362c043 100644
--- a/packages/flutter/lib/src/cupertino/app.dart
+++ b/packages/flutter/lib/src/cupertino/app.dart
@@ -8,7 +8,7 @@
 import 'button.dart';
 import 'colors.dart';
 import 'icons.dart';
-import 'tab_view.dart';
+import 'route.dart';
 
 // Based on specs from https://developer.apple.com/design/resources/ for
 // iOS 12.
@@ -74,8 +74,9 @@
   /// This class creates an instance of [WidgetsApp].
   ///
   /// The boolean arguments, [routes], and [navigatorObservers], must not be null.
-  CupertinoApp({ // can't be const because the asserts use methods on Map :-(
+  const CupertinoApp({
     Key key,
+    this.navigatorKey,
     this.home,
     this.routes = const <String, WidgetBuilder>{},
     this.initialRoute,
@@ -97,43 +98,6 @@
     this.debugShowCheckedModeBanner = true,
   }) : assert(routes != null),
        assert(navigatorObservers != null),
-       assert(
-         home == null ||
-         !routes.containsKey(Navigator.defaultRouteName),
-         'If the home property is specified, the routes table '
-         'cannot include an entry for "/", since it would be redundant.'
-       ),
-       assert(
-         builder != null ||
-         home != null ||
-         routes.containsKey(Navigator.defaultRouteName) ||
-         onGenerateRoute != null ||
-         onUnknownRoute != null,
-         'Either the home property must be specified, '
-         'or the routes table must include an entry for "/", '
-         'or there must be on onGenerateRoute callback specified, '
-         'or there must be an onUnknownRoute callback specified, '
-         'or the builder property must be specified, '
-         'because otherwise there is nothing to fall back on if the '
-         'app is started with an intent that specifies an unknown route.'
-       ),
-       assert(
-         (home != null ||
-          routes.isNotEmpty ||
-          onGenerateRoute != null ||
-          onUnknownRoute != null)
-         ||
-         (builder != null &&
-          initialRoute == null &&
-          navigatorObservers.isEmpty),
-         'If no route is provided using '
-         'home, routes, onGenerateRoute, or onUnknownRoute, '
-         'a non-null callback for the builder property must be provided, '
-         'and the other navigator-related properties, '
-         'navigatorKey, initialRoute, and navigatorObservers, '
-         'must have their initial values '
-         '(null, null, and the empty list, respectively).'
-       ),
        assert(title != null),
        assert(showPerformanceOverlay != null),
        assert(checkerboardRasterCacheImages != null),
@@ -142,31 +106,10 @@
        assert(debugShowCheckedModeBanner != null),
        super(key: key);
 
-  /// The widget for the default route of the app ([Navigator.defaultRouteName],
-  /// which is `/`).
-  ///
-  /// This is the route that is displayed first when the application is started
-  /// normally, unless [initialRoute] is specified. It's also the route that's
-  /// displayed if the [initialRoute] can't be displayed.
-  ///
-  /// To be able to directly call [MediaQuery.of], [Navigator.of], etc, in the
-  /// code that sets the [home] argument in the constructor, you can use a
-  /// [Builder] widget to get a [BuildContext].
-  ///
-  /// If [home] is specified, then [routes] must not include an entry for `/`,
-  /// as [home] takes its place.
-  ///
-  /// The [Navigator] is only built if routes are provided (either via [home],
-  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
-  /// [builder] must not be null.
-  ///
-  /// The difference between using [home] and using [builder] is that the [home]
-  /// subtree is inserted into the application below a [Navigator] (and thus
-  /// below an [Overlay], which [Navigator] uses). With [home], therefore,
-  /// dialog boxes will work automatically, the [routes] table will be used, and
-  /// APIs such as [Navigator.push] and [Navigator.pop] will work as expected.
-  /// In contrast, the widget returned from [builder] is inserted _above_ the
-  /// [CupertinoApp]'s [Navigator] (if any).
+  /// {@macro flutter.widgets.widgetsApp.navigatorKey}
+  final GlobalKey<NavigatorState> navigatorKey;
+
+  /// {@macro flutter.widgets.widgetsApp.home}
   final Widget home;
 
   /// The application's top-level routing table.
@@ -176,81 +119,22 @@
   /// [WidgetBuilder] is used to construct a [CupertinoPageRoute] that performs
   /// an appropriate transition, including [Hero] animations, to the new route.
   ///
-  /// If the app only has one page, then you can specify it using [home] instead.
-  ///
-  /// If [home] is specified, then it implies an entry in this table for the
-  /// [Navigator.defaultRouteName] route (`/`), and it is an error to
-  /// redundantly provide such a route in the [routes] table.
-  ///
-  /// If a route is requested that is not specified in this table (or by
-  /// [home]), then the [onGenerateRoute] callback is called to build the page
-  /// instead.
-  ///
-  /// The [Navigator] is only built if routes are provided (either via [home],
-  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
-  /// [builder] must not be null.
+  /// {@macro flutter.widgets.widgetsApp.routes}
   final Map<String, WidgetBuilder> routes;
 
   /// {@macro flutter.widgets.widgetsApp.initialRoute}
-  ///
-  /// The [Navigator] is only built if routes are provided (either via [home],
-  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
-  /// [initialRoute] must be null and [builder] must not be null.
-  ///
-  /// See also:
-  ///
-  ///  * [Navigator.initialRoute], which is used to implement this property.
-  ///  * [Navigator.push], for pushing additional routes.
-  ///  * [Navigator.pop], for removing a route from the stack.
   final String initialRoute;
 
   /// {@macro flutter.widgets.widgetsApp.onGenerateRoute}
-  ///
-  /// This is used if [routes] does not contain the requested route.
-  ///
-  /// The [Navigator] is only built if routes are provided (either via [home],
-  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
-  /// [builder] must not be null.
   final RouteFactory onGenerateRoute;
 
-  /// Called when [onGenerateRoute] fails to generate a route, except for the
-  /// [initialRoute].
-  ///
   /// {@macro flutter.widgets.widgetsApp.onUnknownRoute}
-  ///
-  /// The [Navigator] is only built if routes are provided (either via [home],
-  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
-  /// [builder] must not be null.
   final RouteFactory onUnknownRoute;
 
   /// {@macro flutter.widgets.widgetsApp.navigatorObservers}
-  ///
-  /// The [Navigator] is only built if routes are provided (either via [home],
-  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
-  /// [navigatorObservers] must be the empty list and [builder] must not be null.
   final List<NavigatorObserver> navigatorObservers;
 
   /// {@macro flutter.widgets.widgetsApp.builder}
-  ///
-  /// If no routes are provided using [home], [routes], [onGenerateRoute], or
-  /// [onUnknownRoute], the `child` will be null, and it is the responsibility
-  /// of the [builder] to provide the application's routing machinery.
-  ///
-  /// If routes _are_ provided using one or more of those properties, then
-  /// `child` is not null, and the returned value should include the `child` in
-  /// the widget subtree; if it does not, then the application will have no
-  /// navigator and the [navigatorKey], [home], [routes], [onGenerateRoute],
-  /// [onUnknownRoute], [initialRoute], and [navigatorObservers] properties will
-  /// have no effect.
-  ///
-  /// If [builder] is null, it is as if a builder was specified that returned
-  /// the `child` directly. If it is null, routes must be provided using one of
-  /// the other properties listed above.
-  ///
-  /// Unless a [Navigator] is provided, either implicitly from [builder] being
-  /// null, or by a [builder] including its `child` argument, or by a [builder]
-  /// explicitly providing a [Navigator] of its own, widgets and APIs such as
-  /// [Hero], [Navigator.push] and [Navigator.pop], will not function.
   final TransitionBuilder builder;
 
   /// {@macro flutter.widgets.widgetsApp.title}
@@ -304,6 +188,12 @@
 
   @override
   _CupertinoAppState createState() => _CupertinoAppState();
+
+  /// The [HeroController] used for Cupertino page transitions.
+  ///
+  /// Used by [CupertinoTabView] and [CupertinoApp].
+  static HeroController createCupertinoHeroController() =>
+      HeroController(); // Linear tweening.
 }
 
 class _AlwaysCupertinoScrollBehavior extends ScrollBehavior {
@@ -320,51 +210,39 @@
 }
 
 class _CupertinoAppState extends State<CupertinoApp> {
+  HeroController _heroController;
 
   @override
   void initState() {
     super.initState();
+    _heroController = CupertinoApp.createCupertinoHeroController();
     _updateNavigator();
   }
 
   @override
   void didUpdateWidget(CupertinoApp 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 = CupertinoApp.createCupertinoHeroController();
+    }
     _updateNavigator();
   }
 
-  bool _haveNavigator;
-  void _updateNavigator() {
-    _haveNavigator = widget.home != null ||
-                     widget.routes.isNotEmpty ||
-                     widget.onGenerateRoute != null ||
-                     widget.onUnknownRoute != null;
-  }
+  List<NavigatorObserver> _navigatorObservers;
 
-  Widget defaultBuilder(BuildContext context, Widget child) {
-    // The `child` coming back out from WidgetsApp will always be null since
-    // we never passed in anything for it to create a Navigator inside
-    // WidgetsApp.
-    assert(child == null);
-    if (_haveNavigator) {
-      // Reuse CupertinoTabView which creates a routing Navigator for us.
-      final Widget navigator = CupertinoTabView(
-        builder: widget.home != null
-            ? (BuildContext context) => widget.home
-            : null,
-        routes: widget.routes,
-        onGenerateRoute: widget.onGenerateRoute,
-        onUnknownRoute: widget.onUnknownRoute,
-        navigatorObservers: widget.navigatorObservers,
-      );
-      if (widget.builder != null) {
-        return widget.builder(context, navigator);
-      } else {
-        return navigator;
-      }
+  void _updateNavigator() {
+    if (widget.home != null ||
+        widget.routes.isNotEmpty ||
+        widget.onGenerateRoute != null ||
+        widget.onUnknownRoute != null) {
+      _navigatorObservers = List<NavigatorObserver>.from(widget.navigatorObservers)
+        ..add(_heroController);
     } else {
-      // We asserted that child is null above.
-      return widget.builder(context, null);
+      _navigatorObservers = null;
     }
   }
 
@@ -374,10 +252,18 @@
       behavior: _AlwaysCupertinoScrollBehavior(),
       child: WidgetsApp(
         key: GlobalObjectKey(this),
-        // We're passing in a builder and nothing else that the WidgetsApp uses
-        // to build its own Navigator because we're building a Navigator with
-        // routes in this class.
-        builder: defaultBuilder,
+        navigatorKey: widget.navigatorKey,
+        navigatorObservers: _navigatorObservers,
+        // TODO(dnfield): when https://github.com/dart-lang/sdk/issues/34572 is resolved
+        // this can use type arguments again
+        pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) =>
+          CupertinoPageRoute<dynamic>(settings: settings, builder: builder),
+        home: widget.home,
+        routes: widget.routes,
+        initialRoute: widget.initialRoute,
+        onGenerateRoute: widget.onGenerateRoute,
+        onUnknownRoute: widget.onUnknownRoute,
+        builder: widget.builder,
         title: widget.title,
         onGenerateTitle: widget.onGenerateTitle,
         textStyle: _kDefaultTextStyle,
diff --git a/packages/flutter/lib/src/cupertino/tab_view.dart b/packages/flutter/lib/src/cupertino/tab_view.dart
index d27a86c..08bb615 100644
--- a/packages/flutter/lib/src/cupertino/tab_view.dart
+++ b/packages/flutter/lib/src/cupertino/tab_view.dart
@@ -4,6 +4,7 @@
 
 import 'package:flutter/widgets.dart';
 
+import 'app.dart' show CupertinoApp;
 import 'route.dart';
 
 /// A single tab view with its own [Navigator] state and history.
@@ -114,7 +115,7 @@
   @override
   void initState() {
     super.initState();
-    _heroController = HeroController(); // Linear tweening.
+    _heroController = CupertinoApp.createCupertinoHeroController();
     _updateObservers();
   }
 
diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart
index a47f803..e1b6274 100644
--- a/packages/flutter/lib/src/material/app.dart
+++ b/packages/flutter/lib/src/material/app.dart
@@ -79,7 +79,7 @@
   /// This class creates an instance of [WidgetsApp].
   ///
   /// The boolean arguments, [routes], and [navigatorObservers], must not be null.
-  MaterialApp({ // can't be const because the asserts use methods on Map :-(
+  const MaterialApp({
     Key key,
     this.navigatorKey,
     this.home,
@@ -105,44 +105,6 @@
     this.debugShowCheckedModeBanner = true,
   }) : assert(routes != null),
        assert(navigatorObservers != null),
-       assert(
-         home == null ||
-         !routes.containsKey(Navigator.defaultRouteName),
-         'If the home property is specified, the routes table '
-         'cannot include an entry for "/", since it would be redundant.'
-       ),
-       assert(
-         builder != null ||
-         home != null ||
-         routes.containsKey(Navigator.defaultRouteName) ||
-         onGenerateRoute != null ||
-         onUnknownRoute != null,
-         'Either the home property must be specified, '
-         'or the routes table must include an entry for "/", '
-         'or there must be on onGenerateRoute callback specified, '
-         'or there must be an onUnknownRoute callback specified, '
-         'or the builder property must be specified, '
-         'because otherwise there is nothing to fall back on if the '
-         'app is started with an intent that specifies an unknown route.'
-       ),
-       assert(
-         (home != null ||
-          routes.isNotEmpty ||
-          onGenerateRoute != null ||
-          onUnknownRoute != null)
-         ||
-         (builder != null &&
-          navigatorKey == null &&
-          initialRoute == null &&
-          navigatorObservers.isEmpty),
-         'If no route is provided using '
-         'home, routes, onGenerateRoute, or onUnknownRoute, '
-         'a non-null callback for the builder property must be provided, '
-         'and the other navigator-related properties, '
-         'navigatorKey, initialRoute, and navigatorObservers, '
-         'must have their initial values '
-         '(null, null, and the empty list, respectively).'
-       ),
        assert(title != null),
        assert(debugShowMaterialGrid != null),
        assert(showPerformanceOverlay != null),
@@ -152,48 +114,10 @@
        assert(debugShowCheckedModeBanner != 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.
-  ///
-  /// The [Navigator] is only built if routes are provided (either via [home],
-  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
-  /// [navigatorKey] must be null and [builder] must not be null.
+  /// {@macro flutter.widgets.widgetsApp.navigatorKey}
   final GlobalKey<NavigatorState> navigatorKey;
 
-  /// The widget for the default route of the app ([Navigator.defaultRouteName],
-  /// which is `/`).
-  ///
-  /// This is the route that is displayed first when the application is started
-  /// normally, unless [initialRoute] is specified. It's also the route that's
-  /// displayed if the [initialRoute] can't be displayed.
-  ///
-  /// To be able to directly call [Theme.of], [MediaQuery.of], etc, in the code
-  /// that sets the [home] argument in the constructor, you can use a [Builder]
-  /// widget to get a [BuildContext].
-  ///
-  /// If [home] is specified, then [routes] must not include an entry for `/`,
-  /// as [home] takes its place.
-  ///
-  /// The [Navigator] is only built if routes are provided (either via [home],
-  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
-  /// [builder] must not be null.
-  ///
-  /// The difference between using [home] and using [builder] is that the [home]
-  /// subtree is inserted into the application below a [Navigator] (and thus
-  /// below an [Overlay], which [Navigator] uses). With [home], therefore,
-  /// dialog boxes will work automatically, [Tooltip]s will work, the [routes]
-  /// table will be used, and APIs such as [Navigator.push] and [Navigator.pop]
-  /// will work as expected. In contrast, the widget returned from [builder] is
-  /// inserted _above_ the [MaterialApp]'s [Navigator] (if any).
+  /// {@macro flutter.widgets.widgetsApp.home}
   final Widget home;
 
   /// The application's top-level routing table.
@@ -203,82 +127,25 @@
   /// [WidgetBuilder] is used to construct a [MaterialPageRoute] that performs
   /// an appropriate transition, including [Hero] animations, to the new route.
   ///
-  /// If the app only has one page, then you can specify it using [home] instead.
-  ///
-  /// If [home] is specified, then it implies an entry in this table for the
-  /// [Navigator.defaultRouteName] route (`/`), and it is an error to
-  /// redundantly provide such a route in the [routes] table.
-  ///
-  /// If a route is requested that is not specified in this table (or by
-  /// [home]), then the [onGenerateRoute] callback is called to build the page
-  /// instead.
-  ///
-  /// The [Navigator] is only built if routes are provided (either via [home],
-  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
-  /// [builder] must not be null.
+  /// {@macro flutter.widgets.widgetsApp.routes}
   final Map<String, WidgetBuilder> routes;
 
   /// {@macro flutter.widgets.widgetsApp.initialRoute}
-  ///
-  /// The [Navigator] is only built if routes are provided (either via [home],
-  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
-  /// [initialRoute] must be null and [builder] must not be null.
-  ///
-  /// See also:
-  ///
-  ///  * [Navigator.initialRoute], which is used to implement this property.
-  ///  * [Navigator.push], for pushing additional routes.
-  ///  * [Navigator.pop], for removing a route from the stack.
   final String initialRoute;
 
   /// {@macro flutter.widgets.widgetsApp.onGenerateRoute}
-  ///
-  /// This is used if [routes] does not contain the requested route.
-  ///
-  /// The [Navigator] is only built if routes are provided (either via [home],
-  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
-  /// [builder] must not be null.
   final RouteFactory onGenerateRoute;
 
-  /// Called when [onGenerateRoute] fails to generate a route, except for the
-  /// [initialRoute].
-  ///
   /// {@macro flutter.widgets.widgetsApp.onUnknownRoute}
-  ///
-  /// The [Navigator] is only built if routes are provided (either via [home],
-  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
-  /// [builder] must not be null.
   final RouteFactory onUnknownRoute;
 
   /// {@macro flutter.widgets.widgetsApp.navigatorObservers}
-  ///
-  /// The [Navigator] is only built if routes are provided (either via [home],
-  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
-  /// [navigatorObservers] must be the empty list and [builder] must not be null.
   final List<NavigatorObserver> navigatorObservers;
 
   /// {@macro flutter.widgets.widgetsApp.builder}
   ///
-  /// If no routes are provided using [home], [routes], [onGenerateRoute], or
-  /// [onUnknownRoute], the `child` will be null, and it is the responsibility
-  /// of the [builder] to provide the application's routing machinery.
-  ///
-  /// If routes _are_ provided using one or more of those properties, then
-  /// `child` is not null, and the returned value should include the `child` in
-  /// the widget subtree; if it does not, then the application will have no
-  /// navigator and the [navigatorKey], [home], [routes], [onGenerateRoute],
-  /// [onUnknownRoute], [initialRoute], and [navigatorObservers] properties will
-  /// have no effect.
-  ///
-  /// If [builder] is null, it is as if a builder was specified that returned
-  /// the `child` directly. If it is null, routes must be provided using one of
-  /// the other properties listed above.
-  ///
-  /// Unless a [Navigator] is provided, either implicitly from [builder] being
-  /// null, or by a [builder] including its `child` argument, or by a [builder]
-  /// explicitly providing a [Navigator] of its own, features such as
-  /// [showDialog] and [showMenu], widgets such as [Tooltip], [PopupMenuButton],
-  /// or [Hero], and APIs such as [Navigator.push] and [Navigator.pop], will not
+  /// Material specific features such as [showDialog] and [showMenu], and widgets
+  /// such as [Tooltip], [PopupMenuButton], also require a [Navigator] to properly
   /// function.
   final TransitionBuilder builder;
 
@@ -472,73 +339,24 @@
     _updateNavigator();
   }
 
-  bool _haveNavigator;
   List<NavigatorObserver> _navigatorObservers;
 
   void _updateNavigator() {
-    _haveNavigator = widget.home != null ||
-                     widget.routes.isNotEmpty ||
-                     widget.onGenerateRoute != null ||
-                     widget.onUnknownRoute != null;
-    _navigatorObservers = List<NavigatorObserver>.from(widget.navigatorObservers)
-      ..add(_heroController);
+    if (widget.home != null ||
+        widget.routes.isNotEmpty ||
+        widget.onGenerateRoute != null ||
+        widget.onUnknownRoute != null) {
+      _navigatorObservers = List<NavigatorObserver>.from(widget.navigatorObservers)
+        ..add(_heroController);
+    } else {
+      _navigatorObservers = null;
+    }
   }
 
   RectTween _createRectTween(Rect begin, Rect end) {
     return MaterialRectArcTween(begin: begin, end: end);
   }
 
-  Route<dynamic> _onGenerateRoute(RouteSettings settings) {
-    final String name = settings.name;
-    WidgetBuilder builder;
-    if (name == Navigator.defaultRouteName && widget.home != null) {
-      builder = (BuildContext context) => widget.home;
-    } else {
-      builder = widget.routes[name];
-    }
-    if (builder != null) {
-      return MaterialPageRoute<dynamic>(
-        builder: builder,
-        settings: settings,
-      );
-    }
-    if (widget.onGenerateRoute != null)
-      return widget.onGenerateRoute(settings);
-    return null;
-  }
-
-  Route<dynamic> _onUnknownRoute(RouteSettings settings) {
-    assert(() {
-      if (widget.onUnknownRoute == null) {
-        throw FlutterError(
-          'Could not find a generator for route $settings in the $runtimeType.\n'
-          'Generators for routes are searched for in the following order:\n'
-          ' 1. For the "/" route, the "home" property, if non-null, is used.\n'
-          ' 2. Otherwise, the "routes" table is used, if it has an entry for '
-          'the route.\n'
-          ' 3. Otherwise, onGenerateRoute is called. It should return a '
-          'non-null value for any valid route not handled by "home" and "routes".\n'
-          ' 4. Finally if all else fails onUnknownRoute is called.\n'
-          'Unfortunately, onUnknownRoute was not set.'
-        );
-      }
-      return true;
-    }());
-    final Route<dynamic> result = widget.onUnknownRoute(settings);
-    assert(() {
-      if (result == null) {
-        throw FlutterError(
-          'The onUnknownRoute callback returned null.\n'
-          'When the $runtimeType requested the route $settings from its '
-          'onUnknownRoute callback, the callback returned null. Such callbacks '
-          'must never return null.'
-        );
-      }
-      return true;
-    }());
-    return result;
-  }
-
   // 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
@@ -559,10 +377,16 @@
       child: WidgetsApp(
         key: GlobalObjectKey(this),
         navigatorKey: widget.navigatorKey,
-        navigatorObservers: _haveNavigator ? _navigatorObservers : null,
+        navigatorObservers: _navigatorObservers,
+        // TODO(dnfield): when https://github.com/dart-lang/sdk/issues/34572 is resolved
+        // this can use type arguments again
+        pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) =>
+          MaterialPageRoute<dynamic>(settings: settings, builder: builder),
+        home: widget.home,
+        routes: widget.routes,
         initialRoute: widget.initialRoute,
-        onGenerateRoute: _haveNavigator ? _onGenerateRoute : null,
-        onUnknownRoute: _haveNavigator ? _onUnknownRoute : null,
+        onGenerateRoute: widget.onGenerateRoute,
+        onUnknownRoute: widget.onUnknownRoute,
         builder: widget.builder,
         title: widget.title,
         onGenerateTitle: widget.onGenerateTitle,
diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart
index e8c95ea..a6db3e3 100644
--- a/packages/flutter/lib/src/widgets/app.dart
+++ b/packages/flutter/lib/src/widgets/app.dart
@@ -14,6 +14,7 @@
 import 'localizations.dart';
 import 'media_query.dart';
 import 'navigator.dart';
+import 'pages.dart';
 import 'performance_overlay.dart';
 import 'semantics_debugger.dart';
 import 'text.dart';
@@ -43,6 +44,13 @@
 /// This function must not return null.
 typedef GenerateAppTitle = String Function(BuildContext context);
 
+/// The signature of [WidgetsApp.pageRouteBuilder].
+///
+/// Creates a [PageRoute] using the given [RouteSettings] and [WidgetBuilder].
+// TODO(dnfield): when https://github.com/dart-lang/sdk/issues/34572 is resolved
+// this can use type arguments again
+typedef PageRouteFactory = PageRoute<dynamic> Function(RouteSettings settings, WidgetBuilder builder);
+
 /// A convenience class that wraps a number of widgets that are commonly
 /// required for an application.
 ///
@@ -58,8 +66,10 @@
   ///
   /// The boolean arguments, [color], and [navigatorObservers] must not be null.
   ///
-  /// If the [builder] is null, the [onGenerateRoute] argument is required, and
-  /// corresponds to [Navigator.onGenerateRoute]. If the [builder] is non-null
+  /// If the [builder] is null, the [onGenerateRoute] and [pageRouteBuilder]
+  /// arguments are required. The [onGenerateRoute] parameter corresponds to
+  /// [Navigator.onGenerateRoute], and [pageRouteBuilder] will create a [PageRoute]
+  /// that wraps newly built routes. If the [builder] is non-null
   /// and the [onGenerateRoute] argument is null, then the [builder] will not be
   /// provided with a [Navigator]. If [onGenerateRoute] is not provided,
   /// [navigatorKey], [onUnknownRoute], [navigatorObservers], and [initialRoute]
@@ -74,6 +84,9 @@
     this.onUnknownRoute,
     this.navigatorObservers = const <NavigatorObserver>[],
     this.initialRoute,
+    this.pageRouteBuilder,
+    this.home,
+    this.routes = const <String, WidgetBuilder>{},
     this.builder,
     this.title = '',
     this.onGenerateTitle,
@@ -91,11 +104,49 @@
     this.debugShowCheckedModeBanner = true,
     this.inspectorSelectButtonBuilder,
   }) : assert(navigatorObservers != null),
-       assert(onGenerateRoute != null || navigatorKey == null),
-       assert(onGenerateRoute != null || onUnknownRoute == null),
-       assert(onGenerateRoute != null || navigatorObservers == const <NavigatorObserver>[]),
-       assert(onGenerateRoute != null || initialRoute == null),
-       assert(onGenerateRoute != null || builder != null),
+       assert(routes != null),
+       assert(
+         home == null ||
+         !routes.containsKey(Navigator.defaultRouteName),
+         'If the home property is specified, the routes table '
+         'cannot include an entry for "/", since it would be redundant.'
+       ),
+       assert(
+         builder != null ||
+         home != null ||
+         routes.containsKey(Navigator.defaultRouteName) ||
+         onGenerateRoute != null ||
+         onUnknownRoute != null,
+         'Either the home property must be specified, '
+         'or the routes table must include an entry for "/", '
+         'or there must be on onGenerateRoute callback specified, '
+         'or there must be an onUnknownRoute callback specified, '
+         'or the builder property must be specified, '
+         'because otherwise there is nothing to fall back on if the '
+         'app is started with an intent that specifies an unknown route.'
+       ),
+       assert(
+         (home != null ||
+          routes.isNotEmpty ||
+          onGenerateRoute != null ||
+          onUnknownRoute != null)
+         ||
+         (builder != null &&
+          navigatorKey == null &&
+          initialRoute == null &&
+          navigatorObservers.isEmpty),
+         'If no route is provided using '
+         'home, routes, onGenerateRoute, or onUnknownRoute, '
+         'a non-null callback for the builder property must be provided, '
+         'and the other navigator-related properties, '
+         'navigatorKey, initialRoute, and navigatorObservers, '
+         'must have their initial values '
+         '(null, null, and the empty list, respectively).'
+       ),
+       assert(onGenerateRoute != null || pageRouteBuilder != null,
+         'If onGenerateRoute is not provided, the pageRouteBuilder must be specified '
+         'so that the default handler will know what kind of PageRoute transition '
+         'bo build.'),
        assert(title != null),
        assert(color != null),
        assert(supportedLocales != null && supportedLocales.isNotEmpty),
@@ -107,6 +158,7 @@
        assert(debugShowWidgetInspector != null),
        super(key: key);
 
+  /// {@template flutter.widgets.widgetsApp.navigatorKey}
   /// A key to use when building the [Navigator].
   ///
   /// If a [navigatorKey] is specified, the [Navigator] can be directly
@@ -121,6 +173,7 @@
   ///
   /// The [Navigator] is only built if [onGenerateRoute] is not null; if it is
   /// null, [navigatorKey] must also be null.
+  /// {@endTemplate}
   final GlobalKey<NavigatorState> navigatorKey;
 
   /// {@template flutter.widgets.widgetsApp.onGenerateRoute}
@@ -134,25 +187,103 @@
   /// During normal app operation, the [onGenerateRoute] callback will only be
   /// applied to route names pushed by the application, and so should never
   /// return null.
+  ///
+  /// This is used if [routes] does not contain the requested route.
+  ///
+  /// The [Navigator] is only built if routes are provided (either via [home],
+  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
+  /// [builder] must not be null.
   /// {@endtemplate}
   ///
-  /// The [Navigator] is only built if [onGenerateRoute] is not null. If
-  /// [onGenerateRoute] is null, the [builder] must be non-null.
+  /// If this property is not set, either the [routes] or [home] properties must
+  /// be set, and the [pageRouteBuilder] must also be set so that the
+  /// default handler will know what routes and [PageRoute]s to build.
   final RouteFactory onGenerateRoute;
 
-  /// Called when [onGenerateRoute] fails to generate a route.
+  /// The [PageRoute] generator callback used when the app is navigated to a
+  /// named route.
   ///
+  /// This callback can be used, for example, to specify that a [MaterialPageRoute]
+  /// or a [CupertinoPageRoute] should be used for building page transitions.
+  final PageRouteFactory pageRouteBuilder;
+
+  /// {@template flutter.widgets.widgetsApp.home}
+  /// The widget for the default route of the app ([Navigator.defaultRouteName],
+  /// which is `/`).
+  ///
+  /// This is the route that is displayed first when the application is started
+  /// normally, unless [initialRoute] is specified. It's also the route that's
+  /// displayed if the [initialRoute] can't be displayed.
+  ///
+  /// To be able to directly call [Theme.of], [MediaQuery.of], etc, in the code
+  /// that sets the [home] argument in the constructor, you can use a [Builder]
+  /// widget to get a [BuildContext].
+  ///
+  /// If [home] is specified, then [routes] must not include an entry for `/`,
+  /// as [home] takes its place.
+  ///
+  /// The [Navigator] is only built if routes are provided (either via [home],
+  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
+  /// [builder] must not be null.
+  ///
+  /// The difference between using [home] and using [builder] is that the [home]
+  /// subtree is inserted into the application below a [Navigator] (and thus
+  /// below an [Overlay], which [Navigator] uses). With [home], therefore,
+  /// dialog boxes will work automatically, the [routes] table will be used, and
+  /// APIs such as [Navigator.push] and [Navigator.pop] will work as expected.
+  /// In contrast, the widget returned from [builder] is inserted _above_ the
+  /// app's [Navigator] (if any).
+  /// {@endTemplate}
+  ///
+  /// If this property is set, the [pageRouteBuilder] property must also be set
+  /// so that the default route handler will know what kind of [PageRoute]s to
+  /// build.
+  final Widget home;
+
+  /// The application's top-level routing table.
+  ///
+  /// When a named route is pushed with [Navigator.pushNamed], the route name is
+  /// looked up in this map. If the name is present, the associated
+  /// [WidgetBuilder] is used to construct a [PageRoute] specified by
+  /// [pageRouteBuilder] to perform an appropriate transition, including [Hero]
+  /// animations, to the new route.
+  ///
+  /// {@template flutter.widgets.widgetsApp.routes}
+  /// If the app only has one page, then you can specify it using [home] instead.
+  ///
+  /// If [home] is specified, then it implies an entry in this table for the
+  /// [Navigator.defaultRouteName] route (`/`), and it is an error to
+  /// redundantly provide such a route in the [routes] table.
+  ///
+  /// If a route is requested that is not specified in this table (or by
+  /// [home]), then the [onGenerateRoute] callback is called to build the page
+  /// instead.
+  ///
+  /// The [Navigator] is only built if routes are provided (either via [home],
+  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
+  /// [builder] must not be null.
+  /// {@endTemplate}
+  ///
+  /// If the routes map is not empty, the [pageRouteBuilder] property must be set
+  /// so that the default route handler will know what kind of [PageRoute]s to
+  /// build.
+  final Map<String, WidgetBuilder> routes;
+
   /// {@template flutter.widgets.widgetsApp.onUnknownRoute}
+  /// Called when [onGenerateRoute] fails to generate a route, except for the
+  /// [initialRoute].
+  ///
   /// This callback is typically used for error handling. For example, this
   /// callback might always generate a "not found" page that describes the route
   /// that wasn't found.
   ///
   /// Unknown routes can arise either from errors in the app or from external
   /// requests to push routes, such as from Android intents.
-  /// {@endtemplate}
   ///
-  /// The [Navigator] is only built if [onGenerateRoute] is not null; if it is
-  /// null, [onUnknownRoute] must also be null.
+  /// The [Navigator] is only built if routes are provided (either via [home],
+  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
+  /// [builder] must not be null.
+  /// {@endtemplate}
   final RouteFactory onUnknownRoute;
 
   /// {@template flutter.widgets.widgetsApp.initialRoute}
@@ -170,16 +301,16 @@
   /// [initialRoute] is ignored and [Navigator.defaultRouteName] is used instead
   /// (`/`). This can happen if the app is started with an intent that specifies
   /// a non-existent route.
-  /// {@endtemplate}
-  ///
-  /// The [Navigator] is only built if [onGenerateRoute] is not null; if it is
-  /// null, [initialRoute] must also be null.
+  /// The [Navigator] is only built if routes are provided (either via [home],
+  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
+  /// [initialRoute] must be null and [builder] must not be null.
   ///
   /// See also:
   ///
   ///  * [Navigator.initialRoute], which is used to implement this property.
   ///  * [Navigator.push], for pushing additional routes.
   ///  * [Navigator.pop], for removing a route from the stack.
+  /// {@endtemplate}
   final String initialRoute;
 
   /// {@template flutter.widgets.widgetsApp.navigatorObservers}
@@ -187,11 +318,11 @@
   ///
   /// This list must be replaced by a list of newly-created observers if the
   /// [navigatorKey] is changed.
-  /// {@endtemplate}
   ///
-  /// The [Navigator] is only built if [onGenerateRoute] is not null; if it is
-  /// null, [navigatorObservers] must be left to its default value, the empty
-  /// list.
+  /// The [Navigator] is only built if routes are provided (either via [home],
+  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
+  /// [navigatorObservers] must be the empty list and [builder] must not be null.
+  /// {@endtemplate}
   final List<NavigatorObserver> navigatorObservers;
 
   /// {@template flutter.widgets.widgetsApp.builder}
@@ -214,21 +345,27 @@
   ///
   /// The [builder] callback is passed two arguments, the [BuildContext] (as
   /// `context`) and a [Navigator] widget (as `child`).
-  /// {@endtemplate}
   ///
-  /// If [onGenerateRoute] is null, the `child` will be null, and it is the
-  /// responsibility of the [builder] to provide the application's routing
-  /// machinery.
+  /// If no routes are provided using [home], [routes], [onGenerateRoute], or
+  /// [onUnknownRoute], the `child` will be null, and it is the responsibility
+  /// of the [builder] to provide the application's routing machinery.
   ///
-  /// If [onGenerateRoute] is not null, then `child` is not null, and the
-  /// returned value should include the `child` in the widget subtree; if it
-  /// does not, then the application will have no navigator and the
-  /// [navigatorKey], [onGenerateRoute], [onUnknownRoute], [initialRoute], and
-  /// [navigatorObservers] properties will have no effect.
+  /// If routes _are_ provided using one or more of those properties, then
+  /// `child` is not null, and the returned value should include the `child` in
+  /// the widget subtree; if it does not, then the application will have no
+  /// navigator and the [navigatorKey], [home], [routes], [onGenerateRoute],
+  /// [onUnknownRoute], [initialRoute], and [navigatorObservers] properties will
+  /// have no effect.
   ///
   /// If [builder] is null, it is as if a builder was specified that returned
-  /// the `child` directly. At least one of either [onGenerateRoute] or
-  /// [builder] must be non-null.
+  /// the `child` directly. If it is null, routes must be provided using one of
+  /// the other properties listed above.
+  ///
+  /// Unless a [Navigator] is provided, either implicitly from [builder] being
+  /// null, or by a [builder] including its `child` argument, or by a [builder]
+  /// explicitly providing a [Navigator] of its own, widgets and APIs such as
+  /// [Hero], [Navigator.push] and [Navigator.pop], will not function.
+  /// {@endtemplate}
   final TransitionBuilder builder;
 
   /// {@template flutter.widgets.widgetsApp.title}
@@ -455,11 +592,64 @@
   GlobalKey<NavigatorState> _navigator;
 
   void _updateNavigator() {
-    if (widget.onGenerateRoute == null) {
-      _navigator = null;
+    _navigator = widget.navigatorKey ?? GlobalObjectKey<NavigatorState>(this);
+  }
+
+  Route<dynamic> _onGenerateRoute(RouteSettings settings) {
+    final String name = settings.name;
+    WidgetBuilder builder;
+    if (name == Navigator.defaultRouteName && widget.home != null) {
+      builder = (BuildContext context) => widget.home;
     } else {
-      _navigator = widget.navigatorKey ?? GlobalObjectKey<NavigatorState>(this);
+      builder = widget.routes[name];
     }
+    if (builder != null) {
+      assert(widget.pageRouteBuilder != null,
+        'The default onGenerateRoute handler for WidgetsApp must have a '
+        'pageRouteBuilder set if the home or routes properties are set.');
+      final Route<dynamic> route = widget.pageRouteBuilder(
+        settings,
+        builder,
+      );
+      assert(route != null,
+        'The pageRouteBuilder for WidgetsApp must return a valid non-null Route.');
+      return route;
+    }
+    if (widget.onGenerateRoute != null)
+      return widget.onGenerateRoute(settings);
+    return null;
+  }
+
+  Route<dynamic> _onUnknownRoute(RouteSettings settings) {
+    assert(() {
+      if (widget.onUnknownRoute == null) {
+        throw FlutterError(
+          'Could not find a generator for route $settings in the $runtimeType.\n'
+          'Generators for routes are searched for in the following order:\n'
+          ' 1. For the "/" route, the "home" property, if non-null, is used.\n'
+          ' 2. Otherwise, the "routes" table is used, if it has an entry for '
+          'the route.\n'
+          ' 3. Otherwise, onGenerateRoute is called. It should return a '
+          'non-null value for any valid route not handled by "home" and "routes".\n'
+          ' 4. Finally if all else fails onUnknownRoute is called.\n'
+          'Unfortunately, onUnknownRoute was not set.'
+        );
+      }
+      return true;
+    }());
+    final Route<dynamic> result = widget.onUnknownRoute(settings);
+    assert(() {
+      if (result == null) {
+        throw FlutterError(
+          'The onUnknownRoute callback returned null.\n'
+          'When the $runtimeType requested the route $settings from its '
+          'onUnknownRoute callback, the callback returned null. Such callbacks '
+          'must never return null.'
+        );
+      }
+      return true;
+    }());
+    return result;
   }
 
   // On Android: the user has pressed the back button.
@@ -566,9 +756,14 @@
     if (_navigator != null) {
       navigator = Navigator(
         key: _navigator,
-        initialRoute: widget.initialRoute ?? ui.window.defaultRouteName,
-        onGenerateRoute: widget.onGenerateRoute,
-        onUnknownRoute: widget.onUnknownRoute,
+        // If ui.window.defaultRouteName isn't '/', we should assume it was set
+        // intentionally via `setInitialRoute`, and should override whatever
+        // is in [widget.initialRoute].
+        initialRoute: ui.window.defaultRouteName != Navigator.defaultRouteName
+            ? ui.window.defaultRouteName
+            : widget.initialRoute ?? ui.window.defaultRouteName,
+        onGenerateRoute: _onGenerateRoute,
+        onUnknownRoute: _onUnknownRoute,
         observers: widget.navigatorObservers,
       );
     }
diff --git a/packages/flutter/test/cupertino/dialog_test.dart b/packages/flutter/test/cupertino/dialog_test.dart
index 65133e9..29a3bcc 100644
--- a/packages/flutter/test/cupertino/dialog_test.dart
+++ b/packages/flutter/test/cupertino/dialog_test.dart
@@ -66,7 +66,7 @@
 
   testWidgets('Has semantic annotations', (WidgetTester tester) async {
     final SemanticsTester semantics = SemanticsTester(tester);
-    await tester.pumpWidget(MaterialApp(home: const Material(
+    await tester.pumpWidget(const MaterialApp(home: Material(
       child: CupertinoAlertDialog(
         title: Text('The Title'),
         content: Text('Content'),
diff --git a/packages/flutter/test/cupertino/nav_bar_test.dart b/packages/flutter/test/cupertino/nav_bar_test.dart
index 8ca3f6c..3ccadc0 100644
--- a/packages/flutter/test/cupertino/nav_bar_test.dart
+++ b/packages/flutter/test/cupertino/nav_bar_test.dart
@@ -16,8 +16,8 @@
 void main() {
   testWidgets('Middle still in center with asymmetrical actions', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const CupertinoNavigationBar(
+      const CupertinoApp(
+        home: CupertinoNavigationBar(
           leading: CupertinoButton(child: Text('Something'), onPressed: null,),
           middle: Text('Title'),
         ),
@@ -30,8 +30,8 @@
 
   testWidgets('Middle still in center with back button', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const CupertinoNavigationBar(
+      const CupertinoApp(
+        home: CupertinoNavigationBar(
           middle: Text('Title'),
         ),
       ),
@@ -54,8 +54,8 @@
 
   testWidgets('Opaque background does not add blur effects', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const CupertinoNavigationBar(
+      const CupertinoApp(
+        home: CupertinoNavigationBar(
           middle: Text('Title'),
           backgroundColor: Color(0xFFE5E5E5),
         ),
@@ -66,8 +66,8 @@
 
   testWidgets('Non-opaque background adds blur effects', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const CupertinoNavigationBar(
+      const CupertinoApp(
+        home: CupertinoNavigationBar(
           middle: Text('Title'),
         ),
       ),
@@ -120,8 +120,8 @@
 
   testWidgets('Padding works in RTL', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const Directionality(
+      const CupertinoApp(
+        home: Directionality(
           textDirection: TextDirection.rtl,
           child: Align(
             alignment: Alignment.topCenter,
@@ -151,8 +151,8 @@
   testWidgets('Verify styles of each slot', (WidgetTester tester) async {
     count = 0x000000;
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const CupertinoNavigationBar(
+      const CupertinoApp(
+        home: CupertinoNavigationBar(
           leading: _ExpectStyles(color: Color(0xFF001122), index: 0x000001),
           middle: _ExpectStyles(color: Color(0xFF000000), letterSpacing: -0.08, index: 0x000100),
           trailing: _ExpectStyles(color: Color(0xFF001122), index: 0x010000),
@@ -165,8 +165,8 @@
 
   testWidgets('No slivers with no large titles', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const CupertinoPageScaffold(
+      const CupertinoApp(
+        home: CupertinoPageScaffold(
           navigationBar: CupertinoNavigationBar(
             middle: Text('Title'),
           ),
@@ -431,8 +431,8 @@
 
   testWidgets('Auto back/close button', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const CupertinoNavigationBar(
+      const CupertinoApp(
+        home: CupertinoNavigationBar(
           middle: Text('Home page'),
         ),
       ),
@@ -486,8 +486,8 @@
 
   testWidgets('Long back label turns into "back"', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const Placeholder(),
+      const CupertinoApp(
+        home: Placeholder(),
       ),
     );
 
@@ -529,8 +529,8 @@
 
   testWidgets('Border should be displayed by default', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const CupertinoNavigationBar(
+      const CupertinoApp(
+        home: CupertinoNavigationBar(
           middle: Text('Title'),
         ),
       ),
@@ -551,8 +551,8 @@
 
   testWidgets('Overrides border color', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const CupertinoNavigationBar(
+      const CupertinoApp(
+        home: CupertinoNavigationBar(
           middle: Text('Title'),
           border: Border(
             bottom: BorderSide(
@@ -580,8 +580,8 @@
 
   testWidgets('Border should not be displayed when null', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const CupertinoNavigationBar(
+      const CupertinoApp(
+        home: CupertinoNavigationBar(
           middle: Text('Title'),
           border: null,
         ),
@@ -746,8 +746,8 @@
     'Standard title golden',
     (WidgetTester tester) async {
       await tester.pumpWidget(
-        CupertinoApp(
-          home: const RepaintBoundary(
+        const CupertinoApp(
+          home: RepaintBoundary(
             child: CupertinoPageScaffold(
               navigationBar: CupertinoNavigationBar(
                 middle: Text('Bling bling'),
diff --git a/packages/flutter/test/cupertino/nav_bar_transition_test.dart b/packages/flutter/test/cupertino/nav_bar_transition_test.dart
index d7715a4..a4b2e8a 100644
--- a/packages/flutter/test/cupertino/nav_bar_transition_test.dart
+++ b/packages/flutter/test/cupertino/nav_bar_transition_test.dart
@@ -14,8 +14,8 @@
   String toTitle,
 }) async {
   await tester.pumpWidget(
-    CupertinoApp(
-      home: const Placeholder(),
+    const CupertinoApp(
+      home: Placeholder(),
     ),
   );
 
@@ -195,8 +195,8 @@
   testWidgets('Fullscreen dialogs do not create heroes',
       (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const Placeholder(),
+      const CupertinoApp(
+        home: Placeholder(),
       ),
     );
 
diff --git a/packages/flutter/test/cupertino/page_test.dart b/packages/flutter/test/cupertino/page_test.dart
index 5bf44ff..a6de3b2 100644
--- a/packages/flutter/test/cupertino/page_test.dart
+++ b/packages/flutter/test/cupertino/page_test.dart
@@ -146,8 +146,8 @@
 
   testWidgets('test iOS fullscreen dialog transition', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const Center(child: Text('Page 1')),
+      const CupertinoApp(
+        home: Center(child: Text('Page 1')),
       ),
     );
 
diff --git a/packages/flutter/test/cupertino/route_test.dart b/packages/flutter/test/cupertino/route_test.dart
index 3f74462..de721c5 100644
--- a/packages/flutter/test/cupertino/route_test.dart
+++ b/packages/flutter/test/cupertino/route_test.dart
@@ -9,8 +9,8 @@
 void main() {
   testWidgets('Middle auto-populates with title', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const Placeholder(),
+      const CupertinoApp(
+        home: Placeholder(),
       ),
     );
 
@@ -39,8 +39,8 @@
 
   testWidgets('Large title auto-populates with title', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const Placeholder(),
+      const CupertinoApp(
+        home: Placeholder(),
       ),
     );
 
@@ -104,8 +104,8 @@
 
   testWidgets('Leading auto-populates with back button with previous title', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const Placeholder(),
+      const CupertinoApp(
+        home: Placeholder(),
       ),
     );
 
@@ -150,8 +150,8 @@
 
   testWidgets('Previous title is correct on first transition frame', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const Placeholder(),
+      const CupertinoApp(
+        home: Placeholder(),
       ),
     );
 
@@ -193,8 +193,8 @@
 
   testWidgets('Previous title stays up to date with changing routes', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const Placeholder(),
+      const CupertinoApp(
+        home: Placeholder(),
       ),
     );
 
diff --git a/packages/flutter/test/cupertino/scaffold_test.dart b/packages/flutter/test/cupertino/scaffold_test.dart
index dbeca86..6620e00 100644
--- a/packages/flutter/test/cupertino/scaffold_test.dart
+++ b/packages/flutter/test/cupertino/scaffold_test.dart
@@ -11,8 +11,8 @@
 void main() {
   testWidgets('Contents are behind translucent bar', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const CupertinoPageScaffold(
+      const CupertinoApp(
+        home: CupertinoPageScaffold(
           // Default nav bar is translucent.
           navigationBar: CupertinoNavigationBar(
             middle: Text('Title'),
@@ -276,8 +276,8 @@
 
   testWidgets('Decorated with white background by default', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const CupertinoPageScaffold(
+      const CupertinoApp(
+        home: CupertinoPageScaffold(
           child: Center(),
         ),
       ),
@@ -292,8 +292,8 @@
 
   testWidgets('Overrides background color', (WidgetTester tester) async {
     await tester.pumpWidget(
-      CupertinoApp(
-        home: const CupertinoPageScaffold(
+      const CupertinoApp(
+        home: CupertinoPageScaffold(
           child: Center(),
           backgroundColor: Color(0xFF010203),
         ),
diff --git a/packages/flutter/test/material/about_test.dart b/packages/flutter/test/material/about_test.dart
index a4134a5..f71ba90 100644
--- a/packages/flutter/test/material/about_test.dart
+++ b/packages/flutter/test/material/about_test.dart
@@ -67,9 +67,9 @@
 
   testWidgets('About box logic defaults to executable name for app name', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
+      const MaterialApp(
         title: 'flutter_tester',
-        home: const Material(child: AboutListTile()),
+        home: Material(child: AboutListTile()),
       ),
     );
     expect(find.text('About flutter_tester'), findsOneWidget);
@@ -89,8 +89,8 @@
     });
 
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Center(
+      const MaterialApp(
+        home: Center(
           child: LicensePage(),
         ),
       ),
diff --git a/packages/flutter/test/material/app_test.dart b/packages/flutter/test/material/app_test.dart
index b42e86c..bbd05e7 100644
--- a/packages/flutter/test/material/app_test.dart
+++ b/packages/flutter/test/material/app_test.dart
@@ -28,9 +28,9 @@
 void main() {
   testWidgets('Can nest apps', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
+      const MaterialApp(
         home: MaterialApp(
-          home: const Text('Home sweet home'),
+          home: Text('Home sweet home'),
         ),
       ),
     );
@@ -57,8 +57,8 @@
     await tester.pumpWidget(FocusScope(
       autofocus: true,
       node: focusScopeNode,
-      child: MaterialApp(
-        home: const Text('Home'),
+      child: const MaterialApp(
+        home: Text('Home'),
       ),
     ));
 
@@ -67,8 +67,8 @@
 
   testWidgets('Can show grid without losing sync', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const StateMarker(),
+      const MaterialApp(
+        home: StateMarker(),
       ),
     );
 
@@ -76,9 +76,9 @@
     state1.marker = 'original';
 
     await tester.pumpWidget(
-      MaterialApp(
+      const MaterialApp(
         debugShowMaterialGrid: true,
-        home: const StateMarker(),
+        home: StateMarker(),
       ),
     );
 
@@ -205,7 +205,7 @@
   });
 
   testWidgets('Cannot pop the initial route', (WidgetTester tester) async {
-    await tester.pumpWidget(MaterialApp(home: const Text('Home')));
+    await tester.pumpWidget(const MaterialApp(home: Text('Home')));
 
     expect(find.text('Home'), findsOneWidget);
 
@@ -400,9 +400,9 @@
       home: const Placeholder(),
     ));
     expect(key.currentState, isInstanceOf<NavigatorState>());
-    await tester.pumpWidget(MaterialApp(
-      color: const Color(0xFF112233),
-      home: const Placeholder(),
+    await tester.pumpWidget(const MaterialApp(
+      color: Color(0xFF112233),
+      home: Placeholder(),
     ));
     expect(key.currentState, isNull);
     await tester.pumpWidget(MaterialApp(
diff --git a/packages/flutter/test/material/bottom_app_bar_test.dart b/packages/flutter/test/material/bottom_app_bar_test.dart
index c783cc5..2d02016 100644
--- a/packages/flutter/test/material/bottom_app_bar_test.dart
+++ b/packages/flutter/test/material/bottom_app_bar_test.dart
@@ -9,8 +9,8 @@
 void main() {
   testWidgets('no overlap with floating action button', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           floatingActionButton: FloatingActionButton(
             onPressed: null,
           ),
@@ -95,8 +95,8 @@
   // _BottomAppBarClipper will try an illegal downcast.
   testWidgets('toggle shape to null', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           bottomNavigationBar: BottomAppBar(
             shape: RectangularNotch(),
           ),
@@ -105,8 +105,8 @@
     );
 
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           bottomNavigationBar: BottomAppBar(
             shape: null,
           ),
@@ -115,8 +115,8 @@
     );
 
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           bottomNavigationBar: BottomAppBar(
             shape: RectangularNotch(),
           ),
@@ -127,8 +127,8 @@
 
   testWidgets('no notch when notch param is null', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           bottomNavigationBar: ShapeListener(BottomAppBar(
             shape: null,
           )),
@@ -159,8 +159,8 @@
 
   testWidgets('notch no margin', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           bottomNavigationBar: ShapeListener(
             BottomAppBar(
               child: SizedBox(height: 100.0),
@@ -211,8 +211,8 @@
 
   testWidgets('notch with margin', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           bottomNavigationBar: ShapeListener(
             BottomAppBar(
               child: SizedBox(height: 100.0),
@@ -263,8 +263,8 @@
 
   testWidgets('observes safe area', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const MediaQuery(
+      const MaterialApp(
+        home: MediaQuery(
           data: MediaQueryData(
             padding: EdgeInsets.all(50.0),
           ),
@@ -287,8 +287,8 @@
 
   testWidgets('clipBehavior is propagated', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           bottomNavigationBar:
               BottomAppBar(
                 child: SizedBox(height: 100.0),
@@ -303,8 +303,8 @@
     expect(physicalShape.clipBehavior, Clip.none);
 
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           bottomNavigationBar:
           BottomAppBar(
             child: SizedBox(height: 100.0),
diff --git a/packages/flutter/test/material/chip_test.dart b/packages/flutter/test/material/chip_test.dart
index 082e8ae..43ff699 100644
--- a/packages/flutter/test/material/chip_test.dart
+++ b/packages/flutter/test/material/chip_test.dart
@@ -1181,8 +1181,8 @@
     testWidgets('label only', (WidgetTester tester) async {
       final SemanticsTester semanticsTester = SemanticsTester(tester);
 
-      await tester.pumpWidget(MaterialApp(
-        home: const Material(
+      await tester.pumpWidget(const MaterialApp(
+        home: Material(
           child: RawChip(
             label: Text('test'),
           ),
diff --git a/packages/flutter/test/material/dialog_test.dart b/packages/flutter/test/material/dialog_test.dart
index a275c4a..10ed47b 100644
--- a/packages/flutter/test/material/dialog_test.dart
+++ b/packages/flutter/test/material/dialog_test.dart
@@ -106,8 +106,8 @@
 
   testWidgets('Simple dialog control test', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Material(
+      const MaterialApp(
+        home: Material(
           child: Center(
             child: RaisedButton(
               onPressed: null,
@@ -149,8 +149,8 @@
 
   testWidgets('Barrier dismissible', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Material(
+      const MaterialApp(
+        home: Material(
           child: Center(
             child: RaisedButton(
               onPressed: null,
@@ -212,8 +212,8 @@
     final SemanticsTester semantics = SemanticsTester(tester);
     const String buttonText = 'A button covered by dialog overlay';
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Material(
+      const MaterialApp(
+        home: Material(
           child: Center(
             child: RaisedButton(
               onPressed: null,
diff --git a/packages/flutter/test/material/drawer_test.dart b/packages/flutter/test/material/drawer_test.dart
index ff29764..9a542eb 100644
--- a/packages/flutter/test/material/drawer_test.dart
+++ b/packages/flutter/test/material/drawer_test.dart
@@ -61,8 +61,8 @@
     final SemanticsTester semantics = SemanticsTester(tester);
     debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           drawer: Drawer()
         ),
       ),
@@ -86,8 +86,8 @@
   testWidgets('Drawer dismiss barrier has no label on Android', (WidgetTester tester) async {
     final SemanticsTester semantics = SemanticsTester(tester);
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
             drawer: Drawer()
         ),
       ),
diff --git a/packages/flutter/test/material/floating_action_button_test.dart b/packages/flutter/test/material/floating_action_button_test.dart
index 63bf4ed..526419c 100644
--- a/packages/flutter/test/material/floating_action_button_test.dart
+++ b/packages/flutter/test/material/floating_action_button_test.dart
@@ -36,8 +36,8 @@
 
   testWidgets('Floating Action Button tooltip', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           floatingActionButton: FloatingActionButton(
             onPressed: null,
             tooltip: 'Add',
@@ -54,8 +54,8 @@
   // Regression test for: https://github.com/flutter/flutter/pull/21084
   testWidgets('Floating Action Button tooltip (long press button edge)', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           floatingActionButton: FloatingActionButton(
             onPressed: null,
             tooltip: 'Add',
@@ -75,8 +75,8 @@
   // Regression test for: https://github.com/flutter/flutter/pull/21084
   testWidgets('Floating Action Button tooltip (long press button edge - no child)', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           floatingActionButton: FloatingActionButton(
             onPressed: null,
             tooltip: 'Add',
@@ -94,8 +94,8 @@
 
   testWidgets('Floating Action Button tooltip (no child)', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           floatingActionButton: FloatingActionButton(
             onPressed: null,
             tooltip: 'Add',
@@ -150,8 +150,8 @@
 
   testWidgets('FloatingActionButton.isExtended', (WidgetTester tester) async {
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           floatingActionButton: FloatingActionButton(onPressed: null),
         ),
       ),
diff --git a/packages/flutter/test/material/persistent_bottom_sheet_test.dart b/packages/flutter/test/material/persistent_bottom_sheet_test.dart
index 9b75216..c880aee 100644
--- a/packages/flutter/test/material/persistent_bottom_sheet_test.dart
+++ b/packages/flutter/test/material/persistent_bottom_sheet_test.dart
@@ -180,8 +180,8 @@
 
     // Remove the persistent bottomSheet
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Scaffold(
+      const MaterialApp(
+        home: Scaffold(
           bottomSheet: null,
           body: Placeholder(),
         ),
diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart
index 2e807d7..4c23e53 100644
--- a/packages/flutter/test/material/scaffold_test.dart
+++ b/packages/flutter/test/material/scaffold_test.dart
@@ -124,7 +124,7 @@
   });
 
   testWidgets('Floating action entrance/exit animation', (WidgetTester tester) async {
-    await tester.pumpWidget(MaterialApp(home: const Scaffold(
+    await tester.pumpWidget(const MaterialApp(home: Scaffold(
       floatingActionButton: FloatingActionButton(
         key: Key('one'),
         onPressed: null,
@@ -134,7 +134,7 @@
 
     expect(tester.binding.transientCallbackCount, 0);
 
-    await tester.pumpWidget(MaterialApp(home: const Scaffold(
+    await tester.pumpWidget(const MaterialApp(home: Scaffold(
       floatingActionButton: FloatingActionButton(
         key: Key('two'),
         onPressed: null,
@@ -146,11 +146,11 @@
     await tester.pumpWidget(Container());
     expect(tester.binding.transientCallbackCount, 0);
 
-    await tester.pumpWidget(MaterialApp(home: const Scaffold()));
+    await tester.pumpWidget(const MaterialApp(home: Scaffold()));
 
     expect(tester.binding.transientCallbackCount, 0);
 
-    await tester.pumpWidget(MaterialApp(home: const Scaffold(
+    await tester.pumpWidget(const MaterialApp(home: Scaffold(
       floatingActionButton: FloatingActionButton(
         key: Key('one'),
         onPressed: null,
@@ -569,7 +569,7 @@
     const String drawerLabel = 'I am the reason for this test';
 
     final SemanticsTester semantics = SemanticsTester(tester);
-    await tester.pumpWidget(MaterialApp(home: const Scaffold(
+    await tester.pumpWidget(const MaterialApp(home: Scaffold(
       body: Text(bodyLabel),
       persistentFooterButtons: <Widget>[Text(persistentFooterButtonLabel)],
       bottomNavigationBar: Text(bottomNavigationBarLabel),
@@ -970,7 +970,7 @@
       const String endDrawerLabel = 'I am the label on end side';
 
       final SemanticsTester semantics = SemanticsTester(tester);
-      await tester.pumpWidget(MaterialApp(home: const Scaffold(
+      await tester.pumpWidget(const MaterialApp(home: Scaffold(
         body: Text(bodyLabel),
         drawer: Drawer(child: Text(drawerLabel)),
         endDrawer: Drawer(child: Text(endDrawerLabel)),
diff --git a/packages/flutter/test/material/text_field_focus_test.dart b/packages/flutter/test/material/text_field_focus_test.dart
index 4bb79d6..a215947 100644
--- a/packages/flutter/test/material/text_field_focus_test.dart
+++ b/packages/flutter/test/material/text_field_focus_test.dart
@@ -37,8 +37,8 @@
     expect(tester.testTextInput.isVisible, isFalse);
 
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Material(
+      const MaterialApp(
+        home: Material(
           child: Center(
             child: TextField(
               autofocus: true,
@@ -59,8 +59,8 @@
     expect(tester.testTextInput.isVisible, isFalse);
 
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Material(
+      const MaterialApp(
+        home: Material(
           child: Center(
             child: TextField(),
           ),
@@ -93,8 +93,8 @@
     expect(tester.testTextInput.isVisible, isFalse);
 
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Material(
+      const MaterialApp(
+        home: Material(
           child: Center(
             child: TextField(
               autofocus: true,
@@ -211,8 +211,8 @@
     // Regression test for https://github.com/flutter/flutter/issues/16880
 
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Material(
+      const MaterialApp(
+        home: Material(
           child: Center(
             child: TextField(
               decoration: null
diff --git a/packages/flutter/test/material/text_field_helper_text_test.dart b/packages/flutter/test/material/text_field_helper_text_test.dart
index ff4d5cd..f4d68b3 100644
--- a/packages/flutter/test/material/text_field_helper_text_test.dart
+++ b/packages/flutter/test/material/text_field_helper_text_test.dart
@@ -7,11 +7,11 @@
 
 void main() {
   testWidgets('TextField works correctly when changing helperText', (WidgetTester tester) async {
-    await tester.pumpWidget(MaterialApp(home: const Material(child: TextField(decoration: InputDecoration(helperText: 'Awesome')))));
+    await tester.pumpWidget(const MaterialApp(home: Material(child: TextField(decoration: InputDecoration(helperText: 'Awesome')))));
     expect(find.text('Awesome'), findsNWidgets(1));
     await tester.pump(const Duration(milliseconds: 100));
     expect(find.text('Awesome'), findsNWidgets(1));
-    await tester.pumpWidget(MaterialApp(home: const Material(child: TextField(decoration: InputDecoration(errorText: 'Awesome')))));
+    await tester.pumpWidget(const MaterialApp(home: Material(child: TextField(decoration: InputDecoration(errorText: 'Awesome')))));
     expect(find.text('Awesome'), findsNWidgets(2));
   });
 }
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart
index 5a1d8d6..46979c5 100644
--- a/packages/flutter/test/material/text_field_test.dart
+++ b/packages/flutter/test/material/text_field_test.dart
@@ -1721,8 +1721,8 @@
   });
 
   testWidgets('setting maxLength shows counter', (WidgetTester tester) async {
-    await tester.pumpWidget(MaterialApp(
-      home: const Material(
+    await tester.pumpWidget(const MaterialApp(
+      home: Material(
         child: DefaultTextStyle(
           style: TextStyle(fontFamily: 'Ahem', fontSize: 10.0),
           child: Center(
@@ -1746,8 +1746,8 @@
     final SemanticsTester semantics = SemanticsTester(tester);
 
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Material(
+      const MaterialApp(
+        home: Material(
           child: DefaultTextStyle(
             style: TextStyle(fontFamily: 'Ahem', fontSize: 10.0),
             child: Center(
diff --git a/packages/flutter/test/material/tooltip_test.dart b/packages/flutter/test/material/tooltip_test.dart
index 8c117f8..b1bea3a 100644
--- a/packages/flutter/test/material/tooltip_test.dart
+++ b/packages/flutter/test/material/tooltip_test.dart
@@ -610,8 +610,8 @@
     final SemanticsTester semantics = SemanticsTester(tester);
 
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Center(
+      const MaterialApp(
+        home: Center(
           child: Tooltip(
             message: 'Foo',
             child: Text('Bar'),
@@ -645,8 +645,8 @@
     final SemanticsTester semantics = SemanticsTester(tester);
 
     await tester.pumpWidget(
-      MaterialApp(
-        home: const Center(
+      const MaterialApp(
+        home: Center(
           child: Tooltip(
             message: 'Foo',
             child: Text('Bar'),
diff --git a/packages/flutter/test/widgets/banner_test.dart b/packages/flutter/test/widgets/banner_test.dart
index c11a6e0..58eb350 100644
--- a/packages/flutter/test/widgets/banner_test.dart
+++ b/packages/flutter/test/widgets/banner_test.dart
@@ -269,7 +269,7 @@
 
   testWidgets('Banner widget in MaterialApp', (WidgetTester tester) async {
     debugDisableShadows = false;
-    await tester.pumpWidget(MaterialApp(home: const Placeholder()));
+    await tester.pumpWidget(const MaterialApp(home: Placeholder()));
     expect(find.byType(CheckedModeBanner), paints
       ..save()
       ..translate(x: 800.0, y: 0.0)
diff --git a/packages/flutter/test/widgets/reassemble_test.dart b/packages/flutter/test/widgets/reassemble_test.dart
index c107812..adc74ba 100644
--- a/packages/flutter/test/widgets/reassemble_test.dart
+++ b/packages/flutter/test/widgets/reassemble_test.dart
@@ -7,8 +7,8 @@
 
 void main() {
   testWidgets('reassemble does not crash', (WidgetTester tester) async {
-    await tester.pumpWidget(MaterialApp(
-      home: const Text('Hello World')
+    await tester.pumpWidget(const MaterialApp(
+      home: Text('Hello World')
     ));
     await tester.pump();
     tester.binding.reassembleApplication();
diff --git a/packages/flutter_test/test/test_text_input_test.dart b/packages/flutter_test/test/test_text_input_test.dart
index 2c89f56..2bed613 100644
--- a/packages/flutter_test/test/test_text_input_test.dart
+++ b/packages/flutter_test/test/test_text_input_test.dart
@@ -10,8 +10,8 @@
   testWidgets('receiveAction() forwards exception when exception occurs during action processing',
           (WidgetTester tester) async {
     // Setup a widget that can receive focus so that we can open the keyboard.
-    final Widget widget = MaterialApp(
-      home: const Material(
+    const Widget widget = MaterialApp(
+      home: Material(
         child: TextField(),
       ),
     );
diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart
index c26094f..ecb5ba0 100644
--- a/packages/flutter_test/test/widget_tester_test.dart
+++ b/packages/flutter_test/test/widget_tester_test.dart
@@ -533,8 +533,8 @@
   group('getSemanticsData', () {
     testWidgets('throws when there are no semantics', (WidgetTester tester) async {
       await tester.pumpWidget(
-        MaterialApp(
-          home: const Scaffold(
+        const MaterialApp(
+          home: Scaffold(
             body: Text('hello'),
           ),
         ),