Provide a way to override global InheritedWidgets (#14348)
For example, so that the gallery can override the media query globally.
This commit is contained in:
parent
12ceaefb18
commit
c12d120bbf
@ -51,6 +51,7 @@ class GalleryAppState extends State<GalleryApp> {
|
|||||||
bool _showPerformanceOverlay = false;
|
bool _showPerformanceOverlay = false;
|
||||||
bool _checkerboardRasterCacheImages = false;
|
bool _checkerboardRasterCacheImages = false;
|
||||||
bool _checkerboardOffscreenLayers = false;
|
bool _checkerboardOffscreenLayers = false;
|
||||||
|
TextDirection _overrideDirection = TextDirection.ltr;
|
||||||
double _timeDilation = 1.0;
|
double _timeDilation = 1.0;
|
||||||
TargetPlatform _platform;
|
TargetPlatform _platform;
|
||||||
|
|
||||||
@ -139,6 +140,12 @@ class GalleryAppState extends State<GalleryApp> {
|
|||||||
_textScaleFactor = value;
|
_textScaleFactor = value;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
overrideDirection: _overrideDirection,
|
||||||
|
onOverrideDirectionChanged: (TextDirection value) {
|
||||||
|
setState(() {
|
||||||
|
_overrideDirection = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
onSendFeedback: widget.onSendFeedback,
|
onSendFeedback: widget.onSendFeedback,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -155,7 +162,7 @@ class GalleryAppState extends State<GalleryApp> {
|
|||||||
// using named routes, consider the example in the Navigator class documentation:
|
// using named routes, consider the example in the Navigator class documentation:
|
||||||
// https://docs.flutter.io/flutter/widgets/Navigator-class.html
|
// https://docs.flutter.io/flutter/widgets/Navigator-class.html
|
||||||
_kRoutes[item.routeName] = (BuildContext context) {
|
_kRoutes[item.routeName] = (BuildContext context) {
|
||||||
return _applyScaleFactor(item.buildRoute(context));
|
return item.buildRoute(context);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,6 +175,12 @@ class GalleryAppState extends State<GalleryApp> {
|
|||||||
checkerboardOffscreenLayers: _checkerboardOffscreenLayers,
|
checkerboardOffscreenLayers: _checkerboardOffscreenLayers,
|
||||||
routes: _kRoutes,
|
routes: _kRoutes,
|
||||||
home: _applyScaleFactor(home),
|
home: _applyScaleFactor(home),
|
||||||
|
builder: (BuildContext context, Widget child) {
|
||||||
|
return new Directionality(
|
||||||
|
textDirection: _overrideDirection,
|
||||||
|
child: _applyScaleFactor(child),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,8 @@ class GalleryDrawer extends StatelessWidget {
|
|||||||
this.checkerboardOffscreenLayers,
|
this.checkerboardOffscreenLayers,
|
||||||
this.onCheckerboardOffscreenLayersChanged,
|
this.onCheckerboardOffscreenLayersChanged,
|
||||||
this.onPlatformChanged,
|
this.onPlatformChanged,
|
||||||
|
this.overrideDirection: TextDirection.ltr,
|
||||||
|
this.onOverrideDirectionChanged,
|
||||||
this.onSendFeedback,
|
this.onSendFeedback,
|
||||||
}) : assert(onThemeChanged != null),
|
}) : assert(onThemeChanged != null),
|
||||||
assert(onTimeDilationChanged != null),
|
assert(onTimeDilationChanged != null),
|
||||||
@ -145,6 +147,9 @@ class GalleryDrawer extends StatelessWidget {
|
|||||||
|
|
||||||
final ValueChanged<TargetPlatform> onPlatformChanged;
|
final ValueChanged<TargetPlatform> onPlatformChanged;
|
||||||
|
|
||||||
|
final TextDirection overrideDirection;
|
||||||
|
final ValueChanged<TextDirection> onOverrideDirectionChanged;
|
||||||
|
|
||||||
final VoidCallback onSendFeedback;
|
final VoidCallback onSendFeedback;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -220,6 +225,16 @@ class GalleryDrawer extends StatelessWidget {
|
|||||||
selected: timeDilation != 1.0,
|
selected: timeDilation != 1.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final Widget overrideDirectionItem = new CheckboxListTile(
|
||||||
|
title: const Text('Force RTL'),
|
||||||
|
value: overrideDirection == TextDirection.rtl,
|
||||||
|
onChanged: (bool value) {
|
||||||
|
onOverrideDirectionChanged(value ? TextDirection.rtl : TextDirection.ltr);
|
||||||
|
},
|
||||||
|
secondary: const Icon(Icons.format_textdirection_r_to_l),
|
||||||
|
selected: overrideDirection == TextDirection.rtl,
|
||||||
|
);
|
||||||
|
|
||||||
final Widget sendFeedbackItem = new ListTile(
|
final Widget sendFeedbackItem = new ListTile(
|
||||||
leading: const Icon(Icons.report),
|
leading: const Icon(Icons.report),
|
||||||
title: const Text('Send feedback'),
|
title: const Text('Send feedback'),
|
||||||
@ -285,6 +300,7 @@ class GalleryDrawer extends StatelessWidget {
|
|||||||
allDrawerItems.addAll(textSizeItems);
|
allDrawerItems.addAll(textSizeItems);
|
||||||
|
|
||||||
allDrawerItems..addAll(<Widget>[
|
allDrawerItems..addAll(<Widget>[
|
||||||
|
overrideDirectionItem,
|
||||||
const Divider(),
|
const Divider(),
|
||||||
animateSlowlyItem,
|
animateSlowlyItem,
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
@ -77,6 +77,8 @@ class GalleryHome extends StatefulWidget {
|
|||||||
this.checkerboardOffscreenLayers,
|
this.checkerboardOffscreenLayers,
|
||||||
this.onCheckerboardOffscreenLayersChanged,
|
this.onCheckerboardOffscreenLayersChanged,
|
||||||
this.onPlatformChanged,
|
this.onPlatformChanged,
|
||||||
|
this.overrideDirection: TextDirection.ltr,
|
||||||
|
this.onOverrideDirectionChanged,
|
||||||
this.onSendFeedback,
|
this.onSendFeedback,
|
||||||
}) : assert(onThemeChanged != null),
|
}) : assert(onThemeChanged != null),
|
||||||
assert(onTimeDilationChanged != null),
|
assert(onTimeDilationChanged != null),
|
||||||
@ -102,6 +104,9 @@ class GalleryHome extends StatefulWidget {
|
|||||||
|
|
||||||
final ValueChanged<TargetPlatform> onPlatformChanged;
|
final ValueChanged<TargetPlatform> onPlatformChanged;
|
||||||
|
|
||||||
|
final TextDirection overrideDirection;
|
||||||
|
final ValueChanged<TextDirection> onOverrideDirectionChanged;
|
||||||
|
|
||||||
final VoidCallback onSendFeedback;
|
final VoidCallback onSendFeedback;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -177,6 +182,8 @@ class GalleryHomeState extends State<GalleryHome> with SingleTickerProviderState
|
|||||||
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
|
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
|
||||||
onCheckerboardOffscreenLayersChanged: widget.onCheckerboardOffscreenLayersChanged,
|
onCheckerboardOffscreenLayersChanged: widget.onCheckerboardOffscreenLayersChanged,
|
||||||
onPlatformChanged: widget.onPlatformChanged,
|
onPlatformChanged: widget.onPlatformChanged,
|
||||||
|
overrideDirection: widget.overrideDirection,
|
||||||
|
onOverrideDirectionChanged: widget.onOverrideDirectionChanged,
|
||||||
onSendFeedback: widget.onSendFeedback,
|
onSendFeedback: widget.onSendFeedback,
|
||||||
),
|
),
|
||||||
body: new CustomScrollView(
|
body: new CustomScrollView(
|
||||||
|
@ -69,7 +69,7 @@ void main() {
|
|||||||
expect(newTextSize, equals(origTextSize));
|
expect(newTextSize, equals(origTextSize));
|
||||||
|
|
||||||
// Scroll to the bottom of the menu.
|
// Scroll to the bottom of the menu.
|
||||||
await tester.drag(find.text('Small'), const Offset(0.0, -450.0));
|
await tester.drag(find.text('Small'), const Offset(0.0, -1000.0));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
|
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ Future<Null> runSmokeTest(WidgetTester tester) async {
|
|||||||
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
|
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
|
||||||
|
|
||||||
// Scroll the 'Send feedback' item into view.
|
// Scroll the 'Send feedback' item into view.
|
||||||
await tester.drag(find.text('Small'), const Offset(0.0, -450.0));
|
await tester.drag(find.text('Small'), const Offset(0.0, -1000.0));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
|
await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
|
||||||
|
|
||||||
|
@ -51,13 +51,16 @@ const TextStyle _errorTextStyle = const TextStyle(
|
|||||||
///
|
///
|
||||||
/// 4. Finally if all else fails [onUnknownRoute] is called.
|
/// 4. Finally if all else fails [onUnknownRoute] is called.
|
||||||
///
|
///
|
||||||
/// At least one of these options must handle the `/` route, since it is used
|
/// If a [Navigator] is created, at least one of these options must handle the
|
||||||
/// when an invalid [initialRoute] is specified on startup (e.g. by another
|
/// `/` route, since it is used when an invalid [initialRoute] is specified on
|
||||||
/// application launching this one with an intent on Android; see
|
/// startup (e.g. by another application launching this one with an intent on
|
||||||
/// [Window.defaultRouteName]).
|
/// Android; see [Window.defaultRouteName]).
|
||||||
///
|
///
|
||||||
/// This widget also configures the top-level [Navigator]'s observer to perform
|
/// This widget also configures the observer of the top-level [Navigator] (if
|
||||||
/// [Hero] animations.
|
/// any) to perform [Hero] animations.
|
||||||
|
///
|
||||||
|
/// If [home], [routes], [onGenerateRoute], and [onUnknownRoute] are all null,
|
||||||
|
/// and [builder] is not null, then no [Navigator] is created.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
@ -68,8 +71,8 @@ const TextStyle _errorTextStyle = const TextStyle(
|
|||||||
class MaterialApp extends StatefulWidget {
|
class MaterialApp extends StatefulWidget {
|
||||||
/// Creates a MaterialApp.
|
/// Creates a MaterialApp.
|
||||||
///
|
///
|
||||||
/// At least one of [home], [routes], or [onGenerateRoute] must be given. If
|
/// At least one of [home], [routes], [onGenerateRoute], or [builder] must be
|
||||||
/// only [routes] is given, it must include an entry for the
|
/// non-null. If only [routes] is given, it must include an entry for the
|
||||||
/// [Navigator.defaultRouteName] (`/`), since that is the route used when the
|
/// [Navigator.defaultRouteName] (`/`), since that is the route used when the
|
||||||
/// application is launched with an intent that specifies an otherwise
|
/// application is launched with an intent that specifies an otherwise
|
||||||
/// unsupported route.
|
/// unsupported route.
|
||||||
@ -80,35 +83,29 @@ class MaterialApp extends StatefulWidget {
|
|||||||
MaterialApp({ // can't be const because the asserts use methods on Map :-(
|
MaterialApp({ // can't be const because the asserts use methods on Map :-(
|
||||||
Key key,
|
Key key,
|
||||||
this.navigatorKey,
|
this.navigatorKey,
|
||||||
this.title: '',
|
|
||||||
this.onGenerateTitle,
|
|
||||||
this.color,
|
|
||||||
this.theme,
|
|
||||||
this.home,
|
this.home,
|
||||||
this.routes: const <String, WidgetBuilder>{},
|
this.routes: const <String, WidgetBuilder>{},
|
||||||
this.initialRoute,
|
this.initialRoute,
|
||||||
this.onGenerateRoute,
|
this.onGenerateRoute,
|
||||||
this.onUnknownRoute,
|
this.onUnknownRoute,
|
||||||
|
this.navigatorObservers: const <NavigatorObserver>[],
|
||||||
|
this.builder,
|
||||||
|
this.title: '',
|
||||||
|
this.onGenerateTitle,
|
||||||
|
this.color,
|
||||||
|
this.theme,
|
||||||
this.locale,
|
this.locale,
|
||||||
this.localizationsDelegates,
|
this.localizationsDelegates,
|
||||||
this.localeResolutionCallback,
|
this.localeResolutionCallback,
|
||||||
this.supportedLocales: const <Locale>[const Locale('en', 'US')],
|
this.supportedLocales: const <Locale>[const Locale('en', 'US')],
|
||||||
this.navigatorObservers: const <NavigatorObserver>[],
|
|
||||||
this.debugShowMaterialGrid: false,
|
this.debugShowMaterialGrid: false,
|
||||||
this.showPerformanceOverlay: false,
|
this.showPerformanceOverlay: false,
|
||||||
this.checkerboardRasterCacheImages: false,
|
this.checkerboardRasterCacheImages: false,
|
||||||
this.checkerboardOffscreenLayers: false,
|
this.checkerboardOffscreenLayers: false,
|
||||||
this.showSemanticsDebugger: false,
|
this.showSemanticsDebugger: false,
|
||||||
this.debugShowCheckedModeBanner: true
|
this.debugShowCheckedModeBanner: true,
|
||||||
}) : assert(title != null),
|
}) : assert(routes != null),
|
||||||
assert(routes != null),
|
|
||||||
assert(navigatorObservers != null),
|
assert(navigatorObservers != null),
|
||||||
assert(debugShowMaterialGrid != null),
|
|
||||||
assert(showPerformanceOverlay != null),
|
|
||||||
assert(checkerboardRasterCacheImages != null),
|
|
||||||
assert(checkerboardOffscreenLayers != null),
|
|
||||||
assert(showSemanticsDebugger != null),
|
|
||||||
assert(debugShowCheckedModeBanner != null),
|
|
||||||
assert(
|
assert(
|
||||||
home == null ||
|
home == null ||
|
||||||
!routes.containsKey(Navigator.defaultRouteName),
|
!routes.containsKey(Navigator.defaultRouteName),
|
||||||
@ -116,6 +113,7 @@ class MaterialApp extends StatefulWidget {
|
|||||||
'cannot include an entry for "/", since it would be redundant.'
|
'cannot include an entry for "/", since it would be redundant.'
|
||||||
),
|
),
|
||||||
assert(
|
assert(
|
||||||
|
builder != null ||
|
||||||
home != null ||
|
home != null ||
|
||||||
routes.containsKey(Navigator.defaultRouteName) ||
|
routes.containsKey(Navigator.defaultRouteName) ||
|
||||||
onGenerateRoute != null ||
|
onGenerateRoute != null ||
|
||||||
@ -124,9 +122,35 @@ class MaterialApp extends StatefulWidget {
|
|||||||
'or the routes table must include an entry for "/", '
|
'or the routes table must include an entry for "/", '
|
||||||
'or there must be on onGenerateRoute callback specified, '
|
'or there must be on onGenerateRoute callback specified, '
|
||||||
'or there must be an onUnknownRoute 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 '
|
'because otherwise there is nothing to fall back on if the '
|
||||||
'app is started with an intent that specifies an unknown route.'
|
'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),
|
||||||
|
assert(checkerboardRasterCacheImages != null),
|
||||||
|
assert(checkerboardOffscreenLayers != null),
|
||||||
|
assert(showSemanticsDebugger != null),
|
||||||
|
assert(debugShowCheckedModeBanner != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
/// A key to use when building the [Navigator].
|
/// A key to use when building the [Navigator].
|
||||||
@ -140,8 +164,173 @@ class MaterialApp extends StatefulWidget {
|
|||||||
/// application state in the process; in that case, the [navigatorObservers]
|
/// application state in the process; in that case, the [navigatorObservers]
|
||||||
/// must also be changed, since the previous observers will be attached to the
|
/// must also be changed, since the previous observers will be attached to the
|
||||||
/// previous navigator.
|
/// 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.
|
||||||
final GlobalKey<NavigatorState> 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).
|
||||||
|
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 [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.
|
||||||
|
final Map<String, WidgetBuilder> routes;
|
||||||
|
|
||||||
|
/// The name of the first route to show, if a [Navigator] is built.
|
||||||
|
///
|
||||||
|
/// Defaults to [Window.defaultRouteName], which may be overridden by the code
|
||||||
|
/// that launched the application.
|
||||||
|
///
|
||||||
|
/// If the route contains slashes, then it is treated as a "deep link", and
|
||||||
|
/// before this route is pushed, the routes leading to this one are pushed
|
||||||
|
/// also. For example, if the route was `/a/b/c`, then the app would start
|
||||||
|
/// with the three routes `/a`, `/a/b`, and `/a/b/c` loaded, in that order.
|
||||||
|
///
|
||||||
|
/// If any part of this process fails to generate routes, then the
|
||||||
|
/// [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.
|
||||||
|
///
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
/// The route generator callback used when the app is navigated to a
|
||||||
|
/// named route.
|
||||||
|
///
|
||||||
|
/// This is used if [routes] does not contain the requested route.
|
||||||
|
///
|
||||||
|
/// If this returns null when building the routes to handle the specified
|
||||||
|
/// [initialRoute], then all the routes are discarded and
|
||||||
|
/// [Navigator.defaultRouteName] is used instead (`/`). See [initialRoute].
|
||||||
|
///
|
||||||
|
/// During normal app operation, the [onGenerateRoute] callback will only be
|
||||||
|
/// applied to route names pushed by the application, and so should never
|
||||||
|
/// return 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.
|
||||||
|
final RouteFactory onGenerateRoute;
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// The default implementation pushes a route that displays an ugly error
|
||||||
|
/// message.
|
||||||
|
///
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
/// The list of observers for the [Navigator] created for this app.
|
||||||
|
///
|
||||||
|
/// This list must be replaced by a list of newly-created observers if the
|
||||||
|
/// [navigatorKey] is changed.
|
||||||
|
///
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
/// A builder for inserting widgets above the [Navigator] but below the other
|
||||||
|
/// widgets created by the [MaterialApp] widget, or for replacing the
|
||||||
|
/// [Navigator] entirely.
|
||||||
|
///
|
||||||
|
/// For example, from the [BuildContext] passed to this method, the
|
||||||
|
/// [Directionality], [Localizations], [DefaultTextStyle], [MediaQuery], etc,
|
||||||
|
/// are all available. They can also be overridden in a way that impacts all
|
||||||
|
/// the routes in the [Navigator].
|
||||||
|
///
|
||||||
|
/// This is rarely useful, but can be used in applications that wish to
|
||||||
|
/// override those defaults, e.g. to force the application into right-to-left
|
||||||
|
/// mode despite being in English, or to override the [MediaQuery] metrics
|
||||||
|
/// (e.g. to leave a gap for advertisements shown by a plugin from OEM code).
|
||||||
|
///
|
||||||
|
/// The [builder] callback is passed two arguments, the [BuildContext] (as
|
||||||
|
/// `context`) and a [Navigator] widget (as `child`).
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
/// function.
|
||||||
|
///
|
||||||
|
/// For specifically overriding the [title] with a value based on the
|
||||||
|
/// [Localizations], consider [onGenerateTitle] instead.
|
||||||
|
final TransitionBuilder builder;
|
||||||
|
|
||||||
/// A one-line description used by the device to identify the app for the user.
|
/// A one-line description used by the device to identify the app for the user.
|
||||||
///
|
///
|
||||||
/// On Android the titles appear above the task manager's app snapshots which are
|
/// On Android the titles appear above the task manager's app snapshots which are
|
||||||
@ -172,21 +361,6 @@ class MaterialApp extends StatefulWidget {
|
|||||||
/// The colors to use for the application's widgets.
|
/// The colors to use for the application's widgets.
|
||||||
final ThemeData theme;
|
final ThemeData theme;
|
||||||
|
|
||||||
/// 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.
|
|
||||||
final Widget home;
|
|
||||||
|
|
||||||
/// The primary color to use for the application in the operating system
|
/// The primary color to use for the application in the operating system
|
||||||
/// interface.
|
/// interface.
|
||||||
///
|
///
|
||||||
@ -194,71 +368,6 @@ class MaterialApp extends StatefulWidget {
|
|||||||
/// application switcher.
|
/// application switcher.
|
||||||
final Color color;
|
final Color color;
|
||||||
|
|
||||||
/// 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 [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.
|
|
||||||
final Map<String, WidgetBuilder> routes;
|
|
||||||
|
|
||||||
/// The name of the first route to show.
|
|
||||||
///
|
|
||||||
/// Defaults to [Window.defaultRouteName], which may be overridden by the code
|
|
||||||
/// that launched the application.
|
|
||||||
///
|
|
||||||
/// If the route contains slashes, then it is treated as a "deep link", and
|
|
||||||
/// before this route is pushed, the routes leading to this one are pushed
|
|
||||||
/// also. For example, if the route was `/a/b/c`, then the app would start
|
|
||||||
/// with the three routes `/a`, `/a/b`, and `/a/b/c` loaded, in that order.
|
|
||||||
///
|
|
||||||
/// If any part of this process fails to generate routes, then the
|
|
||||||
/// [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.
|
|
||||||
///
|
|
||||||
/// 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;
|
|
||||||
|
|
||||||
/// The route generator callback used when the app is navigated to a
|
|
||||||
/// named route.
|
|
||||||
///
|
|
||||||
/// This is used if [routes] does not contain the requested route.
|
|
||||||
///
|
|
||||||
/// If this returns null when building the routes to handle the specified
|
|
||||||
/// [initialRoute], then all the routes are discarded and
|
|
||||||
/// [Navigator.defaultRouteName] is used instead (`/`). See [initialRoute].
|
|
||||||
///
|
|
||||||
/// During normal app operation, the [onGenerateRoute] callback will only be
|
|
||||||
/// applied to route names pushed by the application, and so should never
|
|
||||||
/// return null.
|
|
||||||
final RouteFactory onGenerateRoute;
|
|
||||||
|
|
||||||
/// 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.
|
|
||||||
///
|
|
||||||
/// The default implementation pushes a route that displays an ugly error
|
|
||||||
/// message.
|
|
||||||
final RouteFactory onUnknownRoute;
|
|
||||||
|
|
||||||
/// The initial locale for this app's [Localizations] widget.
|
/// The initial locale for this app's [Localizations] widget.
|
||||||
///
|
///
|
||||||
/// If the `locale` is null the system's locale value is used.
|
/// If the `locale` is null the system's locale value is used.
|
||||||
@ -417,12 +526,6 @@ class MaterialApp extends StatefulWidget {
|
|||||||
/// representative of what will happen in release mode.
|
/// representative of what will happen in release mode.
|
||||||
final bool debugShowCheckedModeBanner;
|
final bool debugShowCheckedModeBanner;
|
||||||
|
|
||||||
/// The list of observers for the [Navigator] created for this app.
|
|
||||||
///
|
|
||||||
/// This list must be replaced by a list of newly-created observers if the
|
|
||||||
/// [navigatorKey] is changed.
|
|
||||||
final List<NavigatorObserver> navigatorObservers;
|
|
||||||
|
|
||||||
/// Turns on a [GridPaper] overlay that paints a baseline grid
|
/// Turns on a [GridPaper] overlay that paints a baseline grid
|
||||||
/// Material apps.
|
/// Material apps.
|
||||||
///
|
///
|
||||||
@ -469,6 +572,7 @@ class _MaterialAppState extends State<MaterialApp> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_heroController = new HeroController(createRectTween: _createRectTween);
|
_heroController = new HeroController(createRectTween: _createRectTween);
|
||||||
|
_updateNavigator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -481,17 +585,19 @@ class _MaterialAppState extends State<MaterialApp> {
|
|||||||
// Navigator has a GlobalKey).
|
// Navigator has a GlobalKey).
|
||||||
_heroController = new HeroController(createRectTween: _createRectTween);
|
_heroController = new HeroController(createRectTween: _createRectTween);
|
||||||
}
|
}
|
||||||
|
_updateNavigator();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combine the Localizations for Material with the ones contributed
|
bool _haveNavigator;
|
||||||
// by the localizationsDelegates parameter, if any. Only the first delegate
|
List<NavigatorObserver> _navigatorObservers;
|
||||||
// of a particular LocalizationsDelegate.type is loaded so the
|
|
||||||
// localizationsDelegate parameter can be used to override
|
void _updateNavigator() {
|
||||||
// _MaterialLocalizationsDelegate.
|
_haveNavigator = widget.home != null ||
|
||||||
Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
|
widget.routes.isNotEmpty ||
|
||||||
if (widget.localizationsDelegates != null)
|
widget.onGenerateRoute != null ||
|
||||||
yield* widget.localizationsDelegates;
|
widget.onUnknownRoute != null;
|
||||||
yield DefaultMaterialLocalizations.delegate;
|
_navigatorObservers = new List<NavigatorObserver>.from(widget.navigatorObservers)
|
||||||
|
..add(_heroController);
|
||||||
}
|
}
|
||||||
|
|
||||||
RectTween _createRectTween(Rect begin, Rect end) {
|
RectTween _createRectTween(Rect begin, Rect end) {
|
||||||
@ -548,6 +654,17 @@ class _MaterialAppState extends State<MaterialApp> {
|
|||||||
return result;
|
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
|
||||||
|
// localizationsDelegate parameter can be used to override
|
||||||
|
// _MaterialLocalizationsDelegate.
|
||||||
|
Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
|
||||||
|
if (widget.localizationsDelegates != null)
|
||||||
|
yield* widget.localizationsDelegates;
|
||||||
|
yield DefaultMaterialLocalizations.delegate;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ThemeData theme = widget.theme ?? new ThemeData.fallback();
|
final ThemeData theme = widget.theme ?? new ThemeData.fallback();
|
||||||
@ -557,17 +674,16 @@ class _MaterialAppState extends State<MaterialApp> {
|
|||||||
child: new WidgetsApp(
|
child: new WidgetsApp(
|
||||||
key: new GlobalObjectKey(this),
|
key: new GlobalObjectKey(this),
|
||||||
navigatorKey: widget.navigatorKey,
|
navigatorKey: widget.navigatorKey,
|
||||||
|
navigatorObservers: _haveNavigator ? _navigatorObservers : null,
|
||||||
|
initialRoute: widget.initialRoute,
|
||||||
|
onGenerateRoute: _haveNavigator ? _onGenerateRoute : null,
|
||||||
|
onUnknownRoute: _haveNavigator ? _onUnknownRoute : null,
|
||||||
|
builder: widget.builder,
|
||||||
title: widget.title,
|
title: widget.title,
|
||||||
onGenerateTitle: widget.onGenerateTitle,
|
onGenerateTitle: widget.onGenerateTitle,
|
||||||
textStyle: _errorTextStyle,
|
textStyle: _errorTextStyle,
|
||||||
// blue is the primary color of the default theme
|
// blue is the primary color of the default theme
|
||||||
color: widget.color ?? theme?.primaryColor ?? Colors.blue,
|
color: widget.color ?? theme?.primaryColor ?? Colors.blue,
|
||||||
navigatorObservers:
|
|
||||||
new List<NavigatorObserver>.from(widget.navigatorObservers)
|
|
||||||
..add(_heroController),
|
|
||||||
initialRoute: widget.initialRoute,
|
|
||||||
onGenerateRoute: _onGenerateRoute,
|
|
||||||
onUnknownRoute: _onUnknownRoute,
|
|
||||||
locale: widget.locale,
|
locale: widget.locale,
|
||||||
localizationsDelegates: _localizationsDelegates,
|
localizationsDelegates: _localizationsDelegates,
|
||||||
localeResolutionCallback: widget.localeResolutionCallback,
|
localeResolutionCallback: widget.localeResolutionCallback,
|
||||||
|
@ -53,29 +53,33 @@ typedef String GenerateAppTitle(BuildContext context);
|
|||||||
/// See also: [CheckedModeBanner], [DefaultTextStyle], [MediaQuery],
|
/// See also: [CheckedModeBanner], [DefaultTextStyle], [MediaQuery],
|
||||||
/// [Localizations], [Title], [Navigator], [Overlay], [SemanticsDebugger] (the
|
/// [Localizations], [Title], [Navigator], [Overlay], [SemanticsDebugger] (the
|
||||||
/// widgets wrapped by this one).
|
/// widgets wrapped by this one).
|
||||||
///
|
|
||||||
/// The [onGenerateRoute] argument is required, and corresponds to
|
|
||||||
/// [Navigator.onGenerateRoute].
|
|
||||||
class WidgetsApp extends StatefulWidget {
|
class WidgetsApp extends StatefulWidget {
|
||||||
/// Creates a widget that wraps a number of widgets that are commonly
|
/// Creates a widget that wraps a number of widgets that are commonly
|
||||||
/// required for an application.
|
/// required for an application.
|
||||||
///
|
///
|
||||||
/// The boolean arguments, [color], [navigatorObservers], and
|
/// The boolean arguments, [color], and [navigatorObservers] must not be null.
|
||||||
/// [onGenerateRoute] 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
|
||||||
|
/// 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]
|
||||||
|
/// must have their default values, as they will have no effect.
|
||||||
///
|
///
|
||||||
/// The `supportedLocales` argument must be a list of one or more elements.
|
/// The `supportedLocales` argument must be a list of one or more elements.
|
||||||
/// By default supportedLocales is `[const Locale('en', 'US')]`.
|
/// By default supportedLocales is `[const Locale('en', 'US')]`.
|
||||||
WidgetsApp({ // can't be const because the asserts use methods on Iterable :-(
|
WidgetsApp({ // can't be const because the asserts use methods on Iterable :-(
|
||||||
Key key,
|
Key key,
|
||||||
this.navigatorKey,
|
this.navigatorKey,
|
||||||
@required this.onGenerateRoute,
|
this.onGenerateRoute,
|
||||||
this.onUnknownRoute,
|
this.onUnknownRoute,
|
||||||
|
this.navigatorObservers: const <NavigatorObserver>[],
|
||||||
|
this.initialRoute,
|
||||||
|
this.builder,
|
||||||
this.title: '',
|
this.title: '',
|
||||||
this.onGenerateTitle,
|
this.onGenerateTitle,
|
||||||
this.textStyle,
|
this.textStyle,
|
||||||
@required this.color,
|
@required this.color,
|
||||||
this.navigatorObservers: const <NavigatorObserver>[],
|
|
||||||
this.initialRoute,
|
|
||||||
this.locale,
|
this.locale,
|
||||||
this.localizationsDelegates,
|
this.localizationsDelegates,
|
||||||
this.localeResolutionCallback,
|
this.localeResolutionCallback,
|
||||||
@ -87,10 +91,14 @@ class WidgetsApp extends StatefulWidget {
|
|||||||
this.debugShowWidgetInspector: false,
|
this.debugShowWidgetInspector: false,
|
||||||
this.debugShowCheckedModeBanner: true,
|
this.debugShowCheckedModeBanner: true,
|
||||||
this.inspectorSelectButtonBuilder,
|
this.inspectorSelectButtonBuilder,
|
||||||
}) : assert(title != null),
|
}) : assert(navigatorObservers != null),
|
||||||
assert(onGenerateRoute != 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(title != null),
|
||||||
assert(color != null),
|
assert(color != null),
|
||||||
assert(navigatorObservers != null),
|
|
||||||
assert(supportedLocales != null && supportedLocales.isNotEmpty),
|
assert(supportedLocales != null && supportedLocales.isNotEmpty),
|
||||||
assert(showPerformanceOverlay != null),
|
assert(showPerformanceOverlay != null),
|
||||||
assert(checkerboardRasterCacheImages != null),
|
assert(checkerboardRasterCacheImages != null),
|
||||||
@ -111,8 +119,109 @@ class WidgetsApp extends StatefulWidget {
|
|||||||
/// application state in the process; in that case, the [navigatorObservers]
|
/// application state in the process; in that case, the [navigatorObservers]
|
||||||
/// must also be changed, since the previous observers will be attached to the
|
/// must also be changed, since the previous observers will be attached to the
|
||||||
/// previous navigator.
|
/// previous navigator.
|
||||||
|
///
|
||||||
|
/// The [Navigator] is only built if [onGenerateRoute] is not null; if it is
|
||||||
|
/// null, [navigatorKey] must also be null.
|
||||||
final GlobalKey<NavigatorState> navigatorKey;
|
final GlobalKey<NavigatorState> navigatorKey;
|
||||||
|
|
||||||
|
/// The route generator callback used when the app is navigated to a
|
||||||
|
/// named route.
|
||||||
|
///
|
||||||
|
/// If this returns null when building the routes to handle the specified
|
||||||
|
/// [initialRoute], then all the routes are discarded and
|
||||||
|
/// [Navigator.defaultRouteName] is used instead (`/`). See [initialRoute].
|
||||||
|
///
|
||||||
|
/// During normal app operation, the [onGenerateRoute] callback will only be
|
||||||
|
/// applied to route names pushed by the application, and so should never
|
||||||
|
/// return null.
|
||||||
|
///
|
||||||
|
/// The [Navigator] is only built if [onGenerateRoute] is not null. If
|
||||||
|
/// [onGenerateRoute] is null, the [builder] must be non-null.
|
||||||
|
final RouteFactory onGenerateRoute;
|
||||||
|
|
||||||
|
/// Called when [onGenerateRoute] fails to generate a route.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// The [Navigator] is only built if [onGenerateRoute] is not null; if it is
|
||||||
|
/// null, [onUnknownRoute] must also be null.
|
||||||
|
final RouteFactory onUnknownRoute;
|
||||||
|
|
||||||
|
/// The name of the first route to show.
|
||||||
|
///
|
||||||
|
/// Defaults to [Window.defaultRouteName], which may be overridden by the code
|
||||||
|
/// that launched the application.
|
||||||
|
///
|
||||||
|
/// If the route contains slashes, then it is treated as a "deep link", and
|
||||||
|
/// before this route is pushed, the routes leading to this one are pushed
|
||||||
|
/// also. For example, if the route was `/a/b/c`, then the app would start
|
||||||
|
/// with the three routes `/a`, `/a/b`, and `/a/b/c` loaded, in that order.
|
||||||
|
///
|
||||||
|
/// If any part of this process fails to generate routes, then the
|
||||||
|
/// [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.
|
||||||
|
///
|
||||||
|
/// The [Navigator] is only built if [onGenerateRoute] is not null; if it is
|
||||||
|
/// null, [initialRoute] must also 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;
|
||||||
|
|
||||||
|
/// The list of observers for the [Navigator] created for this app.
|
||||||
|
///
|
||||||
|
/// This list must be replaced by a list of newly-created observers if the
|
||||||
|
/// [navigatorKey] is changed.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
final List<NavigatorObserver> navigatorObservers;
|
||||||
|
|
||||||
|
/// A builder for inserting widgets above the [Navigator] but below the other
|
||||||
|
/// widgets created by the [WidgetsApp] widget, or for replacing the
|
||||||
|
/// [Navigator] entirely.
|
||||||
|
///
|
||||||
|
/// For example, from the [BuildContext] passed to this method, the
|
||||||
|
/// [Directionality], [Localizations], [DefaultTextStyle], [MediaQuery], etc,
|
||||||
|
/// are all available. They can also be overridden in a way that impacts all
|
||||||
|
/// the routes in the [Navigator].
|
||||||
|
///
|
||||||
|
/// This is rarely useful, but can be used in applications that wish to
|
||||||
|
/// override those defaults, e.g. to force the application into right-to-left
|
||||||
|
/// mode despite being in English, or to override the [MediaQuery] metrics
|
||||||
|
/// (e.g. to leave a gap for advertisements shown by a plugin from OEM code).
|
||||||
|
///
|
||||||
|
/// The [builder] callback is passed two arguments, the [BuildContext] (as
|
||||||
|
/// `context`) and a [Navigator] widget (as `child`).
|
||||||
|
///
|
||||||
|
/// 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 [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 [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.
|
||||||
|
///
|
||||||
|
/// For specifically overriding the [title] with a value based on the
|
||||||
|
/// [Localizations], consider [onGenerateTitle] instead.
|
||||||
|
final TransitionBuilder builder;
|
||||||
|
|
||||||
/// A one-line description used by the device to identify the app for the user.
|
/// A one-line description used by the device to identify the app for the user.
|
||||||
///
|
///
|
||||||
/// On Android the titles appear above the task manager's app snapshots which are
|
/// On Android the titles appear above the task manager's app snapshots which are
|
||||||
@ -146,50 +255,6 @@ class WidgetsApp extends StatefulWidget {
|
|||||||
/// application switcher.
|
/// application switcher.
|
||||||
final Color color;
|
final Color color;
|
||||||
|
|
||||||
/// The route generator callback used when the app is navigated to a
|
|
||||||
/// named route.
|
|
||||||
///
|
|
||||||
/// If this returns null when building the routes to handle the specified
|
|
||||||
/// [initialRoute], then all the routes are discarded and
|
|
||||||
/// [Navigator.defaultRouteName] is used instead (`/`). See [initialRoute].
|
|
||||||
///
|
|
||||||
/// During normal app operation, the [onGenerateRoute] callback will only be
|
|
||||||
/// applied to route names pushed by the application, and so should never
|
|
||||||
/// return null.
|
|
||||||
final RouteFactory onGenerateRoute;
|
|
||||||
|
|
||||||
/// Called when [onGenerateRoute] fails to generate a route.
|
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
final RouteFactory onUnknownRoute;
|
|
||||||
|
|
||||||
/// The name of the first route to show.
|
|
||||||
///
|
|
||||||
/// Defaults to [Window.defaultRouteName], which may be overridden by the code
|
|
||||||
/// that launched the application.
|
|
||||||
///
|
|
||||||
/// If the route contains slashes, then it is treated as a "deep link", and
|
|
||||||
/// before this route is pushed, the routes leading to this one are pushed
|
|
||||||
/// also. For example, if the route was `/a/b/c`, then the app would start
|
|
||||||
/// with the three routes `/a`, `/a/b`, and `/a/b/c` loaded, in that order.
|
|
||||||
///
|
|
||||||
/// If any part of this process fails to generate routes, then the
|
|
||||||
/// [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.
|
|
||||||
///
|
|
||||||
/// 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;
|
|
||||||
|
|
||||||
/// The initial locale for this app's [Localizations] widget.
|
/// The initial locale for this app's [Localizations] widget.
|
||||||
///
|
///
|
||||||
/// If the 'locale' is null the system's locale value is used.
|
/// If the 'locale' is null the system's locale value is used.
|
||||||
@ -298,12 +363,6 @@ class WidgetsApp extends StatefulWidget {
|
|||||||
/// representative of what will happen in release mode.
|
/// representative of what will happen in release mode.
|
||||||
final bool debugShowCheckedModeBanner;
|
final bool debugShowCheckedModeBanner;
|
||||||
|
|
||||||
/// The list of observers for the [Navigator] created for this app.
|
|
||||||
///
|
|
||||||
/// This list must be replaced by a list of newly-created observers if the
|
|
||||||
/// [navigatorKey] is changed.
|
|
||||||
final List<NavigatorObserver> navigatorObservers;
|
|
||||||
|
|
||||||
/// If true, forces the performance overlay to be visible in all instances.
|
/// If true, forces the performance overlay to be visible in all instances.
|
||||||
///
|
///
|
||||||
/// Used by the `showPerformanceOverlay` observatory extension.
|
/// Used by the `showPerformanceOverlay` observatory extension.
|
||||||
@ -332,7 +391,72 @@ class WidgetsApp extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserver {
|
class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserver {
|
||||||
|
|
||||||
|
// STATE LIFECYCLE
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_updateNavigator();
|
||||||
|
_locale = _resolveLocale(ui.window.locale, widget.supportedLocales);
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(WidgetsApp oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.navigatorKey != oldWidget.navigatorKey)
|
||||||
|
_updateNavigator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) { }
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didHaveMemoryPressure() { }
|
||||||
|
|
||||||
|
|
||||||
|
// NAVIGATOR
|
||||||
|
|
||||||
GlobalKey<NavigatorState> _navigator;
|
GlobalKey<NavigatorState> _navigator;
|
||||||
|
|
||||||
|
void _updateNavigator() {
|
||||||
|
if (widget.onGenerateRoute == null) {
|
||||||
|
_navigator = null;
|
||||||
|
} else {
|
||||||
|
_navigator = widget.navigatorKey ?? new GlobalObjectKey<NavigatorState>(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On Android: the user has pressed the back button.
|
||||||
|
@override
|
||||||
|
Future<bool> didPopRoute() async {
|
||||||
|
assert(mounted);
|
||||||
|
final NavigatorState navigator = _navigator?.currentState;
|
||||||
|
if (navigator == null)
|
||||||
|
return false;
|
||||||
|
return await navigator.maybePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> didPushRoute(String route) async {
|
||||||
|
assert(mounted);
|
||||||
|
final NavigatorState navigator = _navigator?.currentState;
|
||||||
|
if (navigator == null)
|
||||||
|
return false;
|
||||||
|
navigator.pushNamed(route);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// LOCALIZATION
|
||||||
|
|
||||||
Locale _locale;
|
Locale _locale;
|
||||||
|
|
||||||
Locale _resolveLocale(Locale newLocale, Iterable<Locale> supportedLocales) {
|
Locale _resolveLocale(Locale newLocale, Iterable<Locale> supportedLocales) {
|
||||||
@ -352,66 +476,6 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
|
|||||||
return matchesLanguageCode ?? supportedLocales.first;
|
return matchesLanguageCode ?? supportedLocales.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_updateNavigator();
|
|
||||||
_locale = _resolveLocale(ui.window.locale, widget.supportedLocales);
|
|
||||||
WidgetsBinding.instance.addObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(WidgetsApp oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
if (widget.navigatorKey != oldWidget.navigatorKey)
|
|
||||||
_updateNavigator();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateNavigator() {
|
|
||||||
_navigator = widget.navigatorKey ?? new GlobalObjectKey<NavigatorState>(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// On Android: the user has pressed the back button.
|
|
||||||
@override
|
|
||||||
Future<bool> didPopRoute() async {
|
|
||||||
assert(mounted);
|
|
||||||
final NavigatorState navigator = _navigator.currentState;
|
|
||||||
assert(navigator != null);
|
|
||||||
return await navigator.maybePop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> didPushRoute(String route) async {
|
|
||||||
assert(mounted);
|
|
||||||
final NavigatorState navigator = _navigator.currentState;
|
|
||||||
assert(navigator != null);
|
|
||||||
navigator.pushNamed(route);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeMetrics() {
|
|
||||||
setState(() {
|
|
||||||
// The properties of ui.window have changed. We use them in our build
|
|
||||||
// function, so we need setState(), but we don't cache anything locally.
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeTextScaleFactor() {
|
|
||||||
setState(() {
|
|
||||||
// The textScaleFactor property of ui.window has changed. We reference
|
|
||||||
// ui.window in our build function, so we need to call setState(), but
|
|
||||||
// we don't need to cache anything locally.
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeLocale(Locale locale) {
|
void didChangeLocale(Locale locale) {
|
||||||
if (locale == _locale)
|
if (locale == _locale)
|
||||||
@ -435,21 +499,53 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
|
|||||||
yield DefaultWidgetsLocalizations.delegate;
|
yield DefaultWidgetsLocalizations.delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) { }
|
// METRICS
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didHaveMemoryPressure() { }
|
void didChangeMetrics() {
|
||||||
|
setState(() {
|
||||||
|
// The properties of ui.window have changed. We use them in our build
|
||||||
|
// function, so we need setState(), but we don't cache anything locally.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeTextScaleFactor() {
|
||||||
|
setState(() {
|
||||||
|
// The textScaleFactor property of ui.window has changed. We reference
|
||||||
|
// ui.window in our build function, so we need to call setState(), but
|
||||||
|
// we don't need to cache anything locally.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// BUILDER
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget result = new Navigator(
|
Widget navigator;
|
||||||
key: _navigator,
|
if (_navigator != null) {
|
||||||
initialRoute: widget.initialRoute ?? ui.window.defaultRouteName,
|
navigator = new Navigator(
|
||||||
onGenerateRoute: widget.onGenerateRoute,
|
key: _navigator,
|
||||||
onUnknownRoute: widget.onUnknownRoute,
|
initialRoute: widget.initialRoute ?? ui.window.defaultRouteName,
|
||||||
observers: widget.navigatorObservers,
|
onGenerateRoute: widget.onGenerateRoute,
|
||||||
);
|
onUnknownRoute: widget.onUnknownRoute,
|
||||||
|
observers: widget.navigatorObservers,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget result;
|
||||||
|
if (widget.builder != null) {
|
||||||
|
result = new Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return widget.builder(context, navigator);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert(navigator != null);
|
||||||
|
result = navigator;
|
||||||
|
}
|
||||||
|
|
||||||
if (widget.textStyle != null) {
|
if (widget.textStyle != null) {
|
||||||
result = new DefaultTextStyle(
|
result = new DefaultTextStyle(
|
||||||
@ -502,28 +598,36 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
|
|||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
|
|
||||||
|
Widget title;
|
||||||
|
if (widget.onGenerateTitle != null) {
|
||||||
|
title = new Builder(
|
||||||
|
// This Builder exists to provide a context below the Localizations widget.
|
||||||
|
// The onGenerateCallback() can refer to Localizations via its context
|
||||||
|
// parameter.
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
final String title = widget.onGenerateTitle(context);
|
||||||
|
assert(title != null, 'onGenerateTitle must return a non-null String');
|
||||||
|
return new Title(
|
||||||
|
title: title,
|
||||||
|
color: widget.color,
|
||||||
|
child: result,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
title = new Title(
|
||||||
|
title: widget.title,
|
||||||
|
color: widget.color,
|
||||||
|
child: result,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return new MediaQuery(
|
return new MediaQuery(
|
||||||
data: new MediaQueryData.fromWindow(ui.window),
|
data: new MediaQueryData.fromWindow(ui.window),
|
||||||
child: new Localizations(
|
child: new Localizations(
|
||||||
locale: widget.locale ?? _locale,
|
locale: widget.locale ?? _locale,
|
||||||
delegates: _localizationsDelegates.toList(),
|
delegates: _localizationsDelegates.toList(),
|
||||||
// This Builder exists to provide a context below the Localizations widget.
|
child: title,
|
||||||
// The onGenerateCallback() can refer to Localizations via its context
|
|
||||||
// parameter.
|
|
||||||
child: new Builder(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
String title = widget.title;
|
|
||||||
if (widget.onGenerateTitle != null) {
|
|
||||||
title = widget.onGenerateTitle(context);
|
|
||||||
assert(title != null, 'onGenerateTitle must return a non-null String');
|
|
||||||
}
|
|
||||||
return new Title(
|
|
||||||
title: title,
|
|
||||||
color: widget.color,
|
|
||||||
child: result,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3557,6 +3557,14 @@ typedef Widget WidgetBuilder(BuildContext context);
|
|||||||
/// Used by [ListView.builder] and other APIs that use lazily-generated widgets.
|
/// Used by [ListView.builder] and other APIs that use lazily-generated widgets.
|
||||||
typedef Widget IndexedWidgetBuilder(BuildContext context, int index);
|
typedef Widget IndexedWidgetBuilder(BuildContext context, int index);
|
||||||
|
|
||||||
|
/// A builder that builds a widget given a child.
|
||||||
|
///
|
||||||
|
/// The child should typically be part of the returned widget tree.
|
||||||
|
///
|
||||||
|
/// Used by [AnimatedBuilder.builder], as well as [WidgetsApp.builder] and
|
||||||
|
/// [MaterialApp.builder].
|
||||||
|
typedef Widget TransitionBuilder(BuildContext context, Widget child);
|
||||||
|
|
||||||
/// An [Element] that composes other [Element]s.
|
/// An [Element] that composes other [Element]s.
|
||||||
///
|
///
|
||||||
/// Rather than creating a [RenderObject] directly, a [ComponentElement] creates
|
/// Rather than creating a [RenderObject] directly, a [ComponentElement] creates
|
||||||
|
@ -527,13 +527,6 @@ class AlignTransition extends AnimatedWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A builder that builds a widget given a child.
|
|
||||||
///
|
|
||||||
/// The child should typically be part of the returned widget tree.
|
|
||||||
///
|
|
||||||
/// Used by [AnimatedBuilder.builder].
|
|
||||||
typedef Widget TransitionBuilder(BuildContext context, Widget child);
|
|
||||||
|
|
||||||
/// A general-purpose widget for building animations.
|
/// A general-purpose widget for building animations.
|
||||||
///
|
///
|
||||||
/// AnimatedBuilder is useful for more complex widgets that wish to include
|
/// AnimatedBuilder is useful for more complex widgets that wish to include
|
||||||
|
65
packages/flutter/test/material/app_builder_test.dart
Normal file
65
packages/flutter/test/material/app_builder_test.dart
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('builder doesn\'t get called if app doesn\'t change', (WidgetTester tester) async {
|
||||||
|
final List<String> log = <String>[];
|
||||||
|
final Widget app = new MaterialApp(
|
||||||
|
theme: new ThemeData(
|
||||||
|
primarySwatch: Colors.green,
|
||||||
|
),
|
||||||
|
home: const Placeholder(),
|
||||||
|
builder: (BuildContext context, Widget child) {
|
||||||
|
log.add('build');
|
||||||
|
expect(Theme.of(context).primaryColor, Colors.green.shade500);
|
||||||
|
expect(Directionality.of(context), TextDirection.ltr);
|
||||||
|
expect(child, const isInstanceOf<Navigator>());
|
||||||
|
return const Placeholder();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: app,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(log, <String>['build']);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: app,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(log, <String>['build']);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('builder doesn\'t get called if app doesn\'t change', (WidgetTester tester) async {
|
||||||
|
final List<String> log = <String>[];
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new MaterialApp(
|
||||||
|
theme: new ThemeData(
|
||||||
|
primarySwatch: Colors.yellow,
|
||||||
|
),
|
||||||
|
home: new Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
log.add('build');
|
||||||
|
expect(Theme.of(context).primaryColor, Colors.yellow.shade500);
|
||||||
|
expect(Directionality.of(context), TextDirection.rtl);
|
||||||
|
return const Placeholder();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
builder: (BuildContext context, Widget child) {
|
||||||
|
return new Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(log, <String>['build']);
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user