refactor(examples/todos): update flutter code

This is an initial pass to get the todo example into a condition that allows
multiple contributors to work on it.

* Updates flutter code to match version: cc93170894
* Adds rough screens based on the Vanadium Todos example

Remaing tasks are being tracked in vanadium/baku#1 and will follow as separate
CLs.

Change-Id: I2b1fb33fb5addfbdb844ae0163c56b724ed72ffa
diff --git a/examples/todos/.gitignore b/examples/todos/.gitignore
index dd20bb4..14c7d4c 100644
--- a/examples/todos/.gitignore
+++ b/examples/todos/.gitignore
@@ -4,5 +4,6 @@
 .packages
 .pub/
 build/
+ios/.generated/
 packages
 pubspec.lock
diff --git a/examples/todos/README.md b/examples/todos/README.md
index 1e1caa6..2df1b5e 100644
--- a/examples/todos/README.md
+++ b/examples/todos/README.md
@@ -1,6 +1,8 @@
 # todos
 
-Details coming soon.
+A new flutter project.
 
-    pub get
-    flutter start && flutter logs
+## Getting Started
+
+For help getting started with Flutter, view our online
+[documentation](http://flutter.io/).
diff --git a/examples/todos/apk/AndroidManifest.xml b/examples/todos/android/AndroidManifest.xml
similarity index 66%
rename from examples/todos/apk/AndroidManifest.xml
rename to examples/todos/android/AndroidManifest.xml
index 91febc0..cbef8cc 100644
--- a/examples/todos/apk/AndroidManifest.xml
+++ b/examples/todos/android/AndroidManifest.xml
@@ -1,15 +1,18 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.todos">
+    package="com.yourcompany.todos"
+    android:versionCode="1"
+    android:versionName="0.0.1">
 
     <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="21" />
     <uses-permission android:name="android.permission.INTERNET"/>
 
-    <application android:name="org.domokit.sky.shell.SkyApplication" android:label="todos">
+    <application android:name="org.domokit.sky.shell.SkyApplication" android:label="todos" android:icon="@mipmap/ic_launcher">
         <activity android:name="org.domokit.sky.shell.SkyActivity"
-                  android:launchMode="singleTask"
+                  android:launchMode="singleTop"
                   android:theme="@android:style/Theme.Black.NoTitleBar"
-                  android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
-                  android:hardwareAccelerated="true">
+                  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
+                  android:hardwareAccelerated="true"
+                  android:windowSoftInputMode="adjustResize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
diff --git a/examples/todos/android/res/mipmap-hdpi/ic_launcher.png b/examples/todos/android/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..f0a55cd
--- /dev/null
+++ b/examples/todos/android/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/examples/todos/android/res/mipmap-mdpi/ic_launcher.png b/examples/todos/android/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..eb98dc4
--- /dev/null
+++ b/examples/todos/android/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/examples/todos/android/res/mipmap-xhdpi/ic_launcher.png b/examples/todos/android/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..f1783db
--- /dev/null
+++ b/examples/todos/android/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/examples/todos/android/res/mipmap-xxhdpi/ic_launcher.png b/examples/todos/android/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..46828a2
--- /dev/null
+++ b/examples/todos/android/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/examples/todos/android/res/mipmap-xxxhdpi/ic_launcher.png b/examples/todos/android/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..a527671
--- /dev/null
+++ b/examples/todos/android/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/examples/todos/flutter.yaml b/examples/todos/flutter.yaml
index fe54c87..3e5efa7 100644
--- a/examples/todos/flutter.yaml
+++ b/examples/todos/flutter.yaml
@@ -1,8 +1,2 @@
 name: todos
-material-design-icons:
-  - name: action/check_circle
-  - name: action/delete
-  - name: content/add
-  - name: content/clear
-  - name: navigation/arrow_back
-  - name: navigation/close
+uses-material-design: true
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..363d1c4
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,142 @@
+{
+  "images" : [
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-Small@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-Small@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-Small-40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-Small-40@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-60@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-60@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-Small.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-Small@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-Small-40.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-Small-40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-76.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-76@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "83.5x83.5",
+      "idiom" : "ipad",
+      "filename" : "Icon-83.5@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "icon_16x16.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "icon_16x16@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "icon_32x32.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "icon_32x32@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "icon_128x128.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "icon_128x128@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "icon_256x256.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "icon_256x256@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "icon_512x512.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "icon_512x512@2x.png",
+      "scale" : "2x"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
new file mode 100644
index 0000000..9996f5e
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
new file mode 100644
index 0000000..7a543ed
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-76.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-76.png
new file mode 100644
index 0000000..05a8268
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-76.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
new file mode 100644
index 0000000..bfbca28
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
new file mode 100644
index 0000000..c924b8e
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png
new file mode 100644
index 0000000..3006f1d
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png
new file mode 100644
index 0000000..5f7d22d
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png
new file mode 100644
index 0000000..9996f5e
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small.png
new file mode 100644
index 0000000..3433da1
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
new file mode 100644
index 0000000..f7f9f16
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
new file mode 100644
index 0000000..e9c2360
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_128x128.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_128x128.png
new file mode 100644
index 0000000..dd5fccb
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_128x128.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png
new file mode 100644
index 0000000..1909e27
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_16x16.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_16x16.png
new file mode 100644
index 0000000..40a701b
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_16x16.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png
new file mode 100644
index 0000000..06c1a80
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_256x256.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_256x256.png
new file mode 100644
index 0000000..1909e27
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_256x256.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png
new file mode 100644
index 0000000..69c96ef
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_32x32.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_32x32.png
new file mode 100644
index 0000000..06c1a80
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_32x32.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png
new file mode 100644
index 0000000..0f94371
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_512x512.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_512x512.png
new file mode 100644
index 0000000..69c96ef
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_512x512.png
Binary files differ
diff --git a/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png
new file mode 100644
index 0000000..1d6355f
--- /dev/null
+++ b/examples/todos/ios/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png
Binary files differ
diff --git a/examples/todos/ios/Info.plist b/examples/todos/ios/Info.plist
new file mode 100644
index 0000000..7758887
--- /dev/null
+++ b/examples/todos/ios/Info.plist
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>Runner</string>
+	<key>CFBundleIdentifier</key>
+	<string>com.yourcompany.todos</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>todos</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIRequiredDeviceCapabilities</key>
+	<array>
+		<string>arm64</string>
+	</array>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UIViewControllerBasedStatusBarAppearance</key>
+	<false/>
+</dict>
+</plist>
diff --git a/examples/todos/ios/LaunchScreen.storyboard b/examples/todos/ios/LaunchScreen.storyboard
new file mode 100644
index 0000000..78686cd
--- /dev/null
+++ b/examples/todos/ios/LaunchScreen.storyboard
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
+                        <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+</document>
diff --git a/examples/todos/lib/components/todo_collection.dart b/examples/todos/lib/components/todo_collection.dart
deleted file mode 100644
index f3baf81..0000000
--- a/examples/todos/lib/components/todo_collection.dart
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2015 The Vanadium 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/material.dart';
-
-import 'todo_item.dart';
-import 'todo_dialog.dart';
-import '../todo_model.dart';
-
-/// [TodoCollection] is a [StatefulComponent] component.
-///
-/// This component is responsible for holding a list of TodoModel instances
-/// and rendering the list appropriately. A Todo list fills the whole screen
-/// and is composed of a tool bar, a list view, and the floating action button
-/// for adding a new Todo item.
-class TodoCollection extends StatefulComponent {
-  TodoCollectionState createState() => new TodoCollectionState();
-}
-
-class TodoCollectionState extends State<TodoCollection> {
-  List<TodoModel> todos = new List<TodoModel>();
-
-  void removeTodo(TodoModel todo) {
-    setState(() {
-      todos.remove(todo);
-    });
-  }
-
-  void addTodo(String title) {
-    // Use `setState(fn)` to signal to the framework that some internal state
-    // has been changed and a needs to be rebuilt. If the state update is not
-    // wrapped in a `setState` call the change will not be displayed to the
-    // screen.
-    setState(() {
-      todos.add(new TodoModel(title: title));
-    });
-  }
-
-  Widget buildTodo(BuildContext context, int index) {
-    if (index >= todos.length) {
-      return null;
-    }
-
-    TodoModel todo = todos[index];
-
-    return new TodoItem(
-        // Key is required here to support a requirement of child
-        // ScrollableMixedWidgetList.
-        key: todo.key,
-        todo: todo,
-        onDelete: removeTodo);
-  }
-
-  Widget build(BuildContext context) {
-    return new Scaffold(
-        toolBar: new ToolBar(center: new Text('TODO List')),
-        // Avoiding MaterialList/ScrollableList here since children
-        // would be required to have a pre-defined height via the contstructor
-        // property itemExtent.
-        // SEE: http://goo.gl/cB5bOE
-        body: new ScrollableMixedWidgetList(
-            builder: buildTodo, token: todos.length),
-        floatingActionButton: new FloatingActionButton(
-            child: new Icon(icon: 'content/add'),
-            onPressed: () => showDialog(
-                context: context, child: new TodoDialog(onSave: addTodo))));
-  }
-}
diff --git a/examples/todos/lib/components/todo_dialog.dart b/examples/todos/lib/components/todo_dialog.dart
deleted file mode 100644
index 3c542b7..0000000
--- a/examples/todos/lib/components/todo_dialog.dart
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2015 The Vanadium 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/material.dart';
-
-typedef void TodoHandleSave(Todo);
-
-class TodoDialog extends StatefulComponent {
-  TodoDialog({Key key, this.value: "", this.title: "Add Todo", this.onSave})
-      : super(key: key);
-
-  final String title;
-  final TodoHandleSave onSave;
-  final String value;
-
-  TodoDialogState createState() => new TodoDialogState();
-}
-
-class TodoDialogState extends State<TodoDialog> {
-  String value;
-
-  @override
-  void initState() {
-    super.initState();
-    value = config.value;
-  }
-
-  void handleInputChange(String update) {
-    value = update;
-  }
-
-  // The Input widget gets different values on change versus on submit, be
-  // sure to update the todo with the most recent value.
-  void handleInputSubmit(String value) {
-    handleInputChange(value);
-    save();
-  }
-
-  void close() {
-    Navigator.pop(context);
-  }
-
-  void save() {
-    close();
-    config.onSave(value);
-  }
-
-  static final GlobalKey inputKey = new GlobalKey(debugLabel: 'todo input');
-
-  Widget build(BuildContext context) {
-    Text label = new Text("What needs to get done?");
-    Input input = new Input(
-        key: inputKey,
-        initialValue: value,
-        onChanged: handleInputChange,
-        onSubmitted: handleInputSubmit);
-    List children = <Widget>[label, input];
-
-    return new Scaffold(
-        toolBar: buildToolbar(),
-        body: new Container(
-            margin: new EdgeDims.all(24.0), child: new Block(children)));
-  }
-
-  Widget buildToolbar() {
-    return new ToolBar(
-        left: new IconButton(icon: "navigation/close", onPressed: close),
-        center: new Text(config.title),
-        right: <Widget>[
-          // NOTE: The right spacing is off here.
-          new FlatButton(
-              child: new Text('SAVE'),
-              // NOTE: without this the button text color is black instead of
-              // white. Is there a correct/canonical way to reach into the
-              // theme and grab the toolbar text color to use here?
-              textColor: Colors.white,
-              onPressed: save)
-        ]);
-  }
-}
diff --git a/examples/todos/lib/components/todo_item.dart b/examples/todos/lib/components/todo_item.dart
deleted file mode 100644
index 51f4ce6..0000000
--- a/examples/todos/lib/components/todo_item.dart
+++ /dev/null
@@ -1,252 +0,0 @@
-// Copyright 2015 The Vanadium 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 'dart:ui' as ui;
-import 'package:flutter/material.dart';
-import 'package:flutter/painting.dart';
-import 'package:flutter/rendering.dart';
-import 'package:flutter/gestures.dart';
-import 'package:flutter/animation.dart';
-
-import "../todo_model.dart";
-
-const Duration _todoSnapDuration = const Duration(milliseconds: 200);
-
-typedef void TodoHandleDelete(TodoModel);
-
-enum TodoStatus { idle, completing, deleting, }
-
-class TodoItem extends StatefulComponent {
-  TodoItem({Key key, this.todo, this.onDelete}) : super(key: key) {
-    assert(todo != null);
-    assert(onDelete != null);
-  }
-
-  final TodoModel todo;
-  final TodoHandleDelete onDelete;
-
-  TodoItemState createState() => new TodoItemState();
-}
-
-// TODO(jasoncampbell): Create a throttle animation so that dragging
-// appears to slow down as the item gets closer to the threshold.
-class TodoItemState extends State<TodoItem> {
-  HorizontalDragGestureRecognizer drag;
-  ValuePerformance<double> snapPerformance;
-
-  TodoItemState() {
-    drag = new HorizontalDragGestureRecognizer(
-        router: Gesturer.instance.pointerRouter,
-        gestureArena: Gesturer.instance.gestureArena)
-      ..onStart = handleDragStart
-      ..onUpdate = handleDragUpdate
-      ..onEnd = handleDragEnd;
-
-    snapPerformance = new ValuePerformance<double>(
-        variable: snapValue, duration: _todoSnapDuration)
-      ..addStatusListener(handleSnapAnimationStatus);
-  }
-
-  TodoModel todo;
-  TodoStatus status;
-  Size size = new Size(ui.window.size.width, 0.0);
-  double position = 0.0;
-  double lastDelta;
-
-  final double dragThreshold = ui.window.size.width / 2 * 0.75;
-
-  final AnimatedValue<double> snapValue =
-      new AnimatedValue<double>(1.0, end: 0.0, curve: Curves.easeOut);
-
-  /// Override `initState()`.
-  @override
-  void initState() {
-    super.initState();
-
-    todo = config.todo;
-  }
-
-  void dispose() {
-    snapPerformance?.stop();
-    super.dispose();
-  }
-
-  void handleSnapAnimationStatus(PerformanceStatus update) {
-    if (update == PerformanceStatus.completed) {
-      setState(() {
-        snapPerformance.progress = 0.0;
-        position = 0.0;
-
-        // Make sure the right thing happens after the snap animation.
-        switch (status) {
-          case TodoStatus.completing:
-            todo.completed = true;
-            status = TodoStatus.idle;
-            break;
-          case TodoStatus.deleting:
-            config.onDelete(todo);
-            break;
-          default:
-            status = TodoStatus.idle;
-        }
-      });
-    }
-  }
-
-  void handlePointerDown(PointerDownEvent event) {
-    drag.addPointer(event);
-  }
-
-  void handleDragStart(Point globalPosition) {
-    position = 0.0;
-  }
-
-  void handleDragUpdate(double delta) {
-    double update = position + delta;
-
-    // NOTE: My eyes might be playing tricks on me but I think this prevents
-    // some jitter while dragging a Todo horizonatlly.
-    if (position != update) {
-      bool pastThreshold = update.abs() > dragThreshold;
-
-      // Prevent the drag from moving past the treshold.
-      if (!pastThreshold) {
-        setState(() {
-          lastDelta = delta;
-          position = update;
-
-          double statusThreshold = dragThreshold / 2;
-
-          // Update status based on the current position.
-          if (position.abs() > statusThreshold) {
-            if (position.isNegative) {
-              // Dragging to the left (revealing the delete icon).
-              status = TodoStatus.deleting;
-            } else {
-              // Dragging to the right (revealing the complete icon).
-              status = TodoStatus.completing;
-            }
-          } else {
-            // When dragging back and forth between the original position and
-            // the status threshold return the item to the idle state.
-            status = TodoStatus.idle;
-          }
-        });
-      }
-    }
-  }
-
-  void handleDragEnd(Offset velocity) {
-    snapPerformance.play();
-  }
-
-  void handleResize(Size update) {
-    setState(() {
-      size = update;
-    });
-  }
-
-  Widget build(BuildContext context) {
-    Widget overlay = new BuilderTransition(
-        variables: <AnimatedValue<double>>[snapPerformance.variable],
-        performance: snapPerformance.view, builder: (BuildContext context) {
-      double left = snapValue.value.clamp(0.0, 1.0) * position;
-
-      return new Positioned(
-          width: size.width,
-          left: left,
-          child: new TodoItemBody(todo: todo, status: status));
-    });
-
-    List<Widget> children = [
-      // NOTE: This instance of TodoItemBody will never be seen, it is used
-      // entirely for sizing purposes and appears behind the action icons
-      // below. The visible instance is inside of a positioned element on top
-      // of the stack and as a result will lack a hight constraint.
-      new TodoItemBody(todo: todo, onResize: handleResize),
-    ];
-
-    Widget background = new Container(
-        decoration: new BoxDecoration(
-            backgroundColor: Theme.of(context).canvasColor,
-            border: new Border(
-                bottom: new BorderSide(
-                    color: Theme.of(context).dividerColor, width: 1.0))),
-        height: size.height,
-        child: new Row(<Widget>[
-          new Flexible(
-              child: new Container(
-                  child: new Align(
-                      child: new Icon(
-                          icon: "action/check_circle",
-                          color: IconThemeColor.white),
-                      alignment: const FractionalOffset(0.0, 0.5)),
-                  decoration: new BoxDecoration(
-                      backgroundColor: Colors.greenAccent[100]),
-                  padding: const EdgeDims.all(24.0))),
-          new Flexible(
-              child: new Container(
-                  child: new Align(
-                      child: new Icon(
-                          icon: "action/delete", color: IconThemeColor.white),
-                      alignment: const FractionalOffset(1.0, 0.5)),
-                  decoration: new BoxDecoration(
-                      backgroundColor: Colors.redAccent[100]),
-                  padding: const EdgeDims.all(24.0)))
-        ]));
-
-    children.add(background);
-    children.add(overlay);
-
-    return new Listener(
-        onPointerDown: handlePointerDown,
-        behavior: HitTestBehavior.translucent,
-        child: new Stack(children));
-  }
-}
-
-typedef void TodoHandleResize(Size);
-
-class TodoItemBody extends StatelessComponent {
-  TodoItemBody({Key key, this.todo, this.status, this.onResize});
-
-  final TodoModel todo;
-  final TodoHandleResize onResize;
-  final TodoStatus status;
-
-  void maybeCallResize(Size size) {
-    if (onResize != null) {
-      onResize(size);
-    }
-  }
-
-  Widget build(BuildContext context) {
-    Color backgroundColor;
-
-    switch (status) {
-      case TodoStatus.completing:
-        backgroundColor = Colors.greenAccent[100];
-        break;
-      case TodoStatus.deleting:
-        backgroundColor = Colors.redAccent[100];
-        break;
-      default:
-        backgroundColor = Theme.of(context).canvasColor;
-    }
-
-    String message = todo.title;
-
-    if (todo.completed) {
-      message = "COMPLETED: ${todo.title}";
-    }
-
-    return new SizeObserver(
-        onSizeChanged: maybeCallResize,
-        child: new Container(
-            decoration: new BoxDecoration(backgroundColor: backgroundColor),
-            child: new Padding(
-                padding: const EdgeDims.all(24.0),
-                child: new Text(message))));
-  }
-}
diff --git a/examples/todos/lib/home.dart b/examples/todos/lib/home.dart
new file mode 100644
index 0000000..48da1fa
--- /dev/null
+++ b/examples/todos/lib/home.dart
@@ -0,0 +1,74 @@
+// Copyright 2016 The Vanadium 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/material.dart';
+
+import 'task_list_data.dart';
+import 'task_list_dialog.dart';
+import 'task_list_row.dart';
+
+/// [Home] is a [StatefulWidget].
+///
+/// This widget is responsible for holding a
+/// collection of Task Lists and rendering them appropriately. The home screen
+/// is composed of a tool bar, a list view, and the floating action button for
+/// adding a new Lists.
+class Home extends StatefulWidget {
+  final Map<String, TaskListData> lists;
+
+  Home(this.lists);
+
+  @override
+  HomeState createState() => new HomeState();
+}
+
+class HomeState extends State<Home> {
+  @override
+  Widget build(BuildContext context) {
+    // TODO(jxson): Look into using a widget that accepts a builder function
+    // for rendering rows in the scrollable list.
+    //
+    // SEE: https://git.io/vrzFH
+    List<Widget> children = config.lists.keys.map((String uuid) {
+      TaskListData list = config.lists[uuid];
+      return new TaskListRow(list, _delete);
+    }).toList();
+
+    return new Scaffold(
+        appBar: new AppBar(title: new Text('Todo Lists')),
+        // NOTE: Using any kind of scrollable list with a height extent prevents
+        // dismissible height animations from occurring.
+        body: new Block(children: children),
+        floatingActionButton: new FloatingActionButton(
+            tooltip: 'Add new list.',
+            child: new Icon(icon: Icons.add),
+            onPressed: _showNewTaskListDialog));
+  }
+
+  void _showNewTaskListDialog() {
+    TaskListData list = new TaskListData();
+    Widget dialog = new TaskListDialog(list: list);
+
+    // TODO(jxson): Use a callback instead of the return future from
+    // `showDialog`. There is too much indirection with the current method
+    // since the dialog needs to use `Navigator.pop(...)` to get values back
+    // to this widget.
+    //
+    // SEE: https://git.io/vrzFH
+    showDialog(context: context, child: dialog).then(_add);
+  }
+
+  void _add(TaskListData list) {
+    if (list == null) return;
+
+    // Use `setState(fn)` to signal to that some internal state has changed
+    // and the widget needs to be rebuilt. If the state updates are not
+    // wrapped in a `setState` call the change will not be displayed.
+    setState(() => config.lists[list.uuid] = list);
+  }
+
+  void _delete(String uuid) {
+    setState(() => config.lists.remove(uuid));
+  }
+}
diff --git a/examples/todos/lib/main.dart b/examples/todos/lib/main.dart
index 6e40cfa..e9e7eb3 100644
--- a/examples/todos/lib/main.dart
+++ b/examples/todos/lib/main.dart
@@ -1,40 +1,71 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Copyright 2016 The Vanadium 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/material.dart';
 
-import './components/todo_collection.dart';
+import 'home.dart';
+import 'task_list_data.dart';
+import 'task_list_view.dart';
 
-/// Start the Todo application.
+/// Start the application.
 ///
-/// Use the Flutter `runApp` function render this application's widget tree
-/// to the screen.
+/// Create an instance of [App] and use the Flutter [runApp] function render
+/// this application to the screen.
 void main() {
   runApp(new App());
 }
 
-/// Subclass `StatelessComponent` to build the top-level component.
+/// Subclass [StatefulWidget] to build the top-level component.
 ///
-/// The `App` component composes other, lower-level components in it's
-/// `build` function. This top-level component, `App` does not need to hold
-/// any state since that is managed by components lower down in the tree like
-/// `TodoList`. Because this component is stateless, and only manages
-/// composition of the widget tree it can inherit from `StatelessComponent`.
-class App extends StatelessComponent {
+/// The top-level [App] widget manages the composition of the enitre
+/// applications's widget tree based on it's state. [AppState] holds an
+/// in-memory [Map] of Task Lists. It is critical this state is held at the
+/// top level (and not further down the widget tree) to prevent user created
+/// values from disappearing between navigation changes.
+class App extends StatefulWidget {
+  @override
+  AppState createState() => new AppState();
+}
+
+class AppState extends State<StatefulWidget> {
+  final Map<String, TaskListData> lists = <String, TaskListData>{};
   final ThemeData theme = new ThemeData(
       brightness: ThemeBrightness.light,
       primarySwatch: Colors.indigo,
       accentColor: Colors.pinkAccent[200]);
 
-  /// Build a `MaterialApp` widget.
+  /// Build a [MaterialApp] widget.
   ///
-  /// The `MaterialApp` widget allows the inheritance of theme data. For now
-  /// only an empty Todo List is rendered as the single, default route.
+  /// The [MaterialApp] widget allows the inheritance of [ThemeData].
+  @override
   Widget build(BuildContext context) {
     return new MaterialApp(
         title: 'TODO',
         theme: theme,
-        routes: {'/': (RouteArguments args) => new TodoCollection()});
+        routes: <String, WidgetBuilder>{
+          '/': (BuildContext context) => new Home(lists)
+        },
+        onGenerateRoute: handleRoute);
+  }
+
+  // TODO(jxson): look into creating a more ergonomic routing system with
+  // clear route definitions and ways to explicitly handle not found errors.
+  Route<Null> handleRoute(RouteSettings settings) {
+    List<String> path = settings.name.split('/');
+
+    if (!settings.name.startsWith('/')) return null;
+
+    switch (path[1]) {
+      case 'lists':
+        TaskListData list = lists[path[2]];
+        return new MaterialPageRoute<Null>(
+            settings: settings,
+            builder: (BuildContext context) {
+              return new TaskListView(list: list);
+            });
+      default:
+        return null;
+    }
   }
 }
diff --git a/examples/todos/lib/task_data.dart b/examples/todos/lib/task_data.dart
new file mode 100644
index 0000000..89c44b9
--- /dev/null
+++ b/examples/todos/lib/task_data.dart
@@ -0,0 +1,25 @@
+// Copyright 2016 The Vanadium 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/material.dart';
+import 'package:uuid/uuid.dart';
+
+class TaskData {
+  String uuid;
+  String description;
+  bool completed;
+  int createdAt;
+  int updatedAt;
+
+  // TODO(jxson): implement task.<from/to>JSON.
+  TaskData({this.uuid}) {
+    int now = new DateTime.now().millisecondsSinceEpoch;
+    uuid ??= new Uuid().v4();
+    completed = false;
+    createdAt = now;
+    updatedAt = now;
+  }
+
+  Key get key => new ObjectKey(this);
+}
diff --git a/examples/todos/lib/task_dialog.dart b/examples/todos/lib/task_dialog.dart
new file mode 100644
index 0000000..d8c7d94
--- /dev/null
+++ b/examples/todos/lib/task_dialog.dart
@@ -0,0 +1,83 @@
+// Copyright 2016 The Vanadium 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/material.dart';
+import 'dart:async';
+
+import 'task_data.dart';
+
+/// [TaskDialog] handles the creation and editing of individual Tasks.
+class TaskDialog extends StatefulWidget {
+  final TaskData task;
+
+  TaskDialog({Key key, this.task}) : super(key: key) {
+    assert(task != null);
+  }
+
+  @override
+  TaskDialogState createState() => new TaskDialogState();
+}
+
+class TaskDialogState extends State<TaskDialog> {
+  @override
+  void initState() {
+    super.initState();
+
+    // HACK(jxson): Trigger an immediate state change to coerce autofocus on
+    // the [Input].
+    //
+    // TODO(jxson): Add an issue to flutter/flutter about this hack to get the
+    // autofocus to work.
+    new Timer(new Duration(milliseconds: 1), () {
+      setState(() {});
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+
+    Widget save = new FlatButton(
+        child: new Text('SAVE',
+            style: theme.textTheme.body1.copyWith(color: Colors.white)),
+        onPressed: _save);
+
+    // TODO(jxson): Clean up the form & input interactions.
+    //
+    // SEE: https://git.io/vrzFH
+    Widget input = new Input(
+        autofocus: true,
+        hintText: 'What needs to happen?',
+        labelText: 'Task Description',
+        formField:
+            new FormField<String>(setter: _update, validator: _validate));
+
+    return new Scaffold(
+        appBar: new AppBar(
+            leading: new IconButton(icon: Icons.clear, onPressed: _cancel),
+            title: new Text('New List'),
+            actions: <Widget>[save]),
+        body: new Form(
+            onSubmitted: _save, child: new Block(children: <Widget>[input])));
+  }
+
+  void _save() {
+    Navigator.pop(context, config.task);
+  }
+
+  void _cancel() {
+    Navigator.pop(context);
+  }
+
+  String _validate(String value) {
+    if (value.isEmpty)
+      return 'List name is required';
+    else
+      return null;
+  }
+
+  void _update(String value) {
+    config.task.description = value;
+  }
+}
diff --git a/examples/todos/lib/task_list_data.dart b/examples/todos/lib/task_list_data.dart
new file mode 100644
index 0000000..e4f2d9b
--- /dev/null
+++ b/examples/todos/lib/task_list_data.dart
@@ -0,0 +1,25 @@
+// Copyright 2016 The Vanadium 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/material.dart';
+import 'package:uuid/uuid.dart';
+
+import 'task_data.dart';
+
+class TaskListData {
+  String uuid;
+  String name;
+  int createdAt;
+  int updatedAt;
+  final Map<String, TaskData> tasks = <String, TaskData>{};
+
+  TaskListData({this.uuid}) {
+    int now = new DateTime.now().millisecondsSinceEpoch;
+    uuid ??= new Uuid().v4();
+    createdAt = now;
+    updatedAt = now;
+  }
+
+  Key get key => new ObjectKey(this);
+}
diff --git a/examples/todos/lib/task_list_dialog.dart b/examples/todos/lib/task_list_dialog.dart
new file mode 100644
index 0000000..5534f94
--- /dev/null
+++ b/examples/todos/lib/task_list_dialog.dart
@@ -0,0 +1,77 @@
+// Copyright 2016 The Vanadium 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/material.dart';
+import 'dart:async';
+
+import 'task_list_data.dart';
+
+/// [TaskListDialog] handles the creation and editing of Task Lists.
+class TaskListDialog extends StatefulWidget {
+  final TaskListData list;
+
+  TaskListDialog({Key key, this.list}) : super(key: key) {
+    assert(list != null);
+  }
+
+  @override
+  TaskListDialogState createState() => new TaskListDialogState();
+}
+
+class TaskListDialogState extends State<TaskListDialog> {
+  @override
+  void initState() {
+    super.initState();
+
+    // HACK(jxson): Trigger an immediate state change to coerce autofocus on
+    // the [Input].
+    new Timer(new Duration(milliseconds: 1), () {
+      setState(() {});
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+
+    Widget save = new FlatButton(
+        child: new Text('SAVE',
+            style: theme.textTheme.body1.copyWith(color: Colors.white)),
+        onPressed: _save);
+
+    Widget input = new Input(
+        autofocus: true,
+        hintText: 'What should this list be called?',
+        labelText: 'List Name',
+        formField:
+            new FormField<String>(setter: _update, validator: _validate));
+
+    return new Scaffold(
+        appBar: new AppBar(
+            leading: new IconButton(icon: Icons.clear, onPressed: _cancel),
+            title: new Text('New List'),
+            actions: <Widget>[save]),
+        body: new Form(
+            onSubmitted: _save, child: new Block(children: <Widget>[input])));
+  }
+
+  void _save() {
+    Navigator.pop(context, config.list);
+  }
+
+  void _cancel() {
+    Navigator.pop(context);
+  }
+
+  String _validate(String value) {
+    if (value.isEmpty)
+      return 'List name is required';
+    else
+      return null;
+  }
+
+  void _update(String value) {
+    config.list.name = value;
+  }
+}
diff --git a/examples/todos/lib/task_list_row.dart b/examples/todos/lib/task_list_row.dart
new file mode 100644
index 0000000..9f5385f
--- /dev/null
+++ b/examples/todos/lib/task_list_row.dart
@@ -0,0 +1,67 @@
+// Copyright 2016 The Vanadium 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/material.dart';
+
+import 'task_list_data.dart';
+
+typedef void TaskListRowHandleDelete(String uuid);
+
+/// [TaskListRow] renders individual rows of Task Lists.
+class TaskListRow extends StatefulWidget {
+  final TaskListData list;
+  final TaskListRowHandleDelete onDelete;
+
+  TaskListRow(this.list, this.onDelete);
+
+  @override
+  TaskListRowState createState() => new TaskListRowState();
+}
+
+class TaskListRowState extends State<TaskListRow> {
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+
+    // TODO(jxson): create a flutter issue about the ink effect going away
+    // when using Dismissable.
+    return new Dismissable(
+        key: new ObjectKey(config.list),
+        direction: DismissDirection.endToStart,
+        onDismissed: _handleDismiss,
+        background: new Container(
+            decoration: new BoxDecoration(
+                backgroundColor: theme.canvasColor,
+                border: new Border(
+                    bottom: new BorderSide(color: theme.dividerColor))),
+            child: new ListItem(trailing: new Icon(icon: Icons.delete))),
+        child: new Container(
+            decoration: new BoxDecoration(
+                backgroundColor: theme.cardColor,
+                boxShadow: kElevationToShadow[2],
+                border: new Border(
+                    bottom: new BorderSide(color: theme.dividerColor))),
+            child: new ListItem(
+                isThreeLine: true,
+                title: new Text(config.list.name),
+                subtitle: new Text(config.list.uuid),
+                onTap: _open)));
+  }
+
+  void _open() {
+    Navigator.pushNamed(context, '/lists/${config.list.uuid}');
+  }
+
+  void _handleDismiss(DismissDirection direction) {
+    switch (direction) {
+      // Swipe from right to left to delete.
+      case DismissDirection.endToStart:
+        config.onDelete(config.list.uuid);
+        break;
+      default:
+        throw "Not implemented.";
+        break;
+    }
+  }
+}
diff --git a/examples/todos/lib/task_list_view.dart b/examples/todos/lib/task_list_view.dart
new file mode 100644
index 0000000..97fd161
--- /dev/null
+++ b/examples/todos/lib/task_list_view.dart
@@ -0,0 +1,65 @@
+// Copyright 2016 The Vanadium 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/material.dart';
+
+import 'task_data.dart';
+import 'task_row.dart';
+import 'task_list_data.dart';
+import 'task_dialog.dart';
+
+/// [TaskListView] renders a page showing all the Tasks associated to a given
+/// `list`.
+class TaskListView extends StatefulWidget {
+  final TaskListData list;
+
+  TaskListView({Key key, this.list}) : super(key: key) {
+    assert(list != null);
+  }
+
+  @override
+  TaskListViewState createState() => new TaskListViewState();
+}
+
+class TaskListViewState extends State<TaskListView> {
+  @override
+  Widget build(BuildContext context) {
+    List<Widget> children = config.list.tasks.keys.map((String uuid) {
+      TaskData task = config.list.tasks[uuid];
+      return new TaskRow(task: task, onDelete: _delete, onComplete: _complete);
+    }).toList();
+
+    return new Scaffold(
+        appBar: new AppBar(title: new Text('List: ${config.list.name}')),
+        body: new Block(children: children),
+        floatingActionButton: new FloatingActionButton(
+            onPressed: _showNewTaskDialog,
+            tooltip: 'Add new list.',
+            child: new Icon(icon: Icons.add)));
+  }
+
+  void _showNewTaskDialog() {
+    TaskData task = new TaskData();
+    Widget dialog = new TaskDialog(task: task);
+
+    showDialog(context: context, child: dialog).then(_add);
+  }
+
+  void _add(TaskData task) {
+    if (task == null) return;
+
+    setState(() => config.list.tasks[task.uuid] = task);
+  }
+
+  void _delete(String uuid) {
+    setState(() => config.list.tasks.remove(uuid));
+  }
+
+  void _complete(String uuid) {
+    setState(() {
+      TaskData task = config.list.tasks[uuid];
+      task.completed = true;
+    });
+  }
+}
diff --git a/examples/todos/lib/task_row.dart b/examples/todos/lib/task_row.dart
new file mode 100644
index 0000000..5721237
--- /dev/null
+++ b/examples/todos/lib/task_row.dart
@@ -0,0 +1,84 @@
+// Copyright 2016 The Vanadium 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/material.dart';
+
+import 'task_data.dart';
+
+typedef void TaskRowHandleDelete(String uuid);
+typedef void TaskRowHandleComplete(String uuid);
+
+/// [TaskRow] renders individual rows of Tasks .
+class TaskRow extends StatefulWidget {
+  final TaskData task;
+  final TaskRowHandleDelete onDelete;
+  final TaskRowHandleComplete onComplete;
+
+  TaskRow({Key key, this.task, this.onDelete, this.onComplete})
+      : super(key: key) {
+    assert(task != null);
+  }
+
+  @override
+  TaskRowState createState() => new TaskRowState();
+}
+
+class TaskRowState extends State<TaskRow> {
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final Text description = config.task.completed
+        ? new Text(config.task.description,
+            style: new TextStyle(
+                color: theme.disabledColor,
+                decoration: TextDecoration.lineThrough))
+        : new Text(config.task.description);
+
+    // TODO(jxson): create a flutter issue about the ripple effect going away
+    // when using Dismissable.
+    return new Dismissable(
+        // NOTE: Key needs to change based on completion.
+        key: new ObjectKey("${config.task.uuid}-${config.task.completed}"),
+        direction: DismissDirection.horizontal,
+        onDismissed: _handleDismiss,
+        background: new Container(
+            decoration: new BoxDecoration(
+                backgroundColor: theme.canvasColor,
+                border: new Border(
+                    bottom: new BorderSide(color: theme.dividerColor))),
+            child: new ListItem(leading: new Icon(icon: Icons.check))),
+        secondaryBackground: new Container(
+            decoration: new BoxDecoration(
+                backgroundColor: theme.canvasColor,
+                border: new Border(
+                    bottom: new BorderSide(color: theme.dividerColor))),
+            child: new ListItem(trailing: new Icon(icon: Icons.delete))),
+        child: new Container(
+            decoration: new BoxDecoration(
+                backgroundColor: theme.cardColor,
+                boxShadow: kElevationToShadow[2],
+                border: new Border(
+                    bottom: new BorderSide(color: theme.dividerColor))),
+            child: new ListItem(
+                isThreeLine: true,
+                title: description,
+                subtitle: new Text(config.task.uuid))));
+  }
+
+  void _handleDismiss(DismissDirection direction) {
+    switch (direction) {
+      // Swipe from right to left to delete.
+      case DismissDirection.endToStart:
+        config.onDelete(config.task.uuid);
+        break;
+      // Swipe from left to right to complete.
+      case DismissDirection.startToEnd:
+        config.onComplete(config.task.uuid);
+        break;
+      default:
+        throw "Not implemented.";
+        break;
+    }
+  }
+}
diff --git a/examples/todos/lib/todo_model.dart b/examples/todos/lib/todo_model.dart
deleted file mode 100644
index 37aecb4..0000000
--- a/examples/todos/lib/todo_model.dart
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2015 The Vanadium 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/material.dart';
-import 'package:uuid/uuid.dart';
-
-class TodoModel {
-  TodoModel({this.id, this.title, this.completed: false}) {
-    id ??= new Uuid().v4();
-  }
-
-  String id;
-  String title;
-  bool completed;
-  Key get key => new ObjectKey(this);
-}
diff --git a/examples/todos/pubspec.yaml b/examples/todos/pubspec.yaml
index 855c158..0fe0f08 100644
--- a/examples/todos/pubspec.yaml
+++ b/examples/todos/pubspec.yaml
@@ -1,6 +1,12 @@
 name: todos
-description: A minimal Flutter project.
+description: A Baku example project.
 dependencies:
   uuid: 0.5.0
   flutter:
-    path: ../../../../../../flutter/packages/flutter
+    path:  ../../../../../../flutter/packages/flutter
+dev_dependencies:
+  test: any
+  flutter_test:
+    path: ../../../../../../flutter/packages/flutter_test
+  flutter_driver:
+    path: ../../../../../../flutter/packages/flutter_driver