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

Provide a way to override global InheritedWidgets (#14348)

For example, so that the gallery can override the media query globally.
parent 12ceaefb
...@@ -51,6 +51,7 @@ class GalleryAppState extends State<GalleryApp> { ...@@ -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> { ...@@ -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> { ...@@ -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> { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 ...@@ -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() { ...@@ -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 { ...@@ -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( ...@@ -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( ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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,37 +164,11 @@ class MaterialApp extends StatefulWidget { ...@@ -140,37 +164,11 @@ 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.
final GlobalKey<NavigatorState> navigatorKey;
/// A one-line description used by the device to identify the app for the user.
/// ///
/// On Android the titles appear above the task manager's app snapshots which are /// The [Navigator] is only built if routes are provided (either via [home],
/// displayed when the user presses the "recent apps" button. Similarly, on /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
/// iOS the titles appear in the App Switcher when the user double presses the /// [navigatorKey] must be null and [builder] must not be null.
/// home button. final GlobalKey<NavigatorState> navigatorKey;
///
/// To provide a localized title instead, use [onGenerateTitle].
///
/// This value is passed unmodified to [WidgetsApp.title].
final String title;
/// If non-null this callback is called to produce the app's
/// title string, otherwise [title] is used.
///
/// The [onGenerateTitle] `context` parameter includes the [WidgetsApp]'s
/// [Localizations] widget so that this callback can be used to produce a
/// localized title.
///
/// This callback function must not return null.
///
/// The [onGenerateTitle] callback is called each time the [MaterialApp]
/// rebuilds.
///
/// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
final GenerateAppTitle onGenerateTitle;
/// The colors to use for the application's widgets.
final ThemeData theme;
/// The widget for the default route of the app ([Navigator.defaultRouteName], /// The widget for the default route of the app ([Navigator.defaultRouteName],
/// which is `/`). /// which is `/`).
...@@ -185,14 +183,19 @@ class MaterialApp extends StatefulWidget { ...@@ -185,14 +183,19 @@ class MaterialApp extends StatefulWidget {
/// ///
/// If [home] is specified, then [routes] must not include an entry for `/`, /// If [home] is specified, then [routes] must not include an entry for `/`,
/// as [home] takes its place. /// as [home] takes its place.
final Widget home;
/// The primary color to use for the application in the operating system
/// interface.
/// ///
/// For example, on Android this is the color used for the application in the /// The [Navigator] is only built if routes are provided (either via [home],
/// application switcher. /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
final Color color; /// [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. /// The application's top-level routing table.
/// ///
...@@ -210,9 +213,13 @@ class MaterialApp extends StatefulWidget { ...@@ -210,9 +213,13 @@ class MaterialApp extends StatefulWidget {
/// If a route is requested that is not specified in this table (or by /// 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 /// [home]), then the [onGenerateRoute] callback is called to build the page
/// instead. /// 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; final Map<String, WidgetBuilder> routes;
/// The name of the first route to show. /// The name of the first route to show, if a [Navigator] is built.
/// ///
/// Defaults to [Window.defaultRouteName], which may be overridden by the code /// Defaults to [Window.defaultRouteName], which may be overridden by the code
/// that launched the application. /// that launched the application.
...@@ -227,6 +234,10 @@ class MaterialApp extends StatefulWidget { ...@@ -227,6 +234,10 @@ class MaterialApp extends StatefulWidget {
/// (`/`). This can happen if the app is started with an intent that specifies /// (`/`). This can happen if the app is started with an intent that specifies
/// a non-existent route. /// 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: /// See also:
/// ///
/// * [Navigator.initialRoute], which is used to implement this property. /// * [Navigator.initialRoute], which is used to implement this property.
...@@ -246,6 +257,10 @@ class MaterialApp extends StatefulWidget { ...@@ -246,6 +257,10 @@ class MaterialApp extends StatefulWidget {
/// During normal app operation, the [onGenerateRoute] callback will only be /// During normal app operation, the [onGenerateRoute] callback will only be
/// applied to route names pushed by the application, and so should never /// applied to route names pushed by the application, and so should never
/// return null. /// 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; final RouteFactory onGenerateRoute;
/// Called when [onGenerateRoute] fails to generate a route, except for the /// Called when [onGenerateRoute] fails to generate a route, except for the
...@@ -257,8 +272,102 @@ class MaterialApp extends StatefulWidget { ...@@ -257,8 +272,102 @@ class MaterialApp extends StatefulWidget {
/// ///
/// The default implementation pushes a route that displays an ugly error /// The default implementation pushes a route that displays an ugly error
/// message. /// 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; 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.
///
/// On Android the titles appear above the task manager's app snapshots which are
/// displayed when the user presses the "recent apps" button. Similarly, on
/// iOS the titles appear in the App Switcher when the user double presses the
/// home button.
///
/// To provide a localized title instead, use [onGenerateTitle].
///
/// This value is passed unmodified to [WidgetsApp.title].
final String title;
/// If non-null this callback is called to produce the app's
/// title string, otherwise [title] is used.
///
/// The [onGenerateTitle] `context` parameter includes the [WidgetsApp]'s
/// [Localizations] widget so that this callback can be used to produce a
/// localized title.
///
/// This callback function must not return null.
///
/// The [onGenerateTitle] callback is called each time the [MaterialApp]
/// rebuilds.
///
/// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
final GenerateAppTitle onGenerateTitle;
/// The colors to use for the application's widgets.
final ThemeData theme;
/// The primary color to use for the application in the operating system
/// interface.
///
/// For example, on Android this is the color used for the application in the
/// application switcher.
final Color color;
/// 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 { ...@@ -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> { ...@@ -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> { ...@@ -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> { ...@@ -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> { ...@@ -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); ...@@ -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 { ...@@ -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,40 +119,10 @@ class WidgetsApp extends StatefulWidget { ...@@ -111,40 +119,10 @@ 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.
final GlobalKey<NavigatorState> navigatorKey;
/// A one-line description used by the device to identify the app for the user.
///
/// On Android the titles appear above the task manager's app snapshots which are
/// displayed when the user presses the "recent apps" button. Similarly, on
/// iOS the titles appear in the App Switcher when the user double presses the
/// home button.
///
/// To provide a localized title instead, use [onGenerateTitle].
final String title;
/// If non-null this callback function is called to produce the app's
/// title string, otherwise [title] is used.
///
/// The [onGenerateTitle] `context` parameter includes the [WidgetsApp]'s
/// [Localizations] widget so that this callback can be used to produce a
/// localized title.
///
/// This callback function must not return null.
/// ///
/// The [onGenerateTitle] callback is called each time the [WidgetsApp] /// The [Navigator] is only built if [onGenerateRoute] is not null; if it is
/// rebuilds. /// null, [navigatorKey] must also be null.
final GenerateAppTitle onGenerateTitle; final GlobalKey<NavigatorState> navigatorKey;
/// The default text style for [Text] in the application.
final TextStyle textStyle;
/// The primary color to use for the application in the operating system
/// interface.
///
/// For example, on Android this is the color used for the application in the
/// application switcher.
final Color color;
/// The route generator callback used when the app is navigated to a /// The route generator callback used when the app is navigated to a
/// named route. /// named route.
...@@ -156,6 +134,9 @@ class WidgetsApp extends StatefulWidget { ...@@ -156,6 +134,9 @@ class WidgetsApp extends StatefulWidget {
/// During normal app operation, the [onGenerateRoute] callback will only be /// During normal app operation, the [onGenerateRoute] callback will only be
/// applied to route names pushed by the application, and so should never /// applied to route names pushed by the application, and so should never
/// return null. /// 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; final RouteFactory onGenerateRoute;
/// Called when [onGenerateRoute] fails to generate a route. /// Called when [onGenerateRoute] fails to generate a route.
...@@ -166,6 +147,9 @@ class WidgetsApp extends StatefulWidget { ...@@ -166,6 +147,9 @@ class WidgetsApp extends StatefulWidget {
/// ///
/// Unknown routes can arise either from errors in the app or from external /// Unknown routes can arise either from errors in the app or from external
/// requests to push routes, such as from Android intents. /// 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; final RouteFactory onUnknownRoute;
/// The name of the first route to show. /// The name of the first route to show.
...@@ -183,6 +167,9 @@ class WidgetsApp extends StatefulWidget { ...@@ -183,6 +167,9 @@ class WidgetsApp extends StatefulWidget {
/// (`/`). This can happen if the app is started with an intent that specifies /// (`/`). This can happen if the app is started with an intent that specifies
/// a non-existent route. /// 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: /// See also:
/// ///
/// * [Navigator.initialRoute], which is used to implement this property. /// * [Navigator.initialRoute], which is used to implement this property.
...@@ -190,6 +177,84 @@ class WidgetsApp extends StatefulWidget { ...@@ -190,6 +177,84 @@ class WidgetsApp extends StatefulWidget {
/// * [Navigator.pop], for removing a route from the stack. /// * [Navigator.pop], for removing a route from the stack.
final String initialRoute; 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.
///
/// On Android the titles appear above the task manager's app snapshots which are
/// displayed when the user presses the "recent apps" button. Similarly, on
/// iOS the titles appear in the App Switcher when the user double presses the
/// home button.
///
/// To provide a localized title instead, use [onGenerateTitle].
final String title;
/// If non-null this callback function is called to produce the app's
/// title string, otherwise [title] is used.
///
/// The [onGenerateTitle] `context` parameter includes the [WidgetsApp]'s
/// [Localizations] widget so that this callback can be used to produce a
/// localized title.
///
/// This callback function must not return null.
///
/// The [onGenerateTitle] callback is called each time the [WidgetsApp]
/// rebuilds.
final GenerateAppTitle onGenerateTitle;
/// The default text style for [Text] in the application.
final TextStyle textStyle;
/// The primary color to use for the application in the operating system
/// interface.
///
/// For example, on Android this is the color used for the application in the
/// application switcher.
final Color color;
/// 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 { ...@@ -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,25 +391,8 @@ class WidgetsApp extends StatefulWidget { ...@@ -332,25 +391,8 @@ class WidgetsApp extends StatefulWidget {
} }
class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserver { class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserver {
GlobalKey<NavigatorState> _navigator;
Locale _locale;
Locale _resolveLocale(Locale newLocale, Iterable<Locale> supportedLocales) {
if (widget.localeResolutionCallback != null) {
final Locale locale = widget.localeResolutionCallback(newLocale, widget.supportedLocales);
if (locale != null)
return locale;
}
Locale matchesLanguageCode; // STATE LIFECYCLE
for (Locale locale in supportedLocales) {
if (locale == newLocale)
return newLocale;
if (locale.languageCode == newLocale.languageCode)
matchesLanguageCode ??= locale;
}
return matchesLanguageCode ?? supportedLocales.first;
}
@override @override
void initState() { void initState() {
...@@ -367,49 +409,71 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv ...@@ -367,49 +409,71 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
_updateNavigator(); _updateNavigator();
} }
void _updateNavigator() {
_navigator = widget.navigatorKey ?? new GlobalObjectKey<NavigatorState>(this);
}
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
super.dispose(); super.dispose();
} }
@override
void didChangeAppLifecycleState(AppLifecycleState state) { }
@override
void didHaveMemoryPressure() { }
// 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. // On Android: the user has pressed the back button.
@override @override
Future<bool> didPopRoute() async { Future<bool> didPopRoute() async {
assert(mounted); assert(mounted);
final NavigatorState navigator = _navigator.currentState; final NavigatorState navigator = _navigator?.currentState;
assert(navigator != null); if (navigator == null)
return false;
return await navigator.maybePop(); return await navigator.maybePop();
} }
@override @override
Future<bool> didPushRoute(String route) async { Future<bool> didPushRoute(String route) async {
assert(mounted); assert(mounted);
final NavigatorState navigator = _navigator.currentState; final NavigatorState navigator = _navigator?.currentState;
assert(navigator != null); if (navigator == null)
return false;
navigator.pushNamed(route); navigator.pushNamed(route);
return true; 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 // LOCALIZATION
void didChangeTextScaleFactor() {
setState(() { Locale _locale;
// The textScaleFactor property of ui.window has changed. We reference
// ui.window in our build function, so we need to call setState(), but Locale _resolveLocale(Locale newLocale, Iterable<Locale> supportedLocales) {
// we don't need to cache anything locally. if (widget.localeResolutionCallback != null) {
}); final Locale locale = widget.localeResolutionCallback(newLocale, widget.supportedLocales);
if (locale != null)
return locale;
}
Locale matchesLanguageCode;
for (Locale locale in supportedLocales) {
if (locale == newLocale)
return newLocale;
if (locale.languageCode == newLocale.languageCode)
matchesLanguageCode ??= locale;
}
return matchesLanguageCode ?? supportedLocales.first;
} }
@override @override
...@@ -435,21 +499,53 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv ...@@ -435,21 +499,53 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
yield DefaultWidgetsLocalizations.delegate; yield DefaultWidgetsLocalizations.delegate;
} }
// METRICS
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { } 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 @override
void didHaveMemoryPressure() { } 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 ...@@ -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); ...@@ -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 { ...@@ -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
......
// 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']);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment