Commit 1b9476c4 authored by Ian Hickson's avatar Ian Hickson

Hide routes from the API when they're not needed. (#3431)

The 'routes' table is a point of confusion with new developers. By
providing a 'home' argument that sets the '/' route, we can delay the
point at which we teach developers about 'routes' until the point where
they want to have a second route.
parent f7360126
......@@ -24,9 +24,7 @@ class ComplexLayoutAppState extends State<ComplexLayoutApp> {
return new MaterialApp(
theme: lightTheme ? new ThemeData.light() : new ThemeData.dark(),
title: 'Advanced Layout',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => new ComplexLayout(),
}
home: new ComplexLayout()
);
}
......
......@@ -465,8 +465,6 @@ class CardCollectionState extends State<CardCollection> {
void main() {
runApp(new MaterialApp(
title: 'Cards',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => new CardCollection(),
}
home: new CardCollection()
));
}
......@@ -298,8 +298,6 @@ class DragAndDropAppState extends State<DragAndDropApp> {
void main() {
runApp(new MaterialApp(
title: 'Drag and Drop Flutter Demo',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => new DragAndDropApp()
}
home: new DragAndDropApp()
));
}
......@@ -249,6 +249,6 @@ class _WindowManagerState extends State<WindowManager> {
void main() {
runApp(new MaterialApp(
title: 'Mozart',
routes: <String, WidgetBuilder>{ '/': (_) => new WindowManager() }
home: new WindowManager()
));
}
......@@ -201,8 +201,6 @@ void main() {
accentColor: Colors.redAccent[200]
),
title: 'Cards',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => new OverlayGeometryApp()
}
home: new OverlayGeometryApp()
));
}
......@@ -145,8 +145,6 @@ void main() {
primarySwatch: Colors.blue,
accentColor: Colors.redAccent[200]
),
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => new PageableListApp(),
}
home: new PageableListApp()
));
}
......@@ -9,25 +9,19 @@ import 'package:sky_services/sky/input_event.mojom.dart' as mojom;
GlobalKey _key = new GlobalKey();
void main() {
runApp(
new MaterialApp(
title: "Hardware Key Demo",
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Hardware Key Demo")
),
body: new Material(
child: new RawKeyboardDemo(
key: _key
)
)
);
}
}
runApp(new MaterialApp(
title: "Hardware Key Demo",
home: new Scaffold(
appBar: new AppBar(
title: new Text("Hardware Key Demo")
),
body: new Material(
child: new RawKeyboardDemo(
key: _key
)
)
)
);
));
}
class RawKeyboardDemo extends StatefulWidget {
......
......@@ -301,11 +301,5 @@ class IsolateExampleState extends State<StatefulWidget> {
}
void main() {
runApp(
new MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => new IsolateExampleWidget()
}
)
);
runApp(new MaterialApp(home: new IsolateExampleWidget()));
}
......@@ -218,14 +218,9 @@ class _GestureDemoState extends State<GestureDemo> {
void main() {
runApp(new MaterialApp(
theme: new ThemeData.dark(),
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Gestures Demo')),
body: new GestureDemo()
);
}
}
home: new Scaffold(
appBar: new AppBar(title: new Text('Gestures Demo')),
body: new GestureDemo()
)
));
}
......@@ -100,15 +100,11 @@ final List<String> _kNames = _initNames();
void main() {
runApp(new MaterialApp(
title: 'Media Query Example',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Media Query Example')
),
body: new Material(child: new AdaptiveContainer(names: _kNames))
);
}
}
home: new Scaffold(
appBar: new AppBar(
title: new Text('Media Query Example')
),
body: new Material(child: new AdaptiveContainer(names: _kNames))
)
));
}
......@@ -153,16 +153,12 @@ class SectorAppState extends State<SectorApp> {
return new MaterialApp(
theme: new ThemeData.light(),
title: 'Sector Layout',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Sector Layout in a Widget Tree')
),
body: buildBody()
);
}
}
home: new Scaffold(
appBar: new AppBar(
title: new Text('Sector Layout in a Widget Tree')
),
body: buildBody()
)
);
}
}
......
......@@ -122,17 +122,14 @@ class _StyledTextDemoState extends State<StyledTextDemo> {
void main() {
runApp(new MaterialApp(
theme: new ThemeData.light(),
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Hal and Dave')),
body: new Material(
color: Colors.grey[50],
child: new StyledTextDemo()
)
);
}
}
home: new Scaffold(
appBar: new AppBar(
title: new Text('Hal and Dave')
),
body: new Material(
color: Colors.grey[50],
child: new StyledTextDemo()
)
)
));
}
......@@ -24,16 +24,14 @@ class GalleryAppState extends State<GalleryApp> {
title: 'Flutter Material Gallery',
theme: _useLightTheme ? _kGalleryLightTheme : _kGalleryDarkTheme,
showPerformanceOverlay: _showPerformanceOverlay,
routes: {
'/': (BuildContext context) => new GalleryHome(
useLightTheme: _useLightTheme,
onThemeChanged: (bool value) { setState(() { _useLightTheme = value; }); },
showPerformanceOverlay: _showPerformanceOverlay,
onShowPerformanceOverlayChanged: (bool value) { setState(() { _showPerformanceOverlay = value; }); },
timeDilation: timeDilation,
onTimeDilationChanged: (double value) { setState(() { timeDilation = value; }); }
)
}
home: new GalleryHome(
useLightTheme: _useLightTheme,
onThemeChanged: (bool value) { setState(() { _useLightTheme = value; }); },
showPerformanceOverlay: _showPerformanceOverlay,
onShowPerformanceOverlayChanged: (bool value) { setState(() { _showPerformanceOverlay = value; }); },
timeDilation: timeDilation,
onTimeDilationChanged: (double value) { setState(() { timeDilation = value; }); }
)
);
}
}
......
......@@ -36,10 +36,18 @@ const TextStyle _errorTextStyle = const TextStyle(
/// * [Scaffold]
/// * [MaterialPageRoute]
class MaterialApp extends WidgetsApp {
/// Creates a MaterialApp.
///
/// At least one of [home], [routes], or [onGenerateRoute] must be
/// given. If only [routes] is given, it must include an entry for
/// the [Navigator.defaultRouteName] (`'/'`).
///
/// See also the [new WidgetsApp] constructor (which this extends).
MaterialApp({
Key key,
String title,
ThemeData theme,
Widget home,
Map<String, WidgetBuilder> routes: const <String, WidgetBuilder>{},
RouteFactory onGenerateRoute,
LocaleChangedCallback onLocaleChanged,
......@@ -48,6 +56,7 @@ class MaterialApp extends WidgetsApp {
bool showSemanticsDebugger: false,
bool debugShowCheckedModeBanner: true
}) : theme = theme,
home = home,
routes = routes,
super(
key: key,
......@@ -56,6 +65,8 @@ class MaterialApp extends WidgetsApp {
color: theme?.primaryColor ?? Colors.blue[500], // blue[500] is the primary color of the default theme
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder = routes[settings.name];
if (builder == null && home != null && settings.name == Navigator.defaultRouteName)
builder = (BuildContext context) => home;
if (builder != null) {
return new MaterialPageRoute<Null>(
builder: builder,
......@@ -72,17 +83,45 @@ class MaterialApp extends WidgetsApp {
debugShowCheckedModeBanner: debugShowCheckedModeBanner
) {
assert(debugShowMaterialGrid != null);
assert(routes != null);
assert(!routes.containsKey(Navigator.defaultRouteName) || (home == null));
assert(routes.containsKey(Navigator.defaultRouteName) || (home != null) || (onGenerateRoute != null));
}
/// The colors to use for the application's widgets.
final ThemeData theme;
/// The widget for the default route of the app
/// ([Navigator.defaultRouteName], which is `'/'`).
///
/// This is the page that is displayed first when the application is
/// started normally.
///
/// To be able to directly call [Theme.of], [MediaQuery.of],
/// [LocaleQuery.of], etc, in the code sets the [home] argument in
/// the constructor, you can use a [Builder] widget to get a
/// [BuildContext].
///
/// If this is not specified, then either the route with name `'/'`
/// must be given in [routes], or the [onGenerateRoute] callback
/// must be able to build a widget for that route.
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 is an error to provide a route
/// in this map for the [Navigator.defaultRouteName] route (`'/'`).
///
/// If a route is requested that is not specified in this table (or
/// by [home]), then the [onGenerateRoute] callback is invoked to
/// build the page instead.
final Map<String, WidgetBuilder> routes;
/// Turns on a [GridPaper] overlay that paints a baseline grid
......
......@@ -66,9 +66,12 @@ class MaterialPageRoute<T> extends PageRoute<T> {
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation) {
Widget result = builder(context);
assert(() {
if (result == null)
debugPrint('The builder for route \'${settings.name}\' returned null. Route builders must never return null.');
assert(result != null && 'A route builder returned null. See the previous log message for details.' is String);
if (result == null) {
throw new FlutterError(
'The builder for route "${settings.name}" returned null.\n'
'Route builders must never return null.'
);
}
return true;
});
return result;
......
......@@ -12,30 +12,26 @@ void main() {
String helloSnackBar = 'Hello SnackBar';
Key tapTarget = new Key('tap-target');
tester.pumpWidget(new MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return new Scaffold(
body: new Builder(
builder: (BuildContext context) {
return new GestureDetector(
onTap: () {
Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text(helloSnackBar),
duration: new Duration(seconds: 2)
));
},
behavior: HitTestBehavior.opaque,
child: new Container(
height: 100.0,
width: 100.0,
key: tapTarget
)
);
}
)
);
}
}
home: new Scaffold(
body: new Builder(
builder: (BuildContext context) {
return new GestureDetector(
onTap: () {
Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text(helloSnackBar),
duration: new Duration(seconds: 2)
));
},
behavior: HitTestBehavior.opaque,
child: new Container(
height: 100.0,
width: 100.0,
key: tapTarget
)
);
}
)
)
));
expect(tester, doesNotHaveWidget(find.text(helloSnackBar)));
tester.tap(find.byKey(tapTarget));
......@@ -63,31 +59,27 @@ void main() {
int snackBarCount = 0;
Key tapTarget = new Key('tap-target');
tester.pumpWidget(new MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return new Scaffold(
body: new Builder(
builder: (BuildContext context) {
return new GestureDetector(
onTap: () {
snackBarCount += 1;
Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text("bar$snackBarCount"),
duration: new Duration(seconds: 2)
));
},
behavior: HitTestBehavior.opaque,
child: new Container(
height: 100.0,
width: 100.0,
key: tapTarget
)
);
}
)
);
}
}
home: new Scaffold(
body: new Builder(
builder: (BuildContext context) {
return new GestureDetector(
onTap: () {
snackBarCount += 1;
Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text("bar$snackBarCount"),
duration: new Duration(seconds: 2)
));
},
behavior: HitTestBehavior.opaque,
child: new Container(
height: 100.0,
width: 100.0,
key: tapTarget
)
);
}
)
)
));
expect(tester, doesNotHaveWidget(find.text('bar1')));
expect(tester, doesNotHaveWidget(find.text('bar2')));
......@@ -146,31 +138,27 @@ void main() {
int time;
ScaffoldFeatureController<SnackBar, Null> lastController;
tester.pumpWidget(new MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return new Scaffold(
body: new Builder(
builder: (BuildContext context) {
return new GestureDetector(
onTap: () {
snackBarCount += 1;
lastController = Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text("bar$snackBarCount"),
duration: new Duration(seconds: time)
));
},
behavior: HitTestBehavior.opaque,
child: new Container(
height: 100.0,
width: 100.0,
key: tapTarget
)
);
}
)
);
}
}
home: new Scaffold(
body: new Builder(
builder: (BuildContext context) {
return new GestureDetector(
onTap: () {
snackBarCount += 1;
lastController = Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text("bar$snackBarCount"),
duration: new Duration(seconds: time)
));
},
behavior: HitTestBehavior.opaque,
child: new Container(
height: 100.0,
width: 100.0,
key: tapTarget
)
);
}
)
)
));
expect(tester, doesNotHaveWidget(find.text('bar1')));
expect(tester, doesNotHaveWidget(find.text('bar2')));
......@@ -236,31 +224,27 @@ void main() {
int snackBarCount = 0;
Key tapTarget = new Key('tap-target');
tester.pumpWidget(new MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return new Scaffold(
body: new Builder(
builder: (BuildContext context) {
return new GestureDetector(
onTap: () {
snackBarCount += 1;
Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text("bar$snackBarCount"),
duration: new Duration(seconds: 2)
));
},
behavior: HitTestBehavior.opaque,
child: new Container(
height: 100.0,
width: 100.0,
key: tapTarget
)
);
}
)
);
}
}
home: new Scaffold(
body: new Builder(
builder: (BuildContext context) {
return new GestureDetector(
onTap: () {
snackBarCount += 1;
Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text("bar$snackBarCount"),
duration: new Duration(seconds: 2)
));
},
behavior: HitTestBehavior.opaque,
child: new Container(
height: 100.0,
width: 100.0,
key: tapTarget
)
);
}
)
)
));
expect(tester, doesNotHaveWidget(find.text('bar1')));
expect(tester, doesNotHaveWidget(find.text('bar2')));
......@@ -286,31 +270,27 @@ void main() {
testWidgets((WidgetTester tester) {
int tapCount = 0;
tester.pumpWidget(new MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return new Scaffold(
body: new Builder(
builder: (BuildContext context) {
return new GestureDetector(
onTap: () {
Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text('I am a snack bar.'),
duration: new Duration(seconds: 2),
action: new SnackBarAction(
label: 'ACTION',
onPressed: () {
++tapCount;
}
)
));
},
child: new Text('X')
);
}
)
);
}
}
home: new Scaffold(
body: new Builder(
builder: (BuildContext context) {
return new GestureDetector(
onTap: () {
Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text('I am a snack bar.'),
duration: new Duration(seconds: 2),
action: new SnackBarAction(
label: 'ACTION',
onPressed: () {
++tapCount;
}
)
));
},
child: new Text('X')
);
}
)
)
));
tester.tap(find.text('X'));
tester.pump(); // start animation
......
......@@ -14,19 +14,15 @@ void main() {
int buildCount = 0;
tester.pumpWidget(new MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return new Scaffold(
key: scaffoldKey,
body: new Center(child: new Text('body'))
);
}
}
home: new Scaffold(
key: scaffoldKey,
body: new Center(child: new Text('body'))
)
));
bottomSheet = scaffoldKey.currentState.showBottomSheet/*<Null>*/((_) {
return new Builder(
builder: (_) {
builder: (BuildContext context) {
buildCount += 1;
return new Container(height: 200.0);
}
......
......@@ -10,23 +10,23 @@ import 'package:test/test.dart';
void main() {
test('Verify that a tap dismisses a modal BottomSheet', () {
testWidgets((WidgetTester tester) {
BuildContext context;
BuildContext savedContext;
bool showBottomSheetThenCalled = false;
tester.pumpWidget(new MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext ctx) {
context = ctx;
return new Container();
}
home: new Builder(
builder: (BuildContext context) {
savedContext = context;
return new Container();
}
)
));
tester.pump();
expect(tester, doesNotHaveWidget(find.text('BottomSheet')));
showModalBottomSheet/*<Null>*/(
context: context,
context: savedContext,
builder: (BuildContext context) => new Text('BottomSheet')
).then((Null result) {
expect(result, isNull);
......@@ -46,7 +46,7 @@ void main() {
tester.pump(new Duration(seconds: 1)); // frame after the animation (sheet has been removed)
expect(tester, doesNotHaveWidget(find.text('BottomSheet')));
showModalBottomSheet/*<Null>*/(context: context, builder: (BuildContext context) => new Text('BottomSheet'));
showModalBottomSheet/*<Null>*/(context: savedContext, builder: (BuildContext context) => new Text('BottomSheet'));
tester.pump(); // bottom sheet show animation starts
tester.pump(new Duration(seconds: 1)); // animation done
expect(tester, hasWidget(find.text('BottomSheet')));
......@@ -66,14 +66,10 @@ void main() {
bool showBottomSheetThenCalled = false;
tester.pumpWidget(new MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return new Scaffold(
key: scaffoldKey,
body: new Center(child: new Text('body'))
);
}
}
home: new Scaffold(
key: scaffoldKey,
body: new Center(child: new Text('body'))
)
));
expect(showBottomSheetThenCalled, isFalse);
......
......@@ -12,19 +12,19 @@ void main() {
test('Drawer control test', () {
testWidgets((WidgetTester tester) {
GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
BuildContext context;
BuildContext savedContext;
tester.pumpWidget(
new MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext ctx) {
context = ctx;
home: new Builder(
builder: (BuildContext context) {
savedContext = context;
return new Scaffold(
key: scaffoldKey,
drawer: new Text('drawer'),
body: new Container()
);
}
}
)
)
);
tester.pump(); // no effect
......@@ -34,7 +34,7 @@ void main() {
expect(tester, hasWidget(find.text('drawer')));
tester.pump(new Duration(seconds: 1)); // animation done
expect(tester, hasWidget(find.text('drawer')));
Navigator.pop(context);
Navigator.pop(savedContext);
tester.pump(); // drawer should be starting to animate away
expect(tester, hasWidget(find.text('drawer')));
tester.pump(new Duration(seconds: 1)); // animation done
......@@ -47,15 +47,11 @@ void main() {
GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
tester.pumpWidget(
new MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return new Scaffold(
key: scaffoldKey,
drawer: new Text('drawer'),
body: new Container()
);
}
}
home: new Scaffold(
key: scaffoldKey,
drawer: new Text('drawer'),
body: new Container()
)
)
);
tester.pump(); // no effect
......@@ -85,27 +81,23 @@ void main() {
GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
tester.pumpWidget(
new MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return new Scaffold(
key: scaffoldKey,
drawer: new Drawer(
child: new Block(
children: <Widget>[
new Text('drawer'),
new Container(
height: 1000.0,
decoration: new BoxDecoration(
backgroundColor: Colors.blue[500]
)
),
]
)
),
body: new Container()
);
}
}
home: new Scaffold(
key: scaffoldKey,
drawer: new Drawer(
child: new Block(
children: <Widget>[
new Text('drawer'),
new Container(
height: 1000.0,
decoration: new BoxDecoration(
backgroundColor: Colors.blue[500]
)
),
]
)
),
body: new Container()
)
)
);
expect(tester, doesNotHaveWidget(find.text('drawer')));
......
......@@ -21,7 +21,7 @@ You can use [hyperlinks](hyperlink) in markdown
## Code blocks
Formatted Dart code looks really pretty too. This is an example of how to create your own Markdown widget:
new Markdown(data: "Hello _world_!");
new Markdown(data: 'Hello _world_!');
Enjoy!
""";
......@@ -29,11 +29,9 @@ Enjoy!
void main() {
runApp(new MaterialApp(
title: "Markdown Demo",
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => new Scaffold(
appBar: new AppBar(title: new Text("Markdown Demo")),
body: new Markdown(data: _kMarkdownData)
)
}
home: new Scaffold(
appBar: new AppBar(title: new Text('Markdown Demo')),
body: new Markdown(data: _kMarkdownData)
)
));
}
......@@ -15,9 +15,7 @@ void main() {
theme: new ThemeData(
primarySwatch: Colors.blue
),
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => new FlutterDemo()
}
home: new FlutterDemo()
)
);
}
......
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