Merge pull request #950 from Hixie/dropdown-asserts

Aggressively try to catch misuse of DropDownButton
diff --git a/examples/material_gallery/flutter.yaml b/examples/material_gallery/flutter.yaml
index bfe9151..c4fb1dc 100644
--- a/examples/material_gallery/flutter.yaml
+++ b/examples/material_gallery/flutter.yaml
@@ -9,3 +9,4 @@
   - name: action/alarm
   - name: action/face
   - name: action/language
+  - name: content/add
diff --git a/examples/material_gallery/lib/demo/modal_bottom_sheet_demo.dart b/examples/material_gallery/lib/demo/modal_bottom_sheet_demo.dart
new file mode 100644
index 0000000..11a43d2
--- /dev/null
+++ b/examples/material_gallery/lib/demo/modal_bottom_sheet_demo.dart
@@ -0,0 +1,45 @@
+// Copyright 2015 The Chromium 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 'widget_demo.dart';
+
+class ModalBottomSheetDemo extends StatelessComponent {
+  final TextStyle textStyle = new TextStyle(
+    color: Colors.indigo[400],
+    fontSize: 24.0,
+    textAlign: TextAlign.center
+  );
+
+  void _showModalBottomSheet(BuildContext context) {
+    showModalBottomSheet(context: context, builder: (_) {
+      return new Container(
+        child: new Padding(
+          padding: const EdgeDims.all(32.0),
+          child: new Text("This is the modal bottom sheet. Click anywhere to dismiss.", style: textStyle)
+        )
+      );
+    });
+  }
+
+  Widget build(BuildContext context) {
+    return new Center(
+      child: new Container(
+        width: 200.0,
+        height: 200.0,
+        child: new RaisedButton(
+          onPressed: () { _showModalBottomSheet(context); },
+          child: new Text('Show the modal bottom sheet', style: textStyle)
+        )
+      )
+    );
+  }
+}
+
+final WidgetDemo kModalBottomSheetDemo = new WidgetDemo(
+  title: 'Modal Bottom Sheet',
+  routeName: '/modalBottomSheet',
+  builder: (_) => new ModalBottomSheetDemo()
+);
diff --git a/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart b/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart
new file mode 100644
index 0000000..bc023fe
--- /dev/null
+++ b/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart
@@ -0,0 +1,55 @@
+// Copyright 2015 The Chromium 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 'widget_demo.dart';
+
+class PersistentBottomSheetDemo extends StatelessComponent {
+
+  final TextStyle textStyle = new TextStyle(
+    color: Colors.indigo[400],
+    fontSize: 24.0,
+    textAlign: TextAlign.center
+  );
+
+  void _showBottomSheet(BuildContext context) {
+    Scaffold.of(context).showBottomSheet((_) {
+      return new Container(
+        decoration: new BoxDecoration(
+          border: new Border(top: new BorderSide(color: Colors.black26, width: 1.0))
+        ),
+        child: new Padding(
+          padding: const EdgeDims.all(32.0),
+          child: new Text("This is a Material persistent bottom sheet. Drag downwards to dismiss it.", style: textStyle)
+        )
+      );
+    });
+  }
+
+  Widget build(BuildContext context) {
+    return new Center(
+      child: new Container(
+        width: 200.0,
+        height: 200.0,
+        child: new RaisedButton(
+          onPressed: () { _showBottomSheet(context); },
+          child: new Text('Show the persistent bottom sheet', style: textStyle)
+        )
+      )
+    );
+  }
+}
+
+final WidgetDemo kPersistentBottomSheetDemo = new WidgetDemo(
+  title: 'Persistent Bottom Sheet',
+  routeName: '/persistentBottomSheet',
+  builder: (_) => new PersistentBottomSheetDemo(),
+  floatingActionButtonBuilder: (_) {
+    return new FloatingActionButton(
+      child: new Icon(icon: 'content/add'),
+      backgroundColor: Colors.redAccent[200]
+    );
+  }
+);
diff --git a/examples/material_gallery/lib/demo/widget_demo.dart b/examples/material_gallery/lib/demo/widget_demo.dart
index 7d4daf4..d22c677 100644
--- a/examples/material_gallery/lib/demo/widget_demo.dart
+++ b/examples/material_gallery/lib/demo/widget_demo.dart
@@ -5,10 +5,11 @@
 import 'package:flutter/material.dart';
 
 class WidgetDemo {
-  WidgetDemo({ this.title, this.routeName, this.tabBarBuilder, this.builder });
+  WidgetDemo({ this.title, this.routeName, this.tabBarBuilder, this.floatingActionButtonBuilder, this.builder });
 
   final String title;
   final String routeName;
   final WidgetBuilder tabBarBuilder;
+  final WidgetBuilder floatingActionButtonBuilder;
   final WidgetBuilder builder;
 }
diff --git a/examples/material_gallery/lib/gallery_page.dart b/examples/material_gallery/lib/gallery_page.dart
index 0d56461..6c4a611 100644
--- a/examples/material_gallery/lib/gallery_page.dart
+++ b/examples/material_gallery/lib/gallery_page.dart
@@ -49,6 +49,11 @@
     return builder != null ? builder(context) : null;
   }
 
+  Widget _buildFloatingActionButton() {
+    final WidgetBuilder builder = config.active?.floatingActionButtonBuilder;
+    return builder != null ? builder(context) : null;
+  }
+
   Widget build(BuildContext context) {
     return new Scaffold(
       toolBar: new ToolBar(
@@ -56,6 +61,7 @@
         tabBar: _buildTabBar()
       ),
       drawer: _buildDrawer(),
+      floatingActionButton: _buildFloatingActionButton(),
       body: _buildBody()
     );
   }
diff --git a/examples/material_gallery/lib/main.dart b/examples/material_gallery/lib/main.dart
index e80c5ce..8abd802 100644
--- a/examples/material_gallery/lib/main.dart
+++ b/examples/material_gallery/lib/main.dart
@@ -7,6 +7,8 @@
 import 'demo/chip_demo.dart';
 import 'demo/date_picker_demo.dart';
 import 'demo/drop_down_demo.dart';
+import 'demo/modal_bottom_sheet_demo.dart';
+import 'demo/persistent_bottom_sheet_demo.dart';
 import 'demo/selection_controls_demo.dart';
 import 'demo/slider_demo.dart';
 import 'demo/tabs_demo.dart';
@@ -22,6 +24,8 @@
   kTabsDemo,
   kTimePickerDemo,
   kDropDownDemo,
+  kModalBottomSheetDemo,
+  kPersistentBottomSheetDemo,
 ];
 
 class _MaterialGallery extends StatefulComponent {
diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart
index 00acbe6..3e344bd 100644
--- a/packages/flutter/lib/src/material/dropdown.dart
+++ b/packages/flutter/lib/src/material/dropdown.dart
@@ -95,7 +95,10 @@
             padding: _kMenuHorizontalPadding,
             child: route.items[itemIndex]
           ),
-          onTap: () => Navigator.pop(context, route.items[itemIndex].value)
+          onTap: () => Navigator.pop(
+            context,
+            new _DropDownRouteResult<T>(route.items[itemIndex].value)
+          )
         )
       ));
     }
@@ -143,9 +146,24 @@
   }
 }
 
-class _DropDownRoute<T> extends PopupRoute<T> {
+// We box the return value so that the return value can be null. Otherwise,
+// canceling the route (which returns null) would get confused with actually
+// returning a real null value.
+class _DropDownRouteResult<T> {
+  const _DropDownRouteResult(this.result);
+  final T result;
+  bool operator ==(dynamic other) {
+    if (other is! _DropDownRouteResult)
+      return false;
+    final _DropDownRouteResult<T> typedOther = other;
+    return result == typedOther.result;
+  }
+  int get hashCode => result.hashCode;
+}
+
+class _DropDownRoute<T> extends PopupRoute<_DropDownRouteResult<T>> {
   _DropDownRoute({
-    Completer<T> completer,
+    Completer<_DropDownRouteResult<T>> completer,
     this.items,
     this.selectedIndex,
     this.rect,
@@ -249,7 +267,7 @@
   void _handleTap() {
     final RenderBox renderBox = indexedStackKey.currentContext.findRenderObject();
     final Rect rect = renderBox.localToGlobal(Point.origin) & renderBox.size;
-    final Completer completer = new Completer<T>();
+    final Completer completer = new Completer<_DropDownRouteResult<T>>();
     Navigator.push(context, new _DropDownRoute<T>(
       completer: completer,
       items: config.items,
@@ -257,11 +275,11 @@
       rect: _kMenuHorizontalPadding.inflateRect(rect),
       elevation: config.elevation
     ));
-    completer.future.then((T newValue) {
-      if (!mounted)
+    completer.future.then((_DropDownRouteResult<T> newValue) {
+      if (!mounted || newValue == null)
         return;
       if (config.onChanged != null)
-        config.onChanged(newValue);
+        config.onChanged(newValue.result);
     });
   }