From 90a0f6300f81da2e0a995536cb2e36fbffba5eaa Mon Sep 17 00:00:00 2001 From: Hixie Date: Fri, 2 Oct 2015 15:07:35 -0700 Subject: [PATCH] Simplify the usage of Navigator's routes argument (These are changes cherry-picked from in-flight branches since they are more independent and could be helpful even without those changes.) - Change RouteBuilder's signature to take a single argument in which the other fields are placed, so that we can keep iterating on those arguments without having to break compatibility each time. Also, this makes defining route builders much simpler (only one argument to ignore rather than a variable number). - Expose the next performance to RouteBuilders, since sometimes the route itself might not be where it's used. - Allow BuildContext to be used to walk children, just like it can for ancestors - Allow BuildContext to be used to get the Widget of the current BuildContext - Allow StatefulComponentElement to be referenced with a type specialisation so that you don't have to cast when you know what the type you're dealing with actually is. --- examples/address_book/lib/main.dart | 2 +- examples/demo_launcher/lib/main.dart | 2 +- examples/fitness/lib/main.dart | 74 +++++++++---------- examples/game/example_effect_line.dart | 2 +- examples/game/lib/main.dart | 8 +- examples/game/test_drawatlas.dart | 2 +- examples/game/test_physics.dart | 2 +- examples/stocks/lib/main.dart | 6 +- examples/widgets/card_collection.dart | 2 +- examples/widgets/drag_and_drop.dart | 2 +- examples/widgets/navigation.dart | 16 ++-- examples/widgets/overlay_geometry.dart | 2 +- examples/widgets/pageable_list.dart | 2 +- packages/flutter/lib/src/widgets/dialog.dart | 8 +- packages/flutter/lib/src/widgets/drawer.dart | 3 - .../flutter/lib/src/widgets/framework.dart | 27 ++++--- .../flutter/lib/src/widgets/navigator.dart | 12 ++- packages/unit/test/widget/draggable_test.dart | 4 +- packages/unit/test/widget/navigator_test.dart | 4 +- 19 files changed, 94 insertions(+), 86 deletions(-) diff --git a/examples/address_book/lib/main.dart b/examples/address_book/lib/main.dart index c12a7d2114b..2bca9be10fb 100644 --- a/examples/address_book/lib/main.dart +++ b/examples/address_book/lib/main.dart @@ -101,7 +101,7 @@ void main() { title: 'Address Book', theme: theme, routes: { - '/': (NavigatorState navigator, Route route) => new AddressBookHome(navigator: navigator) + '/': (RouteArguments args) => new AddressBookHome(navigator: args.navigator) } )); } diff --git a/examples/demo_launcher/lib/main.dart b/examples/demo_launcher/lib/main.dart index 4fc089c33d0..2bbef2362be 100644 --- a/examples/demo_launcher/lib/main.dart +++ b/examples/demo_launcher/lib/main.dart @@ -206,7 +206,7 @@ void main() { title: 'Flutter Demos', theme: _theme, routes: { - '/': (NavigatorState navigator, Route route) => new DemoHome() + '/': (RouteArguments args) => new DemoHome() } )); } diff --git a/examples/fitness/lib/main.dart b/examples/fitness/lib/main.dart index 1f12e111a50..98f87db579b 100644 --- a/examples/fitness/lib/main.dart +++ b/examples/fitness/lib/main.dart @@ -92,8 +92,6 @@ class FitnessApp extends StatefulComponent { class FitnessAppState extends State { UserDataImpl _userData; - Map _routes; - void initState() { super.initState(); loadFitnessData().then((UserData data) { @@ -102,36 +100,6 @@ class FitnessAppState extends State { print("Failed to load data: $e"); setState(() => _userData = new UserDataImpl()); }); - - _routes = { - '/': (NavigatorState navigator, Route route) { - return new FeedFragment( - navigator: navigator, - userData: _userData, - onItemCreated: _handleItemCreated, - onItemDeleted: _handleItemDeleted - ); - }, - '/meals/new': (navigator, route) { - return new MealFragment( - navigator: navigator, - onCreated: _handleItemCreated - ); - }, - '/measurements/new': (NavigatorState navigator, Route route) { - return new MeasurementFragment( - navigator: navigator, - onCreated: _handleItemCreated - ); - }, - '/settings': (navigator, route) { - return new SettingsFragment( - navigator: navigator, - userData: _userData, - updater: settingsUpdater - ); - } - }; } void _handleItemCreated(FitnessItem item) { @@ -158,17 +126,43 @@ class FitnessAppState extends State { }); } - final ThemeData _theme = new ThemeData( - brightness: ThemeBrightness.light, - primarySwatch: Colors.indigo, - accentColor: Colors.pinkAccent[200] - ); - Widget build(BuildContext) { return new App( - theme: _theme, + theme: new ThemeData( + brightness: ThemeBrightness.light, + primarySwatch: Colors.indigo, + accentColor: Colors.pinkAccent[200] + ), title: 'Fitness', - routes: _routes + routes: { + '/': (RouteArguments args) { + return new FeedFragment( + navigator: args.navigator, + userData: _userData, + onItemCreated: _handleItemCreated, + onItemDeleted: _handleItemDeleted + ); + }, + '/meals/new': (RouteArguments args) { + return new MealFragment( + navigator: args.navigator, + onCreated: _handleItemCreated + ); + }, + '/measurements/new': (RouteArguments args) { + return new MeasurementFragment( + navigator: args.navigator, + onCreated: _handleItemCreated + ); + }, + '/settings': (RouteArguments args) { + return new SettingsFragment( + navigator: args.navigator, + userData: _userData, + updater: settingsUpdater + ); + } + } ); } } diff --git a/examples/game/example_effect_line.dart b/examples/game/example_effect_line.dart index 4a6e7cc150d..05323d00d9d 100644 --- a/examples/game/example_effect_line.dart +++ b/examples/game/example_effect_line.dart @@ -62,7 +62,7 @@ class TestAppState extends State { ); } - Column _buildColumn(NavigatorState navigator, Route route) { + Column _buildColumn(RouteArguments args) { return new Column([ new Flexible(child: _buildSpriteWidget()), _buildTabBar() diff --git a/examples/game/lib/main.dart b/examples/game/lib/main.dart index a93038d44d4..2cb407aa393 100644 --- a/examples/game/lib/main.dart +++ b/examples/game/lib/main.dart @@ -92,11 +92,11 @@ class GameDemoState extends State { ); } - Widget _buildGameScene(NavigatorState navigator, Route route) { + Widget _buildGameScene(RouteArguments args) { return new SpriteWidget(_game, SpriteBoxTransformMode.fixedWidth); } - Widget _buildMainScene(navigator, route) { + Widget _buildMainScene(RouteArguments args) { return new Stack([ new SpriteWidget(new MainScreenBackground(), SpriteBoxTransformMode.fixedWidth), new Column([ @@ -109,10 +109,10 @@ class GameDemoState extends State { _sounds, (lastScore) { setState(() {_lastScore = lastScore;}); - navigator.pop(); + args.navigator.pop(); } ); - navigator.pushNamed('/game'); + args.navigator.pushNamed('/game'); }, texture: _spriteSheetUI['btn_play_up.png'], textureDown: _spriteSheetUI['btn_play_down.png'], diff --git a/examples/game/test_drawatlas.dart b/examples/game/test_drawatlas.dart index 135334eccc2..dba1748a259 100644 --- a/examples/game/test_drawatlas.dart +++ b/examples/game/test_drawatlas.dart @@ -36,7 +36,7 @@ main() async { title: 'Test drawAtlas', theme: _theme, routes: { - '/': (NavigatorState navigator, Route route) { + '/': (RouteArguments args) { return new SpriteWidget( new TestDrawAtlas(), SpriteBoxTransformMode.fixedWidth diff --git a/examples/game/test_physics.dart b/examples/game/test_physics.dart index 2a9ad884ac0..aabb773bd2a 100644 --- a/examples/game/test_physics.dart +++ b/examples/game/test_physics.dart @@ -34,7 +34,7 @@ main() async { primarySwatch: Colors.purple ), routes: { - '/': (navigator, route) { + '/': (RouteArguments args) { return new SpriteWidget( new TestBed(), SpriteBoxTransformMode.letterbox diff --git a/examples/stocks/lib/main.dart b/examples/stocks/lib/main.dart index 4b2c1e89c39..a931d547eaa 100644 --- a/examples/stocks/lib/main.dart +++ b/examples/stocks/lib/main.dart @@ -82,7 +82,7 @@ class StocksAppState extends State { if (path.length != 3) return null; if (_stocks.containsKey(path[2])) - return (navigator, route) => new StockSymbolViewer(navigator, _stocks[path[2]]); + return (RouteArguments args) => new StockSymbolViewer(args.navigator, _stocks[path[2]]); return null; } return null; @@ -93,8 +93,8 @@ class StocksAppState extends State { title: 'Stocks', theme: theme, routes: { - '/': (navigator, route) => new StockHome(navigator, _stocks, _symbols, _optimismSetting, modeUpdater), - '/settings': (navigator, route) => new StockSettings(navigator, _optimismSetting, _backupSetting, settingsUpdater) + '/': (RouteArguments args) => new StockHome(args.navigator, _stocks, _symbols, _optimismSetting, modeUpdater), + '/settings': (RouteArguments args) => new StockSettings(args.navigator, _optimismSetting, _backupSetting, settingsUpdater) }, onGenerateRoute: _getRoute ); diff --git a/examples/widgets/card_collection.dart b/examples/widgets/card_collection.dart index 0e7a06c4f52..fb0458b0ab7 100644 --- a/examples/widgets/card_collection.dart +++ b/examples/widgets/card_collection.dart @@ -357,7 +357,7 @@ void main() { accentColor: Colors.redAccent[200] ), routes: { - '/': (NavigatorState navigator, Route route) => new CardCollection(navigator: navigator), + '/': (RouteArguments args) => new CardCollection(navigator: args.navigator), } )); } diff --git a/examples/widgets/drag_and_drop.dart b/examples/widgets/drag_and_drop.dart index c7a618763dc..f35934c3af2 100644 --- a/examples/widgets/drag_and_drop.dart +++ b/examples/widgets/drag_and_drop.dart @@ -133,7 +133,7 @@ void main() { runApp(new App( title: 'Drag and Drop Flutter Demo', routes: { - '/': (NavigatorState navigator, Route route) => new DragAndDropApp(navigator: navigator) + '/': (RouteArguments args) => new DragAndDropApp(navigator: args.navigator) } )); } diff --git a/examples/widgets/navigation.dart b/examples/widgets/navigation.dart index c9dd69b1613..7ade991408a 100644 --- a/examples/widgets/navigation.dart +++ b/examples/widgets/navigation.dart @@ -6,46 +6,46 @@ import 'package:sky/material.dart'; import 'package:sky/widgets.dart'; final Map routes = { - '/': (NavigatorState navigator, Route route) => new Container( + '/': (RouteArguments args) => new Container( padding: const EdgeDims.all(30.0), decoration: new BoxDecoration(backgroundColor: const Color(0xFFCCCCCC)), child: new Column([ new Text("You are at home"), new RaisedButton( child: new Text('GO SHOPPING'), - onPressed: () => navigator.pushNamed('/shopping') + onPressed: () => args.navigator.pushNamed('/shopping') ), new RaisedButton( child: new Text('START ADVENTURE'), - onPressed: () => navigator.pushNamed('/adventure') + onPressed: () => args.navigator.pushNamed('/adventure') )], justifyContent: FlexJustifyContent.center ) ), - '/shopping': (NavigatorState navigator, Route route) => new Container( + '/shopping': (RouteArguments args) => new Container( padding: const EdgeDims.all(20.0), decoration: new BoxDecoration(backgroundColor: const Color(0xFFBF5FFF)), child: new Column([ new Text("Village Shop"), new RaisedButton( child: new Text('RETURN HOME'), - onPressed: () => navigator.pop() + onPressed: () => args.navigator.pop() ), new RaisedButton( child: new Text('GO TO DUNGEON'), - onPressed: () => navigator.pushNamed('/adventure') + onPressed: () => args.navigator.pushNamed('/adventure') )], justifyContent: FlexJustifyContent.center ) ), - '/adventure': (NavigatorState navigator, Route route) => new Container( + '/adventure': (RouteArguments args) => new Container( padding: const EdgeDims.all(20.0), decoration: new BoxDecoration(backgroundColor: const Color(0xFFDC143C)), child: new Column([ new Text("Monster's Lair"), new RaisedButton( child: new Text('RUN!!!'), - onPressed: () => navigator.pop() + onPressed: () => args.navigator.pop() )], justifyContent: FlexJustifyContent.center ) diff --git a/examples/widgets/overlay_geometry.dart b/examples/widgets/overlay_geometry.dart index 8a525204d48..6aa66e91ed3 100644 --- a/examples/widgets/overlay_geometry.dart +++ b/examples/widgets/overlay_geometry.dart @@ -165,7 +165,7 @@ void main() { ), title: 'Cards', routes: { - '/': (navigator, route) => new OverlayGeometryApp() + '/': (RouteArguments args) => new OverlayGeometryApp() } )); } diff --git a/examples/widgets/pageable_list.dart b/examples/widgets/pageable_list.dart index c00da4b488e..e68ebbc87ad 100644 --- a/examples/widgets/pageable_list.dart +++ b/examples/widgets/pageable_list.dart @@ -165,7 +165,7 @@ void main() { accentColor: Colors.redAccent[200] ), routes: { - '/': (NavigatorState navigator, Route route) => new PageableListApp(navigator: navigator), + '/': (RouteArguments args) => new PageableListApp(navigator: args.navigator), } )); } diff --git a/packages/flutter/lib/src/widgets/dialog.dart b/packages/flutter/lib/src/widgets/dialog.dart index 54c2a6f1747..4efe02b5a94 100644 --- a/packages/flutter/lib/src/widgets/dialog.dart +++ b/packages/flutter/lib/src/widgets/dialog.dart @@ -145,7 +145,7 @@ class DialogRoute extends Route { return new FadeTransition( performance: performance, opacity: new AnimatedValue(0.0, end: 1.0, curve: easeOut), - child: builder(navigator, this) + child: builder(new RouteArguments(navigator: navigator, previousPerformance: this.performance, nextPerformance: nextRoutePerformance)) ); } @@ -159,11 +159,11 @@ Future showDialog(NavigatorState navigator, DialogBuilder builder) { Completer completer = new Completer(); navigator.push(new DialogRoute( completer: completer, - builder: (navigator, route) { + builder: (RouteArguments args) { return new Focus( - key: new GlobalObjectKey(route), + key: new GlobalObjectKey(completer), autofocus: true, - child: builder(navigator) + child: builder(args.navigator) ); } )); diff --git a/packages/flutter/lib/src/widgets/drawer.dart b/packages/flutter/lib/src/widgets/drawer.dart index 945542c5dda..1d73b9fc84b 100644 --- a/packages/flutter/lib/src/widgets/drawer.dart +++ b/packages/flutter/lib/src/widgets/drawer.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - import 'package:sky/animation.dart'; import 'package:sky/material.dart'; import 'package:sky/src/widgets/animated_container.dart'; @@ -11,7 +9,6 @@ import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/navigator.dart'; -import 'package:sky/src/widgets/scrollable.dart'; import 'package:sky/src/widgets/theme.dart'; import 'package:sky/src/widgets/transitions.dart'; import 'package:sky/src/widgets/focus.dart'; diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index c0cce2b28fa..0d769af67c4 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -486,9 +486,11 @@ final _InactiveElements _inactiveElements = new _InactiveElements(); typedef void ElementVisitor(Element element); abstract class BuildContext { - InheritedWidget inheritedWidgetOfType(Type targetType); + Widget get widget; RenderObject findRenderObject(); + InheritedWidget inheritedWidgetOfType(Type targetType); void visitAncestorElements(bool visitor(Element element)); + void visitChildElements(void visitor(Element element)); } /// Elements are the instantiations of Widget configurations. @@ -540,6 +542,13 @@ abstract class Element implements BuildContext { /// Calls the argument for each child. Must be overridden by subclasses that support having children. void visitChildren(ElementVisitor visitor) { } + /// Wrapper around visitChildren for BuildContext. + void visitChildElements(void visitor(Element element)) { + // don't allow visitChildElements() during build, since children aren't necessarily built yet + assert(BuildableElement._debugStateLockLevel == 0); + visitChildren(visitor); + } + bool detachChild(Element child) => false; /// This method is the core of the system. @@ -952,11 +961,11 @@ abstract class BuildableElement extends Element { /// Instantiation of StatelessComponent widgets. class StatelessComponentElement extends BuildableElement { - StatelessComponentElement(StatelessComponent widget) : super(widget) { + StatelessComponentElement(T widget) : super(widget) { _builder = widget.build; } - void update(StatelessComponent newWidget) { + void update(T newWidget) { super.update(newWidget); assert(widget == newWidget); _builder = widget.build; @@ -966,10 +975,10 @@ class StatelessComponentElement extends BuildableE } /// Instantiation of StatefulComponent widgets. -class StatefulComponentElement extends BuildableElement { - StatefulComponentElement(StatefulComponent widget) +class StatefulComponentElement> extends BuildableElement { + StatefulComponentElement(T widget) : _state = widget.createState(), super(widget) { - assert(_state._debugTypesAreRight(widget)); + assert(_state._debugTypesAreRight(widget)); // can't use T and U, since normally we don't actually set those assert(_state._element == null); _state._element = this; assert(_builder == null); @@ -992,10 +1001,10 @@ class StatefulComponentElement extends BuildableElement { assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; }); } - State get state => _state; - State _state; + U get state => _state; + U _state; - void update(StatefulComponent newWidget) { + void update(T newWidget) { super.update(newWidget); assert(widget == newWidget); StatefulComponent oldConfig = _state._config; diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index 7df0d8fe0a4..f47c70ad5b2 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -8,7 +8,14 @@ import 'package:sky/src/widgets/focus.dart'; import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/transitions.dart'; -typedef Widget RouteBuilder(NavigatorState navigator, Route route); +class RouteArguments { + const RouteArguments({ this.navigator, this.previousPerformance, this.nextPerformance }); + final NavigatorState navigator; + final PerformanceView previousPerformance; + final PerformanceView nextPerformance; +} + +typedef Widget RouteBuilder(RouteArguments args); typedef RouteBuilder RouteGenerator(String name); typedef void StateRouteCallback(StateRoute route); typedef void NotificationCallback(); @@ -154,6 +161,7 @@ class NavigatorState extends State { } return new Focus(child: new Stack(visibleRoutes.reversed.toList())); } + } @@ -272,7 +280,7 @@ class PageRoute extends Route { child: new FadeTransition( performance: performance, opacity: new AnimatedValue(0.0, end: 1.0, curve: easeOut), - child: builder(navigator, this) + child: builder(new RouteArguments(navigator: navigator, previousPerformance: this.performance, nextPerformance: nextRoutePerformance)) ) ); } diff --git a/packages/unit/test/widget/draggable_test.dart b/packages/unit/test/widget/draggable_test.dart index 31120bc3764..8e8b224d2aa 100644 --- a/packages/unit/test/widget/draggable_test.dart +++ b/packages/unit/test/widget/draggable_test.dart @@ -13,9 +13,9 @@ void main() { tester.pumpWidget(new Navigator( routes: { - '/': (NavigatorState navigator, Route route) { return new Column([ + '/': (RouteArguments args) { return new Column([ new Draggable( - navigator: navigator, + navigator: args.navigator, data: 1, child: new Text('Source'), feedback: new Text('Dragging') diff --git a/packages/unit/test/widget/navigator_test.dart b/packages/unit/test/widget/navigator_test.dart index 5da23d03ef7..a9bfcd5e630 100644 --- a/packages/unit/test/widget/navigator_test.dart +++ b/packages/unit/test/widget/navigator_test.dart @@ -49,8 +49,8 @@ void main() { test('Can navigator navigate to and from a stateful component', () { testWidgets((WidgetTester tester) { final Map routes = { - '/': (navigator, route) => new FirstComponent(navigator), - '/second': (navigator, route) => new SecondComponent(navigator), + '/': (RouteArguments args) => new FirstComponent(args.navigator), + '/second': (RouteArguments args) => new SecondComponent(args.navigator), }; tester.pumpWidget(new Navigator(routes: routes));