Port stocks to fn3 and introduce an App component.
diff --git a/examples/stocks/lib/main.dart b/examples/stocks/lib/main.dart
index a70739b..aaacab0 100644
--- a/examples/stocks/lib/main.dart
+++ b/examples/stocks/lib/main.dart
@@ -10,7 +10,8 @@
 
 import 'package:sky/animation.dart';
 import 'package:sky/material.dart';
-import 'package:sky/widgets.dart';
+import 'package:sky/painting.dart';
+import 'package:sky/src/fn3.dart';
 
 import 'stock_data.dart';
 
@@ -22,53 +23,16 @@
 part 'stock_settings.dart';
 part 'stock_types.dart';
 
-class StocksApp extends App {
+class StocksApp extends StatefulComponent {
+  StocksAppState createState() => new StocksAppState();
+}
 
-  NavigationState _navigationState;
-
-  void initState() {
-    _navigationState = new NavigationState([
-      new Route(
-        name: '/',
-        builder: (navigator, route) => new StockHome(navigator, _stocks, optimismSetting, modeUpdater)
-      ),
-      new Route(
-        name: '/settings',
-        builder: (navigator, route) => new StockSettings(navigator, optimismSetting, backupSetting, settingsUpdater)
-      ),
-    ]);
-    super.initState();
-  }
-
-  void onBack() {
-    if (_navigationState.hasPrevious()) {
-      setState(() {
-        _navigationState.pop();
-      });
-    } else {
-      super.onBack();
-    }
-  }
-
-  StockMode optimismSetting = StockMode.optimistic;
-  BackupMode backupSetting = BackupMode.disabled;
-  void modeUpdater(StockMode optimism) {
-    setState(() {
-      optimismSetting = optimism;
-    });
-  }
-  void settingsUpdater({ StockMode optimism, BackupMode backup }) {
-    setState(() {
-      if (optimism != null)
-        optimismSetting = optimism;
-      if (backup != null)
-        backupSetting = backup;
-    });
-  }
+class StocksAppState extends State<StocksApp> {
 
   final List<Stock> _stocks = [];
-  void didMount() {
-    super.didMount();
+
+  void initState(BuildContext context) {
+    super.initState(context);
     new StockDataFetcher((StockData data) {
       setState(() {
         data.appendTo(_stocks);
@@ -76,32 +40,47 @@
     });
   }
 
-  Widget build() {
+  StockMode _optimismSetting = StockMode.optimistic;
+  BackupMode _backupSetting = BackupMode.disabled;
+  void modeUpdater(StockMode optimism) {
+    setState(() {
+      _optimismSetting = optimism;
+    });
+  }
+  void settingsUpdater({ StockMode optimism, BackupMode backup }) {
+    setState(() {
+      if (optimism != null)
+        _optimismSetting = optimism;
+      if (backup != null)
+        _backupSetting = backup;
+    });
+  }
 
-    ThemeData theme;
-    if (optimismSetting == StockMode.optimistic) {
-      theme = new ThemeData(
-        brightness: ThemeBrightness.light,
-        primarySwatch: Colors.purple
-      );
-    } else {
-      theme = new ThemeData(
-        brightness: ThemeBrightness.dark,
-        accentColor: Colors.redAccent[200]
-      );
+  ThemeData get theme {
+    switch (_optimismSetting) {
+      case StockMode.optimistic:
+        return new ThemeData(
+          brightness: ThemeBrightness.light,
+          primarySwatch: Colors.purple
+        );
+      case StockMode.pessimistic:
+        return new ThemeData(
+          brightness: ThemeBrightness.dark,
+          accentColor: Colors.redAccent[200]
+        );
     }
+  }
 
-    return new Theme(
-      data: theme,
-        child: new DefaultTextStyle(
-          style: Typography.error, // if you see this, you've forgotten to correctly configure the text style!
-          child: new Title(
-            title: 'Stocks',
-            child: new Navigator(_navigationState)
-          )
-        )
-     );
-   }
+  Widget build(BuildContext context) {
+    return new App(
+      title: 'Stocks',
+      theme: theme,
+      routes: <String, RouteBuilder>{
+         '/':         (navigator, route) => new StockHome(navigator, _stocks, _optimismSetting, modeUpdater),
+         '/settings': (navigator, route) => new StockSettings(navigator, _optimismSetting, _backupSetting, settingsUpdater)
+      }
+    );
+  }
 }
 
 void main() {
diff --git a/examples/stocks/lib/stock_arrow.dart b/examples/stocks/lib/stock_arrow.dart
index 394eda3..a1f56b5 100644
--- a/examples/stocks/lib/stock_arrow.dart
+++ b/examples/stocks/lib/stock_arrow.dart
@@ -4,8 +4,7 @@
 
 part of stocks;
 
-class StockArrow extends Component {
-
+class StockArrow extends StatelessComponent {
   StockArrow({ Key key, this.percentChange }) : super(key: key);
 
   final double percentChange;
@@ -22,7 +21,7 @@
     return Colors.red[_colorIndexForPercentChange(percentChange)];
   }
 
-  Widget build() {
+  Widget build(BuildContext context) {
     // TODO(jackson): This should change colors with the theme
     Color color = _colorForPercentChange(percentChange);
     const double kSize = 40.0;
@@ -65,5 +64,4 @@
       margin: const EdgeDims.symmetric(horizontal: 5.0)
     );
   }
-
 }
diff --git a/examples/stocks/lib/stock_home.dart b/examples/stocks/lib/stock_home.dart
index c85dd54..ee47f6e 100644
--- a/examples/stocks/lib/stock_home.dart
+++ b/examples/stocks/lib/stock_home.dart
@@ -9,20 +9,17 @@
 const Duration _kSnackbarSlideDuration = const Duration(milliseconds: 200);
 
 class StockHome extends StatefulComponent {
-
   StockHome(this.navigator, this.stocks, this.stockMode, this.modeUpdater);
 
-  Navigator navigator;
-  List<Stock> stocks;
-  StockMode stockMode;
-  ModeUpdater modeUpdater;
+  final NavigatorState navigator;
+  final List<Stock> stocks;
+  final StockMode stockMode;
+  final ModeUpdater modeUpdater;
 
-  void syncConstructorArguments(StockHome source) {
-    navigator = source.navigator;
-    stocks = source.stocks;
-    stockMode = source.stockMode;
-    modeUpdater = source.modeUpdater;
-  }
+  StockHomeState createState() => new StockHomeState();
+}
+
+class StockHomeState extends State<StockHome> {
 
   bool _isSearching = false;
   String _searchQuery;
@@ -31,7 +28,7 @@
   bool _isSnackBarShowing = false;
 
   void _handleSearchBegin() {
-    navigator.pushState(this, (_) {
+    config.navigator.pushState(this, (_) {
       setState(() {
         _isSearching = false;
         _searchQuery = null;
@@ -43,9 +40,9 @@
   }
 
   void _handleSearchEnd() {
-    assert(navigator.currentRoute is RouteState);
-    assert((navigator.currentRoute as RouteState).owner == this); // TODO(ianh): remove cast once analyzer is cleverer
-    navigator.pop();
+    assert(config.navigator.currentRoute is RouteState);
+    assert((config.navigator.currentRoute as RouteState).owner == this); // TODO(ianh): remove cast once analyzer is cleverer
+    config.navigator.pop();
     setState(() {
       _isSearching = false;
       _searchQuery = null;
@@ -82,15 +79,12 @@
   }
 
   void _handleStockModeChange(StockMode value) {
-    setState(() {
-      stockMode = value;
-    });
-    if (modeUpdater != null)
-      modeUpdater(value);
+    if (config.modeUpdater != null)
+      config.modeUpdater(value);
   }
 
   void _handleMenuShow() {
-    showStockMenu(navigator,
+    showStockMenu(config.navigator,
       autorefresh: _autorefresh,
       onAutorefreshChanged: _handleAutorefreshChanged
     );
@@ -104,7 +98,7 @@
       level: 3,
       showing: _drawerShowing,
       onDismissed: _handleDrawerDismissed,
-      navigator: navigator,
+      navigator: config.navigator,
       children: [
         new DrawerHeader(child: new Text('Stocks')),
         new DrawerItem(
@@ -122,7 +116,7 @@
           onPressed: () => _handleStockModeChange(StockMode.optimistic),
           child: new Row([
             new Flexible(child: new Text('Optimistic')),
-            new Radio(value: StockMode.optimistic, groupValue: stockMode, onChanged: _handleStockModeChange)
+            new Radio(value: StockMode.optimistic, groupValue: config.stockMode, onChanged: _handleStockModeChange)
           ])
         ),
         new DrawerItem(
@@ -130,7 +124,7 @@
           onPressed: () => _handleStockModeChange(StockMode.pessimistic),
           child: new Row([
             new Flexible(child: new Text('Pessimistic')),
-            new Radio(value: StockMode.pessimistic, groupValue: stockMode, onChanged: _handleStockModeChange)
+            new Radio(value: StockMode.pessimistic, groupValue: config.stockMode, onChanged: _handleStockModeChange)
           ])
         ),
         new DrawerDivider(),
@@ -146,23 +140,26 @@
   }
 
   void _handleShowSettings() {
-    navigator.pop();
-    navigator.pushNamed('/settings');
+    config.navigator.pop();
+    config.navigator.pushNamed('/settings');
   }
 
   Widget buildToolBar() {
     return new ToolBar(
         left: new IconButton(
           icon: "navigation/menu",
-          onPressed: _handleOpenDrawer),
+          onPressed: _handleOpenDrawer
+        ),
         center: new Text('Stocks'),
         right: [
           new IconButton(
             icon: "action/search",
-            onPressed: _handleSearchBegin),
+            onPressed: _handleSearchBegin
+          ),
           new IconButton(
             icon: "navigation/more_vert",
-            onPressed: _handleMenuShow)
+            onPressed: _handleMenuShow
+          )
         ]
       );
   }
@@ -181,12 +178,12 @@
     return stocks.where((stock) => stock.symbol.contains(regexp));
   }
 
-  Widget buildMarketStockList() {
-    return new Stocklist(stocks: _filterBySearchQuery(stocks).toList());
+  Widget buildMarketStockList(BuildContext context) {
+    return new Stocklist(stocks: _filterBySearchQuery(config.stocks).toList());
   }
 
-  Widget buildPortfolioStocklist() {
-    return new Stocklist(stocks: _filterBySearchQuery(_filterByPortfolio(stocks)).toList());
+  Widget buildPortfolioStocklist(BuildContext context) {
+    return new Stocklist(stocks: _filterBySearchQuery(_filterByPortfolio(config.stocks)).toList());
   }
 
   Widget buildTabNavigator() {
@@ -216,7 +213,7 @@
     return new ToolBar(
       left: new IconButton(
         icon: "navigation/arrow_back",
-        color: Theme.of(this).accentColor,
+        color: Theme.of(context).accentColor,
         onPressed: _handleSearchEnd
       ),
       center: new Input(
@@ -224,7 +221,7 @@
         placeholder: 'Search stocks',
         onChanged: _handleSearchQueryChanged
       ),
-      backgroundColor: Theme.of(this).canvasColor
+      backgroundColor: Theme.of(context).canvasColor
     );
   }
 
@@ -255,17 +252,14 @@
   }
 
   Widget buildFloatingActionButton() {
-    return new TransitionProxy(
-      transitionKey: snackBarKey,
-      child: new FloatingActionButton(
-        child: new Icon(type: 'content/add', size: 24),
-        backgroundColor: Colors.redAccent[200],
-        onPressed: _handleStockPurchased
-      )
+    return new FloatingActionButton(
+      child: new Icon(type: 'content/add', size: 24),
+      backgroundColor: Colors.redAccent[200],
+      onPressed: _handleStockPurchased
     );
   }
 
-  Widget build() {
+  Widget build(BuildContext context) {
     return new Scaffold(
       toolbar: _isSearching ? buildSearchBar() : buildToolBar(),
       body: buildTabNavigator(),
diff --git a/examples/stocks/lib/stock_list.dart b/examples/stocks/lib/stock_list.dart
index 42ea384..a65d735 100644
--- a/examples/stocks/lib/stock_list.dart
+++ b/examples/stocks/lib/stock_list.dart
@@ -4,18 +4,18 @@
 
 part of stocks;
 
-class Stocklist extends Component {
+class Stocklist extends StatelessComponent {
   Stocklist({ Key key, this.stocks }) : super(key: key);
 
   final List<Stock> stocks;
 
-  Widget build() {
+  Widget build(BuildContext context) {
     return new Material(
       type: MaterialType.canvas,
       child: new ScrollableList<Stock>(
         items: stocks,
         itemExtent: StockRow.kHeight,
-        itemBuilder: (Stock stock) => new StockRow(stock: stock)
+        itemBuilder: (BuildContext context, Stock stock) => new StockRow(stock: stock)
       )
     );
   }
diff --git a/examples/stocks/lib/stock_menu.dart b/examples/stocks/lib/stock_menu.dart
index a61073a..f9c3278 100644
--- a/examples/stocks/lib/stock_menu.dart
+++ b/examples/stocks/lib/stock_menu.dart
@@ -4,19 +4,27 @@
 
 part of stocks;
 
-Future showStockMenu(Navigator navigator, { bool autorefresh, ValueChanged onAutorefreshChanged }) {
-  return showMenu(
+enum _MenuItems { add, remove, autorefresh }
+
+Future showStockMenu(NavigatorState navigator, { bool autorefresh, ValueChanged onAutorefreshChanged }) async {
+  switch (await showMenu(
     navigator: navigator,
     position: new MenuPosition(
       right: sky.view.paddingRight,
       top: sky.view.paddingTop
     ),
-    builder: (Navigator navigator) {
+    builder: (NavigatorState navigator) {
       return <PopupMenuItem>[
-        new PopupMenuItem(child: new Text('Add stock')),
-        new PopupMenuItem(child: new Text('Remove stock')),
         new PopupMenuItem(
-          onPressed: () => onAutorefreshChanged(!autorefresh),
+          value: _MenuItems.add,
+          child: new Text('Add stock')
+        ),
+        new PopupMenuItem(
+          value: _MenuItems.remove,
+          child: new Text('Remove stock')
+        ),
+        new PopupMenuItem(
+          value: _MenuItems.autorefresh,
           child: new Row([
               new Flexible(child: new Text('Autorefresh')),
               new Checkbox(
@@ -28,5 +36,28 @@
         ),
       ];
     }
-  );
+  )) {
+    case _MenuItems.autorefresh:
+      onAutorefreshChanged(!autorefresh);
+      break;
+    case _MenuItems.add:
+    case _MenuItems.remove:
+      await showDialog(navigator, (NavigatorState navigator) {
+        return new Dialog(
+          title: new Text('Not Implemented'),
+          content: new Text('This feature has not yet been implemented.'),
+          actions: [
+            new FlatButton(
+              child: new Text('OH WELL'),
+              onPressed: () {
+                navigator.pop(false);
+              }
+            ),
+          ]
+        );
+      });
+      break;
+    default:
+      // menu was canceled.
+  }
 }
\ No newline at end of file
diff --git a/examples/stocks/lib/stock_row.dart b/examples/stocks/lib/stock_row.dart
index f2f4e68..4ebe859 100644
--- a/examples/stocks/lib/stock_row.dart
+++ b/examples/stocks/lib/stock_row.dart
@@ -4,15 +4,14 @@
 
 part of stocks;
 
-class StockRow extends Component {
-
+class StockRow extends StatelessComponent {
   StockRow({ Stock stock }) : this.stock = stock, super(key: new Key(stock.symbol));
 
   final Stock stock;
 
   static const double kHeight = 79.0;
 
-  Widget build() {
+  Widget build(BuildContext context) {
     String lastSale = "\$${stock.lastSale.toStringAsFixed(2)}";
 
     String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%";
@@ -32,7 +31,7 @@
       new Flexible(
         child: new Text(
           changeInPrice,
-          style: Theme.of(this).text.caption.copyWith(textAlign: TextAlign.right)
+          style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right)
         )
       )
     ];
@@ -43,7 +42,7 @@
       height: kHeight,
       decoration: new BoxDecoration(
         border: new Border(
-          bottom: new BorderSide(color: Theme.of(this).dividerColor)
+          bottom: new BorderSide(color: Theme.of(context).dividerColor)
         )
       ),
       child: new Row([
@@ -55,7 +54,7 @@
           child: new Row(
             children,
             alignItems: FlexAlignItems.baseline,
-            textBaseline: DefaultTextStyle.of(this).textBaseline
+            textBaseline: DefaultTextStyle.of(context).textBaseline
           )
         )
       ])
diff --git a/examples/stocks/lib/stock_settings.dart b/examples/stocks/lib/stock_settings.dart
index 19f3125..269afe1 100644
--- a/examples/stocks/lib/stock_settings.dart
+++ b/examples/stocks/lib/stock_settings.dart
@@ -10,42 +10,32 @@
 });
 
 class StockSettings extends StatefulComponent {
+  const StockSettings(this.navigator, this.optimism, this.backup, this.updater);
 
-  StockSettings(this.navigator, this.optimism, this.backup, this.updater);
+  final NavigatorState navigator;
+  final StockMode optimism;
+  final BackupMode backup;
+  final SettingsUpdater updater;
 
-  Navigator navigator;
-  StockMode optimism;
-  BackupMode backup;
-  SettingsUpdater updater;
+  StockSettingsState createState() => new StockSettingsState();
+}
 
-  void syncConstructorArguments(StockSettings source) {
-    navigator = source.navigator;
-    optimism = source.optimism;
-    backup = source.backup;
-    updater = source.updater;
-  }
-
+class StockSettingsState extends State<StockSettings> {
   void _handleOptimismChanged(bool value) {
-    setState(() {
-      optimism = value ? StockMode.optimistic : StockMode.pessimistic;
-    });
-    sendUpdates();
+    sendUpdates(value ? StockMode.optimistic : StockMode.pessimistic, config.backup);
   }
 
   void _handleBackupChanged(bool value) {
-    setState(() {
-      backup = value ? BackupMode.enabled : BackupMode.disabled;
-    });
-    sendUpdates();
+    sendUpdates(config.optimism, value ? BackupMode.enabled : BackupMode.disabled);
   }
 
   void _confirmOptimismChange() {
-    switch (optimism) {
+    switch (config.optimism) {
       case StockMode.optimistic:
         _handleOptimismChanged(false);
         break;
       case StockMode.pessimistic:
-        showDialog(navigator, (navigator) {
+        showDialog(config.navigator, (NavigatorState navigator) {
           return new Dialog(
             title: new Text("Change mode?"),
             content: new Text("Optimistic mode means everything is awesome. Are you sure you can handle that?"),
@@ -72,24 +62,25 @@
     }
   }
 
-  void sendUpdates() {
-    if (updater != null)
-      updater(
+  void sendUpdates(StockMode optimism, BackupMode backup) {
+    if (config.updater != null)
+      config.updater(
         optimism: optimism,
         backup: backup
       );
   }
 
-  Widget buildToolBar() {
+  Widget buildToolBar(BuildContext context) {
     return new ToolBar(
       left: new IconButton(
         icon: 'navigation/arrow_back',
-        onPressed: navigator.pop),
+        onPressed: config.navigator.pop
+      ),
       center: new Text('Settings')
     );
   }
 
-  Widget buildSettingsPane() {
+  Widget buildSettingsPane(BuildContext context) {
     // TODO(ianh): Once we have the gesture API hooked up, fix https://github.com/domokit/mojo/issues/281
     // (whereby tapping the widgets below causes both the widget and the menu item to fire their callbacks)
     return new Material(
@@ -103,15 +94,21 @@
               onPressed: () => _confirmOptimismChange(),
               child: new Row([
                 new Flexible(child: new Text('Everything is awesome')),
-                new Checkbox(value: optimism == StockMode.optimistic, onChanged: (_) => _confirmOptimismChange()),
+                new Checkbox(
+                  value: config.optimism == StockMode.optimistic,
+                  onChanged: (_) => _confirmOptimismChange()
+                ),
               ])
             ),
             new DrawerItem(
               icon: 'action/backup',
-              onPressed: () { _handleBackupChanged(!(backup == BackupMode.enabled)); },
+              onPressed: () { _handleBackupChanged(!(config.backup == BackupMode.enabled)); },
               child: new Row([
                 new Flexible(child: new Text('Back up stock list to the cloud')),
-                new Switch(value: backup == BackupMode.enabled, onChanged: _handleBackupChanged),
+                new Switch(
+                  value: config.backup == BackupMode.enabled,
+                  onChanged: _handleBackupChanged
+                ),
               ])
             ),
           ])
@@ -120,10 +117,10 @@
     );
   }
 
-  Widget build() {
+  Widget build(BuildContext context) {
     return new Scaffold(
-      toolbar: buildToolBar(),
-      body: buildSettingsPane()
+      toolbar: buildToolBar(context),
+      body: buildSettingsPane(context)
     );
   }
 }