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);
});
}