diff --git a/packages/flutter/lib/src/material/two_level_list.dart b/packages/flutter/lib/src/material/two_level_list.dart index e8946c4e1e..90301fd27e 100644 --- a/packages/flutter/lib/src/material/two_level_list.dart +++ b/packages/flutter/lib/src/material/two_level_list.dart @@ -154,11 +154,14 @@ class _TwoLevelSublistState extends State { } class TwoLevelList extends StatelessWidget { - TwoLevelList({ Key key, this.items, this.type: MaterialListType.twoLine }) : super(key: key); + TwoLevelList({ Key key, this.scrollableKey, this.items, this.type: MaterialListType.twoLine }) : super(key: key); final List items; final MaterialListType type; + final Key scrollableKey; @override - Widget build(BuildContext context) => new Block(children: items); + Widget build(BuildContext context) { + return new Block(children: KeyedSubtree.ensureUniqueKeysForList(items), scrollableKey: scrollableKey); + } } diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 0ed152dd03..3e128b4465 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -7,6 +7,7 @@ import 'dart:ui' as ui show Image; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; +import 'debug.dart'; import 'framework.dart'; export 'package:flutter/animation.dart'; @@ -2575,6 +2576,27 @@ class KeyedSubtree extends StatelessWidget { /// The widget below this widget in the tree. final Widget child; + /// Wrap each item in a KeyedSubtree whose key is based on the item's existing key or + /// its list index + baseIndex. + static List ensureUniqueKeysForList(Iterable items, { int baseIndex: 0 }) { + if (items == null || items.isEmpty) + return items; + + List itemsWithUniqueKeys = []; + int itemIndex = baseIndex; + for(Widget item in items) { + itemsWithUniqueKeys.add(new KeyedSubtree( + key: item.key != null ? new ValueKey(item.key) : new ValueKey(itemIndex), + child: item + )); + itemIndex += 1; + } + + assert(!debugItemsHaveDuplicateKeys(itemsWithUniqueKeys)); + return items; + } + + @override Widget build(BuildContext context) => child; } diff --git a/packages/flutter/lib/src/widgets/debug.dart b/packages/flutter/lib/src/widgets/debug.dart index ad15fc676c..ac290adc90 100644 --- a/packages/flutter/lib/src/widgets/debug.dart +++ b/packages/flutter/lib/src/widgets/debug.dart @@ -25,22 +25,39 @@ bool debugCheckHasMediaQuery(BuildContext context) { return true; } -bool debugHasDuplicateKeys(Widget parent, Iterable children) { +Key _firstNonUniqueKey(Iterable widgets) { + Set keySet = new HashSet(); + for (Widget widget in widgets) { + assert(widget != null); + if (widget.key == null) + continue; + if (!keySet.add(widget.key)) + return widget.key; + } + return null; +} + +bool debugChildrenHaveDuplicateKeys(Widget parent, Iterable children) { assert(() { - Set keySet = new HashSet(); - for (Widget child in children) { - assert(child != null); - if (child.key == null) - continue; - if (!keySet.add(child.key)) { - throw new FlutterError( - 'Duplicate keys found.\n' - 'If multiple keyed nodes exist as children of another node, they must have unique keys.\n' - '$parent has multiple children with key "${child.key}".' - ); - } + final Key nonUniqueKey = _firstNonUniqueKey(children); + if (nonUniqueKey != null) { + throw new FlutterError( + 'Duplicate keys found.\n' + 'If multiple keyed nodes exist as children of another node, they must have unique keys.\n' + '$parent has multiple children with key $nonUniqueKey.' + ); } return true; }); return false; } + +bool debugItemsHaveDuplicateKeys(Iterable items) { + assert(() { + final Key nonUniqueKey = _firstNonUniqueKey(items); + if (nonUniqueKey != null) + throw new FlutterError('Duplicate key found: $nonUniqueKey.\n'); + return true; + }); + return false; +} diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index ddaf381be6..c071af2a46 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -1937,7 +1937,7 @@ class SingleChildRenderObjectElement extends RenderObjectElement { /// Instantiation of RenderObjectWidgets that can have a list of children class MultiChildRenderObjectElement extends RenderObjectElement { MultiChildRenderObjectElement(MultiChildRenderObjectWidget widget) : super(widget) { - assert(!debugHasDuplicateKeys(widget, widget.children)); + assert(!debugChildrenHaveDuplicateKeys(widget, widget.children)); } @override diff --git a/packages/flutter/lib/src/widgets/virtual_viewport.dart b/packages/flutter/lib/src/widgets/virtual_viewport.dart index 4fce429e56..ee64cc4dcf 100644 --- a/packages/flutter/lib/src/widgets/virtual_viewport.dart +++ b/packages/flutter/lib/src/widgets/virtual_viewport.dart @@ -173,8 +173,9 @@ abstract class VirtualViewportElement extends RenderObjectElement { Key key = child.key != null ? new ValueKey(child.key) : new ValueKey(childIndex); newWidgets[i] = new RepaintBoundary(key: key, child: child); } - assert(!debugHasDuplicateKeys(widget, newWidgets)); - _materializedChildren = updateChildren(_materializedChildren, newWidgets); + + assert(!debugChildrenHaveDuplicateKeys(widget, newWidgets)); + _materializedChildren = updateChildren(_materializedChildren, newWidgets.toList()); } @override