Canceling a dropdown menu selects null value

Previously we didn't distinguish between canceling the menu (which
popped the route with no return value, i.e. null) and explicitly
selecting an item whose value is null (pop with value null). Both fired
onChange(null).

Now we box the return value so we can distinguish the two cases.

I would have preferred to just disallow "null" as a value but it turns
out people like being able to use "null" as a value for much the same
reason we do, and it seems best for us to pay the complexity cost of
boxing than having everyone else do it. :-)
This commit is contained in:
Hixie 2015-12-15 16:52:58 -08:00
parent 09ab80502f
commit 0c6e3416d8

View File

@ -95,7 +95,10 @@ class _DropDownMenu<T> extends StatusTransitionComponent {
padding: _kMenuHorizontalPadding, padding: _kMenuHorizontalPadding,
child: route.items[itemIndex] 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 _DropDownMenu<T> extends StatusTransitionComponent {
} }
} }
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({ _DropDownRoute({
Completer<T> completer, Completer<_DropDownRouteResult<T>> completer,
this.items, this.items,
this.selectedIndex, this.selectedIndex,
this.rect, this.rect,
@ -246,7 +264,7 @@ class _DropDownButtonState<T> extends State<DropDownButton<T>> {
void _handleTap() { void _handleTap() {
final RenderBox renderBox = indexedStackKey.currentContext.findRenderObject(); final RenderBox renderBox = indexedStackKey.currentContext.findRenderObject();
final Rect rect = renderBox.localToGlobal(Point.origin) & renderBox.size; 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>( Navigator.push(context, new _DropDownRoute<T>(
completer: completer, completer: completer,
items: config.items, items: config.items,
@ -254,11 +272,11 @@ class _DropDownButtonState<T> extends State<DropDownButton<T>> {
rect: _kMenuHorizontalPadding.inflateRect(rect), rect: _kMenuHorizontalPadding.inflateRect(rect),
elevation: config.elevation elevation: config.elevation
)); ));
completer.future.then((T newValue) { completer.future.then((_DropDownRouteResult<T> newValue) {
if (!mounted) if (!mounted || newValue == null)
return; return;
if (config.onChanged != null) if (config.onChanged != null)
config.onChanged(newValue); config.onChanged(newValue.result);
}); });
} }