Add arguments for pushing named routes (#27058)

diff --git a/examples/stocks/lib/main.dart b/examples/stocks/lib/main.dart
index 469e3bb..5ee32aa 100644
--- a/examples/stocks/lib/main.dart
+++ b/examples/stocks/lib/main.dart
@@ -84,22 +84,8 @@
   }
 
   Route<dynamic> _getRoute(RouteSettings settings) {
-    // Routes, by convention, are split on slashes, like filesystem paths.
-    final List<String> path = settings.name.split('/');
-    // We only support paths that start with a slash, so bail if
-    // the first component is not empty:
-    if (path[0] != '')
-      return null;
-    // If the path is "/stock:..." then show a stock page for the
-    // specified stock symbol.
-    if (path[1].startsWith('stock:')) {
-      // We don't yet support subpages of a stock, so bail if there's
-      // any more path components.
-      if (path.length != 2)
-        return null;
-      // Extract the symbol part of "stock:..." and return a route
-      // for that symbol.
-      final String symbol = path[1].substring(6);
+    if (settings.name == '/stocks') {
+      final String symbol = settings.arguments;
       return MaterialPageRoute<void>(
         settings: settings,
         builder: (BuildContext context) => StockSymbolPage(symbol: symbol, stocks: stocks),
diff --git a/examples/stocks/lib/stock_home.dart b/examples/stocks/lib/stock_home.dart
index f13b51b..be60a75 100644
--- a/examples/stocks/lib/stock_home.dart
+++ b/examples/stocks/lib/stock_home.dart
@@ -262,7 +262,7 @@
       stocks: stocks.toList(),
       onAction: _buyStock,
       onOpen: (Stock stock) {
-        Navigator.pushNamed(context, '/stock:${stock.symbol}');
+        Navigator.pushNamed(context, '/stock', arguments: stock.symbol);
       },
       onShow: (Stock stock) {
         _scaffoldKey.currentState.showBottomSheet<void>((BuildContext context) => StockSymbolBottomSheet(stock: stock));
diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart
index b500250..b475469 100644
--- a/packages/flutter/lib/src/widgets/navigator.dart
+++ b/packages/flutter/lib/src/widgets/navigator.dart
@@ -289,6 +289,7 @@
   const RouteSettings({
     this.name,
     this.isInitialRoute = false,
+    this.arguments,
   });
 
   /// Creates a copy of this route settings object with the given fields
@@ -296,10 +297,12 @@
   RouteSettings copyWith({
     String name,
     bool isInitialRoute,
+    Object arguments,
   }) {
     return RouteSettings(
       name: name ?? this.name,
       isInitialRoute: isInitialRoute ?? this.isInitialRoute,
+      arguments: arguments ?? this.arguments,
     );
   }
 
@@ -313,8 +316,13 @@
   /// The initial route typically skips any entrance transition to speed startup.
   final bool isInitialRoute;
 
+  /// The arguments passed to this route.
+  ///
+  /// May be used when building the route, e.g. in [Navigator.onGenerateRoute].
+  final Object arguments;
+
   @override
-  String toString() => '"$name"';
+  String toString() => '$runtimeType("$name", $arguments)';
 }
 
 /// An interface for observing the behavior of a [Navigator].
@@ -725,19 +733,77 @@
   /// The `T` type argument is the type of the return value of the route.
   /// {@endtemplate}
   ///
+  /// {@template flutter.widgets.navigator.pushNamed.arguments}
+  /// The provided `arguments` are passed to the pushed route via
+  /// [RouteSettings.arguments]. Any object can be passed as `arguments` (e.g. a
+  /// [String], [int], or an instance of a custom `MyRouteArguments` class).
+  /// Often, a [Map] is used to pass key-value pairs.
+  ///
+  /// The `arguments` may be used in [Navigator.onGenerateRoute] or
+  /// [Navigator.onUnknownRoute] to construct the route.
+  /// {@endtemplate}
+  ///
   /// {@tool sample}
   ///
   /// Typical usage is as follows:
   ///
   /// ```dart
   /// void _didPushButton() {
-  ///   Navigator.pushNamed(context, '/nyc/1776');
+  ///   Navigator.pushNamed(context, '/settings');
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@tool sample}
+  ///
+  /// The following example shows how to pass additional `arguments` to the
+  /// route:
+  ///
+  /// ```dart
+  /// void _showBerlinWeather() {
+  ///   Navigator.pushNamed(
+  ///     context,
+  ///     '/weather',
+  ///     arguments: <String, String>{
+  ///       'city': 'Berlin',
+  ///       'country': 'Germany',
+  ///     },
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@tool sample}
+  ///
+  /// The following example shows how to pass a custom Object to the route:
+  ///
+  /// ```dart
+  /// class WeatherRouteArguments {
+  ///   WeatherRouteArguments({ this.city, this.country });
+  ///   final String city;
+  ///   final String country;
+  ///
+  ///   bool get isGermanCapital {
+  ///     return country == 'Germany' && city == 'Berlin';
+  ///   }
+  /// }
+  ///
+  /// void _showWeather() {
+  ///   Navigator.pushNamed(
+  ///     context,
+  ///     '/weather',
+  ///     arguments: WeatherRouteArguments(city: 'Berlin', country: 'Germany'),
+  ///   );
   /// }
   /// ```
   /// {@end-tool}
   @optionalTypeArgs
-  static Future<T> pushNamed<T extends Object>(BuildContext context, String routeName) {
-    return Navigator.of(context).pushNamed<T>(routeName);
+  static Future<T> pushNamed<T extends Object>(
+    BuildContext context,
+    String routeName, {
+    Object arguments,
+   }) {
+    return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments);
   }
 
   /// Replace the current route of the navigator that most tightly encloses the
@@ -773,19 +839,26 @@
   /// and `TO` is the type of the return value of the old route.
   /// {@endtemplate}
   ///
+  /// {@macro flutter.widgets.navigator.pushNamed.arguments}
+  ///
   /// {@tool sample}
   ///
   /// Typical usage is as follows:
   ///
   /// ```dart
-  /// void _showNext() {
-  ///   Navigator.pushReplacementNamed(context, '/jouett/1781');
+  /// void _switchToBrightness() {
+  ///   Navigator.pushReplacementNamed(context, '/settings/brightness');
   /// }
   /// ```
   /// {@end-tool}
   @optionalTypeArgs
-  static Future<T> pushReplacementNamed<T extends Object, TO extends Object>(BuildContext context, String routeName, { TO result }) {
-    return Navigator.of(context).pushReplacementNamed<T, TO>(routeName, result: result);
+  static Future<T> pushReplacementNamed<T extends Object, TO extends Object>(
+    BuildContext context,
+    String routeName, {
+    TO result,
+    Object arguments,
+  }) {
+    return Navigator.of(context).pushReplacementNamed<T, TO>(routeName, arguments: arguments, result: result);
   }
 
   /// Pop the current route off the navigator that most tightly encloses the
@@ -821,19 +894,26 @@
   /// and `TO` is the return value type of the old route.
   /// {@endtemplate}
   ///
+  /// {@macro flutter.widgets.navigator.pushNamed.arguments}
+  ///
   /// {@tool sample}
   ///
   /// Typical usage is as follows:
   ///
   /// ```dart
-  /// void _selectNewYork() {
-  ///   Navigator.popAndPushNamed(context, '/nyc/1776');
+  /// void _selectAccessibility() {
+  ///   Navigator.popAndPushNamed(context, '/settings/accessibility');
   /// }
   /// ```
   /// {@end-tool}
   @optionalTypeArgs
-  static Future<T> popAndPushNamed<T extends Object, TO extends Object>(BuildContext context, String routeName, { TO result }) {
-    return Navigator.of(context).popAndPushNamed<T, TO>(routeName, result: result);
+  static Future<T> popAndPushNamed<T extends Object, TO extends Object>(
+    BuildContext context,
+    String routeName, {
+    TO result,
+    Object arguments,
+   }) {
+    return Navigator.of(context).popAndPushNamed<T, TO>(routeName, arguments: arguments, result: result);
   }
 
   /// Push the route with the given name onto the navigator that most tightly
@@ -875,6 +955,8 @@
   /// The `T` type argument is the type of the return value of the new route.
   /// {@endtemplate}
   ///
+  /// {@macro flutter.widgets.navigator.pushNamed.arguments}
+  ///
   /// {@tool sample}
   ///
   /// Typical usage is as follows:
@@ -886,8 +968,13 @@
   /// ```
   /// {@end-tool}
   @optionalTypeArgs
-  static Future<T> pushNamedAndRemoveUntil<T extends Object>(BuildContext context, String newRouteName, RoutePredicate predicate) {
-    return Navigator.of(context).pushNamedAndRemoveUntil<T>(newRouteName, predicate);
+  static Future<T> pushNamedAndRemoveUntil<T extends Object>(
+    BuildContext context,
+    String newRouteName,
+    RoutePredicate predicate, {
+    Object arguments,
+  }) {
+    return Navigator.of(context).pushNamedAndRemoveUntil<T>(newRouteName, predicate, arguments: arguments);
   }
 
   /// Push the given route onto the navigator that most tightly encloses the
@@ -1329,7 +1416,7 @@
         Navigator.defaultRouteName,
       ];
       final List<Route<dynamic>> plannedInitialRoutes = <Route<dynamic>>[
-        _routeNamed<dynamic>(Navigator.defaultRouteName, allowNull: true),
+        _routeNamed<dynamic>(Navigator.defaultRouteName, allowNull: true, arguments: null),
       ];
       final List<String> routeParts = initialRouteName.split('/');
       if (initialRouteName.isNotEmpty) {
@@ -1337,7 +1424,7 @@
         for (String part in routeParts) {
           routeName += '/$part';
           plannedInitialRouteNames.add(routeName);
-          plannedInitialRoutes.add(_routeNamed<dynamic>(routeName, allowNull: true));
+          plannedInitialRoutes.add(_routeNamed<dynamic>(routeName, allowNull: true, arguments: null));
         }
       }
       if (plannedInitialRoutes.contains(null)) {
@@ -1357,15 +1444,15 @@
           );
           return true;
         }());
-        push(_routeNamed<Object>(Navigator.defaultRouteName));
+        push(_routeNamed<Object>(Navigator.defaultRouteName, arguments: null));
       } else {
         plannedInitialRoutes.forEach(push);
       }
     } else {
       Route<Object> route;
       if (initialRouteName != Navigator.defaultRouteName)
-        route = _routeNamed<Object>(initialRouteName, allowNull: true);
-      route ??= _routeNamed<Object>(Navigator.defaultRouteName);
+        route = _routeNamed<Object>(initialRouteName, allowNull: true, arguments: null);
+      route ??= _routeNamed<Object>(Navigator.defaultRouteName, arguments: null);
       push(route);
     }
     for (Route<dynamic> route in _history)
@@ -1416,12 +1503,13 @@
 
   bool _debugLocked = false; // used to prevent re-entrant calls to push, pop, and friends
 
-  Route<T> _routeNamed<T>(String name, { bool allowNull = false }) {
+  Route<T> _routeNamed<T>(String name, { @required Object arguments, bool allowNull = false }) {
     assert(!_debugLocked);
     assert(name != null);
     final RouteSettings settings = RouteSettings(
       name: name,
       isInitialRoute: _history.isEmpty,
+      arguments: arguments,
     );
     Route<T> route = widget.onGenerateRoute(settings);
     if (route == null && !allowNull) {
@@ -1458,6 +1546,8 @@
   ///
   /// {@macro flutter.widgets.navigator.pushNamed}
   ///
+  /// {@macro flutter.widgets.navigator.pushNamed.arguments}
+  ///
   /// {@tool sample}
   ///
   /// Typical usage is as follows:
@@ -1469,8 +1559,11 @@
   /// ```
   /// {@end-tool}
   @optionalTypeArgs
-  Future<T> pushNamed<T extends Object>(String routeName) {
-    return push<T>(_routeNamed<T>(routeName));
+  Future<T> pushNamed<T extends Object>(
+    String routeName, {
+    Object arguments,
+  }) {
+    return push<T>(_routeNamed<T>(routeName, arguments: arguments));
   }
 
   /// Replace the current route of the navigator by pushing the route named
@@ -1479,6 +1572,8 @@
   ///
   /// {@macro flutter.widgets.navigator.pushReplacementNamed}
   ///
+  /// {@macro flutter.widgets.navigator.pushNamed.arguments}
+  ///
   /// {@tool sample}
   ///
   /// Typical usage is as follows:
@@ -1490,8 +1585,12 @@
   /// ```
   /// {@end-tool}
   @optionalTypeArgs
-  Future<T> pushReplacementNamed<T extends Object, TO extends Object>(String routeName, { TO result }) {
-    return pushReplacement<T, TO>(_routeNamed<T>(routeName), result: result);
+  Future<T> pushReplacementNamed<T extends Object, TO extends Object>(
+    String routeName, {
+    TO result,
+    Object arguments,
+  }) {
+    return pushReplacement<T, TO>(_routeNamed<T>(routeName, arguments: arguments), result: result);
   }
 
   /// Pop the current route off the navigator and push a named route in its
@@ -1499,6 +1598,8 @@
   ///
   /// {@macro flutter.widgets.navigator.popAndPushNamed}
   ///
+  /// {@macro flutter.widgets.navigator.pushNamed.arguments}
+  ///
   /// {@tool sample}
   ///
   /// Typical usage is as follows:
@@ -1510,9 +1611,13 @@
   /// ```
   /// {@end-tool}
   @optionalTypeArgs
-  Future<T> popAndPushNamed<T extends Object, TO extends Object>(String routeName, { TO result }) {
+  Future<T> popAndPushNamed<T extends Object, TO extends Object>(
+    String routeName, {
+    TO result,
+    Object arguments,
+  }) {
     pop<TO>(result);
-    return pushNamed<T>(routeName);
+    return pushNamed<T>(routeName, arguments: arguments);
   }
 
   /// Push the route with the given name onto the navigator, and then remove all
@@ -1520,6 +1625,8 @@
   ///
   /// {@macro flutter.widgets.navigator.pushNamedAndRemoveUntil}
   ///
+  /// {@macro flutter.widgets.navigator.pushNamed.arguments}
+  ///
   /// {@tool sample}
   ///
   /// Typical usage is as follows:
@@ -1531,8 +1638,12 @@
   /// ```
   /// {@end-tool}
   @optionalTypeArgs
-  Future<T> pushNamedAndRemoveUntil<T extends Object>(String newRouteName, RoutePredicate predicate) {
-    return pushAndRemoveUntil<T>(_routeNamed<T>(newRouteName), predicate);
+  Future<T> pushNamedAndRemoveUntil<T extends Object>(
+    String newRouteName,
+    RoutePredicate predicate, {
+    Object arguments,
+  }) {
+    return pushAndRemoveUntil<T>(_routeNamed<T>(newRouteName, arguments: arguments), predicate);
   }
 
   /// Push the given route onto the navigator.
diff --git a/packages/flutter/test/widgets/navigator_test.dart b/packages/flutter/test/widgets/navigator_test.dart
index 70f0d61..f7d96b6 100644
--- a/packages/flutter/test/widgets/navigator_test.dart
+++ b/packages/flutter/test/widgets/navigator_test.dart
@@ -4,6 +4,7 @@
 
 import 'dart:ui';
 
+import 'package:flutter/foundation.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:flutter/material.dart';
 
@@ -870,4 +871,149 @@
 
     semantics.dispose();
   });
+
+  testWidgets('arguments for named routes on Navigator', (WidgetTester tester) async {
+    GlobalKey currentRouteKey;
+    final List<Object> arguments = <Object>[];
+
+    await tester.pumpWidget(MaterialApp(
+      onGenerateRoute: (RouteSettings settings) {
+        arguments.add(settings.arguments);
+        return MaterialPageRoute<void>(
+          settings: settings,
+          builder: (BuildContext context) => Center(key: currentRouteKey = GlobalKey(), child: Text(settings.name)),
+        );
+      },
+    ));
+
+    expect(find.text('/'), findsOneWidget);
+    expect(arguments.single, isNull);
+    arguments.clear();
+
+    Navigator.pushNamed(
+      currentRouteKey.currentContext,
+      '/A',
+      arguments: 'pushNamed',
+    );
+    await tester.pumpAndSettle();
+
+    expect(find.text('/'), findsNothing);
+    expect(find.text('/A'), findsOneWidget);
+    expect(arguments.single, 'pushNamed');
+    arguments.clear();
+
+    Navigator.popAndPushNamed(
+      currentRouteKey.currentContext,
+      '/B',
+      arguments: 'popAndPushNamed',
+    );
+    await tester.pumpAndSettle();
+
+    expect(find.text('/'), findsNothing);
+    expect(find.text('/A'), findsNothing);
+    expect(find.text('/B'), findsOneWidget);
+    expect(arguments.single, 'popAndPushNamed');
+    arguments.clear();
+
+    Navigator.pushNamedAndRemoveUntil(
+      currentRouteKey.currentContext,
+      '/C',
+      (Route<dynamic> route) => route.isFirst,
+      arguments: 'pushNamedAndRemoveUntil',
+    );
+    await tester.pumpAndSettle();
+
+    expect(find.text('/'), findsNothing);
+    expect(find.text('/A'), findsNothing);
+    expect(find.text('/B'), findsNothing);
+    expect(find.text('/C'), findsOneWidget);
+    expect(arguments.single, 'pushNamedAndRemoveUntil');
+    arguments.clear();
+
+    Navigator.pushReplacementNamed(
+      currentRouteKey.currentContext,
+      '/D',
+      arguments: 'pushReplacementNamed',
+    );
+    await tester.pumpAndSettle();
+
+    expect(find.text('/'), findsNothing);
+    expect(find.text('/A'), findsNothing);
+    expect(find.text('/B'), findsNothing);
+    expect(find.text('/C'), findsNothing);
+    expect(find.text('/D'), findsOneWidget);
+    expect(arguments.single, 'pushReplacementNamed');
+    arguments.clear();
+  });
+
+  testWidgets('arguments for named routes on NavigatorState', (WidgetTester tester) async {
+    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
+    final List<Object> arguments = <Object>[];
+
+    await tester.pumpWidget(MaterialApp(
+      navigatorKey: navigatorKey,
+      onGenerateRoute: (RouteSettings settings) {
+        arguments.add(settings.arguments);
+        return MaterialPageRoute<void>(
+          settings: settings,
+          builder: (BuildContext context) => Center(child: Text(settings.name)),
+        );
+      },
+    ));
+
+    expect(find.text('/'), findsOneWidget);
+    expect(arguments.single, isNull);
+    arguments.clear();
+
+    navigatorKey.currentState.pushNamed(
+      '/A',
+      arguments:'pushNamed',
+    );
+    await tester.pumpAndSettle();
+
+    expect(find.text('/'), findsNothing);
+    expect(find.text('/A'), findsOneWidget);
+    expect(arguments.single, 'pushNamed');
+    arguments.clear();
+
+    navigatorKey.currentState.popAndPushNamed(
+      '/B',
+      arguments: 'popAndPushNamed',
+    );
+    await tester.pumpAndSettle();
+
+    expect(find.text('/'), findsNothing);
+    expect(find.text('/A'), findsNothing);
+    expect(find.text('/B'), findsOneWidget);
+    expect(arguments.single, 'popAndPushNamed');
+    arguments.clear();
+
+    navigatorKey.currentState.pushNamedAndRemoveUntil(
+      '/C',
+      (Route<dynamic> route) => route.isFirst,
+      arguments: 'pushNamedAndRemoveUntil',
+    );
+    await tester.pumpAndSettle();
+
+    expect(find.text('/'), findsNothing);
+    expect(find.text('/A'), findsNothing);
+    expect(find.text('/B'), findsNothing);
+    expect(find.text('/C'), findsOneWidget);
+    expect(arguments.single, 'pushNamedAndRemoveUntil');
+    arguments.clear();
+
+    navigatorKey.currentState.pushReplacementNamed(
+      '/D',
+      arguments: 'pushReplacementNamed',
+    );
+    await tester.pumpAndSettle();
+
+    expect(find.text('/'), findsNothing);
+    expect(find.text('/A'), findsNothing);
+    expect(find.text('/B'), findsNothing);
+    expect(find.text('/C'), findsNothing);
+    expect(find.text('/D'), findsOneWidget);
+    expect(arguments.single, 'pushReplacementNamed');
+    arguments.clear();
+  });
 }
diff --git a/packages/flutter/test/widgets/routes_test.dart b/packages/flutter/test/widgets/routes_test.dart
index 2d8e784..b4f4f20 100644
--- a/packages/flutter/test/widgets/routes_test.dart
+++ b/packages/flutter/test/widgets/routes_test.dart
@@ -4,6 +4,7 @@
 
 import 'dart:collection';
 
+import 'package:flutter/foundation.dart';
 import 'package:mockito/mockito.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:flutter/widgets.dart';
@@ -114,6 +115,23 @@
     expect(settings3.isInitialRoute, true);
   });
 
+  testWidgets('Route settings arguments', (WidgetTester tester) async {
+    const RouteSettings settings = RouteSettings(name: 'A');
+    expect(settings.arguments, isNull);
+
+    final Object arguments = Object();
+    final RouteSettings settings2 = RouteSettings(name: 'A', arguments: arguments);
+    expect(settings2.arguments, same(arguments));
+
+    final RouteSettings settings3 = settings2.copyWith();
+    expect(settings3.arguments, equals(arguments));
+
+    final Object arguments2 = Object();
+    final RouteSettings settings4 = settings2.copyWith(arguments: arguments2);
+    expect(settings4.arguments, same(arguments2));
+    expect(settings4.arguments, isNot(same(arguments)));
+  });
+
   testWidgets('Route management - push, replace, pop', (WidgetTester tester) async {
     final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
     await tester.pumpWidget(