Commit babba2f0 authored by Hixie's avatar Hixie

Make Route.build() arguments match RouteBuilder()

Assert at build time that PageRoute route builders do not return null
widget trees.

Also very minor new code comments and code reorg to help make the heroes
patch easier to review.

(These are changes that are unrelated to Heroes but that were part of
the Heroes patch.)
parent 789031b5
...@@ -139,11 +139,11 @@ class _DialogRoute extends PerformanceRoute { ...@@ -139,11 +139,11 @@ class _DialogRoute extends PerformanceRoute {
bool get opaque => false; bool get opaque => false;
Duration get transitionDuration => const Duration(milliseconds: 150); Duration get transitionDuration => const Duration(milliseconds: 150);
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) { Widget build(RouteArguments args) {
return new FadeTransition( return new FadeTransition(
performance: performance, performance: args.previousPerformance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.easeOut), opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.easeOut),
child: builder(new RouteArguments(navigator: navigator, previousPerformance: this.performance, nextPerformance: nextRoutePerformance)) child: builder(args)
); );
} }
......
...@@ -112,7 +112,7 @@ class _DrawerRoute extends Route { ...@@ -112,7 +112,7 @@ class _DrawerRoute extends Route {
bool _interactive = true; bool _interactive = true;
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) { Widget build(RouteArguments args) {
return new Focus( return new Focus(
key: new GlobalObjectKey(this), key: new GlobalObjectKey(this),
autofocus: true, autofocus: true,
......
...@@ -26,9 +26,7 @@ class MaterialApp extends StatefulComponent { ...@@ -26,9 +26,7 @@ class MaterialApp extends StatefulComponent {
this.theme, this.theme,
this.routes, this.routes,
this.onGenerateRoute this.onGenerateRoute
}) : super(key: key) { }) : super(key: key);
assert(routes != null);
}
final String title; final String title;
final ThemeData theme; final ThemeData theme;
......
...@@ -134,7 +134,7 @@ class _MenuRoute extends PerformanceRoute { ...@@ -134,7 +134,7 @@ class _MenuRoute extends PerformanceRoute {
bool get opaque => false; bool get opaque => false;
Duration get transitionDuration => _kMenuDuration; Duration get transitionDuration => _kMenuDuration;
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) { Widget build(RouteArguments args) {
return new Positioned( return new Positioned(
top: position?.top, top: position?.top,
right: position?.right, right: position?.right,
......
...@@ -102,7 +102,7 @@ class _SnackBarRoute extends PerformanceRoute { ...@@ -102,7 +102,7 @@ class _SnackBarRoute extends PerformanceRoute {
bool get modal => false; bool get modal => false;
Duration get transitionDuration => const Duration(milliseconds: 200); Duration get transitionDuration => const Duration(milliseconds: 200);
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) => null; Widget build(RouteArguments args) => null;
} }
void showSnackBar({ NavigatorState navigator, GlobalKey<PlaceholderState> placeholderKey, Widget content, List<SnackBarAction> actions }) { void showSnackBar({ NavigatorState navigator, GlobalKey<PlaceholderState> placeholderKey, Widget content, List<SnackBarAction> actions }) {
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
import 'dart:collection'; import 'dart:collection';
import 'package:flutter/animation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'basic.dart'; import 'basic.dart';
...@@ -257,7 +256,7 @@ class DragRoute extends Route { ...@@ -257,7 +256,7 @@ class DragRoute extends Route {
bool get modal => false; bool get modal => false;
bool get opaque => false; bool get opaque => false;
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) { Widget build(RouteArguments args) {
return new Positioned( return new Positioned(
left: _lastOffset.dx, left: _lastOffset.dx,
top: _lastOffset.dy, top: _lastOffset.dy,
......
...@@ -9,8 +9,10 @@ import 'focus.dart'; ...@@ -9,8 +9,10 @@ import 'focus.dart';
import 'framework.dart'; import 'framework.dart';
import 'transitions.dart'; import 'transitions.dart';
const String kDefaultRouteName = '/';
class RouteArguments { class RouteArguments {
const RouteArguments({ this.navigator, this.previousPerformance, this.nextPerformance }); const RouteArguments(this.navigator, { this.previousPerformance, this.nextPerformance });
final NavigatorState navigator; final NavigatorState navigator;
final PerformanceView previousPerformance; final PerformanceView previousPerformance;
final PerformanceView nextPerformance; final PerformanceView nextPerformance;
...@@ -28,7 +30,8 @@ class Navigator extends StatefulComponent { ...@@ -28,7 +30,8 @@ class Navigator extends StatefulComponent {
this.onUnknownRoute // 404 generator. You only need to implement this if you have a way to navigate to arbitrary names. this.onUnknownRoute // 404 generator. You only need to implement this if you have a way to navigate to arbitrary names.
}) : super(key: key) { }) : super(key: key) {
// To use a navigator, you must at a minimum define the route with the name '/'. // To use a navigator, you must at a minimum define the route with the name '/'.
assert(routes.containsKey('/')); assert(routes != null);
assert(routes.containsKey(kDefaultRouteName));
} }
final Map<String, RouteBuilder> routes; final Map<String, RouteBuilder> routes;
...@@ -38,18 +41,21 @@ class Navigator extends StatefulComponent { ...@@ -38,18 +41,21 @@ class Navigator extends StatefulComponent {
NavigatorState createState() => new NavigatorState(); NavigatorState createState() => new NavigatorState();
} }
// The navigator tracks which "page" we are on.
// It also animates between these pages.
class NavigatorState extends State<Navigator> { class NavigatorState extends State<Navigator> {
List<Route> _history = new List<Route>(); List<Route> _history = new List<Route>();
int _currentPosition = 0; int _currentPosition = 0; // which route is "current"
Route get currentRoute => _history[_currentPosition]; Route get currentRoute => _history[_currentPosition];
bool get hasPreviousRoute => _history.length > 1; bool get hasPreviousRoute => _history.length > 1;
void initState() { void initState() {
super.initState(); super.initState();
PageRoute route = new PageRoute(config.routes['/']); PageRoute route = new PageRoute(config.routes[kDefaultRouteName], name: kDefaultRouteName);
assert(route != null); assert(route.hasContent);
assert(!route.ephemeral); assert(!route.ephemeral);
_insertRoute(route); _insertRoute(route);
} }
...@@ -67,17 +73,20 @@ class NavigatorState extends State<Navigator> { ...@@ -67,17 +73,20 @@ class NavigatorState extends State<Navigator> {
assert(config.onGenerateRoute != null); assert(config.onGenerateRoute != null);
return config.onGenerateRoute(name); return config.onGenerateRoute(name);
} }
RouteBuilder builder = config.routes[name] ?? generateRoute() ?? config.onUnknownRoute; final RouteBuilder builder = config.routes[name] ?? generateRoute() ?? config.onUnknownRoute;
push(new PageRoute(builder)); assert(builder != null); // 404 getting your 404!
push(new PageRoute(builder, name: name));
} }
void push(Route route) { void push(Route route) {
assert(!_debugCurrentlyHaveRoute(route)); assert(!_debugCurrentlyHaveRoute(route));
setState(() { setState(() {
// pop ephemeral routes (like popup menus)
while (currentRoute.ephemeral) { while (currentRoute.ephemeral) {
currentRoute.didPop(null); currentRoute.didPop(null);
_currentPosition -= 1; _currentPosition -= 1;
} }
// add the new route
_currentPosition += 1; _currentPosition += 1;
_insertRoute(route); _insertRoute(route);
}); });
...@@ -87,21 +96,21 @@ class NavigatorState extends State<Navigator> { ...@@ -87,21 +96,21 @@ class NavigatorState extends State<Navigator> {
assert(_debugCurrentlyHaveRoute(route)); assert(_debugCurrentlyHaveRoute(route));
assert(_currentPosition > 0); assert(_currentPosition > 0);
setState(() { setState(() {
// pop any routes above this one (they must be ephemeral, otherwise there's an error)
while (currentRoute != route) { while (currentRoute != route) {
assert(currentRoute.ephemeral); assert(currentRoute.ephemeral);
currentRoute.didPop(null); currentRoute.didPop(null);
_currentPosition -= 1; _currentPosition -= 1;
} }
assert(_currentPosition > 0);
currentRoute.didPop(result);
_currentPosition -= 1;
}); });
pop(result);
assert(!_debugCurrentlyHaveRoute(route)); assert(!_debugCurrentlyHaveRoute(route));
} }
void pop([dynamic result]) { void pop([dynamic result]) {
setState(() { setState(() {
assert(_currentPosition > 0); assert(_currentPosition > 0);
// pop the route
currentRoute.didPop(result); currentRoute.didPop(result);
_currentPosition -= 1; _currentPosition -= 1;
}); });
...@@ -142,8 +151,8 @@ class NavigatorState extends State<Navigator> { ...@@ -142,8 +151,8 @@ class NavigatorState extends State<Navigator> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Widget> visibleRoutes = new List<Widget>(); List<Widget> visibleRoutes = new List<Widget>();
bool alreadyInsertModalBarrier = false; bool alreadyInsertedModalBarrier = false;
PerformanceView nextPerformance; Route nextContentRoute;
for (int i = _history.length-1; i >= 0; i -= 1) { for (int i = _history.length-1; i >= 0; i -= 1) {
Route route = _history[i]; Route route = _history[i];
if (!route.hasContent) { if (!route.hasContent) {
...@@ -153,20 +162,20 @@ class NavigatorState extends State<Navigator> { ...@@ -153,20 +162,20 @@ class NavigatorState extends State<Navigator> {
visibleRoutes.add( visibleRoutes.add(
new KeyedSubtree( new KeyedSubtree(
key: new ObjectKey(route), key: new ObjectKey(route),
child: route.build(this, nextPerformance) child: route._internalBuild(nextContentRoute)
) )
); );
if (route.isActuallyOpaque) if (route.isActuallyOpaque)
break; break;
assert(route.modal || route.ephemeral); assert(route.modal || route.ephemeral);
if (route.modal && i > 0 && !alreadyInsertModalBarrier) { if (route.modal && i > 0 && !alreadyInsertedModalBarrier) {
visibleRoutes.add(new Listener( visibleRoutes.add(new Listener(
onPointerDown: (_) { pop(); }, onPointerDown: (_) { pop(); },
child: new Container() child: new Container()
)); ));
alreadyInsertModalBarrier = true; alreadyInsertedModalBarrier = true;
} }
nextPerformance = route.performance; nextContentRoute = route;
} }
return new Focus(child: new Stack(visibleRoutes.reversed.toList())); return new Focus(child: new Stack(visibleRoutes.reversed.toList()));
} }
...@@ -227,13 +236,12 @@ abstract class Route { ...@@ -227,13 +236,12 @@ abstract class Route {
PerformanceView get performance => null; PerformanceView get performance => null;
bool get isActuallyOpaque => (performance == null || performance.isCompleted) && opaque; bool get isActuallyOpaque => (performance == null || performance.isCompleted) && opaque;
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance); NavigatorState get navigator => _navigator;
NavigatorState _navigator; NavigatorState _navigator;
void setState(void fn()) { void setState(void fn()) {
assert(_navigator != null); assert(navigator != null);
_navigator.setState(fn); navigator.setState(fn);
} }
void didPush(NavigatorState navigator) { void didPush(NavigatorState navigator) {
...@@ -244,22 +252,36 @@ abstract class Route { ...@@ -244,22 +252,36 @@ abstract class Route {
} }
void didPop([dynamic result]) { void didPop([dynamic result]) {
assert(_navigator != null); assert(navigator != null);
if (performance == null) if (performance == null)
_navigator._removeRoute(this); navigator._removeRoute(this);
} }
void _handlePerformanceStatusChanged(PerformanceStatus status) { void _handlePerformanceStatusChanged(PerformanceStatus status) {
if (status == PerformanceStatus.completed) { if (status == PerformanceStatus.completed) {
_navigator._didCompleteRoute(this); navigator._didCompleteRoute(this);
} else if (status == PerformanceStatus.dismissed) { } else if (status == PerformanceStatus.dismissed) {
_navigator._didDismissRoute(this); navigator._didDismissRoute(this);
_navigator._removeRoute(this); navigator._removeRoute(this);
_navigator = null; _navigator = null;
} }
} }
String toString() => '$runtimeType()'; /// Called by the navigator.build() function if hasContent is true, to get the
/// subtree for this route.
Widget _internalBuild(Route nextRoute) {
assert(navigator != null);
return build(new RouteArguments(
navigator,
previousPerformance: performance,
nextPerformance: nextRoute?.performance
));
}
Widget build(RouteArguments args);
String get debugLabel => '$runtimeType';
String toString() => '$runtimeType(performance: $performance)';
} }
abstract class PerformanceRoute extends Route { abstract class PerformanceRoute extends Route {
...@@ -272,14 +294,12 @@ abstract class PerformanceRoute extends Route { ...@@ -272,14 +294,12 @@ abstract class PerformanceRoute extends Route {
Performance createPerformance() { Performance createPerformance() {
Duration duration = transitionDuration; Duration duration = transitionDuration;
assert(duration >= Duration.ZERO); assert(duration != null && duration >= Duration.ZERO);
return new Performance(duration: duration); return new Performance(duration: duration, debugLabel: debugLabel);
} }
Duration get transitionDuration; Duration get transitionDuration;
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance);
void didPush(NavigatorState navigator) { void didPush(NavigatorState navigator) {
super.didPush(navigator); super.didPush(navigator);
_performance?.forward(); _performance?.forward();
...@@ -294,15 +314,21 @@ abstract class PerformanceRoute extends Route { ...@@ -294,15 +314,21 @@ abstract class PerformanceRoute extends Route {
const Duration _kTransitionDuration = const Duration(milliseconds: 150); const Duration _kTransitionDuration = const Duration(milliseconds: 150);
const Point _kTransitionStartPoint = const Point(0.0, 75.0); const Point _kTransitionStartPoint = const Point(0.0, 75.0);
/// A route that represents a page in an application.
class PageRoute extends PerformanceRoute { class PageRoute extends PerformanceRoute {
PageRoute(this.builder); PageRoute(this._builder, {
this.name: '<anonymous>'
}) {
assert(_builder != null);
}
final RouteBuilder builder; final RouteBuilder _builder;
final String name;
bool get opaque => true; bool get opaque => true;
Duration get transitionDuration => _kTransitionDuration; Duration get transitionDuration => _kTransitionDuration;
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) { Widget build(RouteArguments args) {
// TODO(jackson): Hit testing should ignore transform // TODO(jackson): Hit testing should ignore transform
// TODO(jackson): Block input unless content is interactive // TODO(jackson): Block input unless content is interactive
return new SlideTransition( return new SlideTransition(
...@@ -311,10 +337,23 @@ class PageRoute extends PerformanceRoute { ...@@ -311,10 +337,23 @@ class PageRoute extends PerformanceRoute {
child: new FadeTransition( child: new FadeTransition(
performance: performance, performance: performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.easeOut), opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.easeOut),
child: builder(new RouteArguments(navigator: navigator, previousPerformance: this.performance, nextPerformance: nextRoutePerformance)) child: invokeBuilder(args)
) )
); );
} }
Widget invokeBuilder(RouteArguments args) {
Widget result = _builder(args);
assert(() {
if (result == null)
debugPrint('The builder for route \'$name\' returned null. RouteBuilders must never return null.');
assert(result != null && 'A RouteBuilder returned null. See the previous log message for details.' is String);
return true;
});
return result;
}
String get debugLabel => '${super.debugLabel}($name)';
} }
class StateRoute extends Route { class StateRoute extends Route {
...@@ -335,5 +374,5 @@ class StateRoute extends Route { ...@@ -335,5 +374,5 @@ class StateRoute extends Route {
super.didPop(result); super.didPop(result);
} }
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) => null; Widget build(RouteArguments args) => null;
} }
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