From 3ab799bbc934e5f6d032557cdcb1486ce6b93c4b Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Thu, 6 May 2021 19:49:02 -0700 Subject: [PATCH] Assert when duplicated keys are introduced in subsequent build (#81850) --- .../flutter/lib/src/widgets/framework.dart | 1 + .../flutter/test/widgets/framework_test.dart | 16 +++--- .../multichildobject_with_keys_test.dart | 51 +++++++++++++++++++ 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 518229ba5f..3b67a66c7f 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -6304,6 +6304,7 @@ class MultiChildRenderObjectElement extends RenderObjectElement { void update(MultiChildRenderObjectWidget newWidget) { super.update(newWidget); assert(widget == newWidget); + assert(!debugChildrenHaveDuplicateKeys(widget, widget.children)); _children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren); _forgottenChildren.clear(); } diff --git a/packages/flutter/test/widgets/framework_test.dart b/packages/flutter/test/widgets/framework_test.dart index 739f9cad5d..b9199d6bdf 100644 --- a/packages/flutter/test/widgets/framework_test.dart +++ b/packages/flutter/test/widgets/framework_test.dart @@ -512,11 +512,9 @@ void main() { expect( exception.toString(), equalsIgnoringHashCodes( - 'Multiple widgets used the same GlobalKey.\n' - 'The key [GlobalKey#00000 problematic] was used by 2 widgets:\n' - ' SizedBox-[GlobalKey#00000 problematic]\n' - ' Placeholder-[GlobalKey#00000 problematic]\n' - 'A GlobalKey can only be specified on one widget at a time in the widget tree.', + 'Duplicate keys found.\n' + 'If multiple keyed nodes exist as children of another node, they must have unique keys.\n' + 'Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose) has multiple children with key [GlobalKey#00000 problematic].' ), ); }); @@ -541,11 +539,9 @@ void main() { expect( exception.toString(), equalsIgnoringHashCodes( - 'Multiple widgets used the same GlobalKey.\n' - 'The key [GlobalKey#00000 problematic] was used by 2 widgets:\n' - ' Container-[GlobalKey#00000 problematic]\n' - ' Placeholder-[GlobalKey#00000 problematic]\n' - 'A GlobalKey can only be specified on one widget at a time in the widget tree.', + 'Duplicate keys found.\n' + 'If multiple keyed nodes exist as children of another node, they must have unique keys.\n' + 'Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose) has multiple children with key [GlobalKey#00000 problematic].' ), ); }); diff --git a/packages/flutter/test/widgets/multichildobject_with_keys_test.dart b/packages/flutter/test/widgets/multichildobject_with_keys_test.dart index 7d6562c482..2a45d05049 100644 --- a/packages/flutter/test/widgets/multichildobject_with_keys_test.dart +++ b/packages/flutter/test/widgets/multichildobject_with_keys_test.dart @@ -58,6 +58,57 @@ void main() { ['0', '6', '7', '8', '1', '2', '3', '4', '5'], ); }); + + testWidgets('Building a new MultiChildRenderObjectElement with children having duplicated keys throws', (WidgetTester tester) async { + const ValueKey duplicatedKey = ValueKey(1); + + await tester.pumpWidget(Column( + children: const [ + Text('Text 1', textDirection: TextDirection.ltr, key: duplicatedKey), + Text('Text 2', textDirection: TextDirection.ltr, key: duplicatedKey), + ], + )); + + expect( + tester.takeException(), + isA().having( + (FlutterError error) => error.message, + 'error.message', + startsWith('Duplicate keys found.'), + ), + ); + }); + + testWidgets('Updating a MultiChildRenderObjectElement to have children with duplicated keys throws', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/81541 + + const ValueKey key1 = ValueKey(1); + const ValueKey key2 = ValueKey(2); + + Future _buildWithKey(Key key) { + return tester.pumpWidget(Column( + children: [ + const Text('Text 1', textDirection: TextDirection.ltr, key: key1), + Text('Text 2', textDirection: TextDirection.ltr, key: key), + ], + )); + } + + // Initial build with two different keys. + await _buildWithKey(key2); + expect(tester.takeException(), isNull); + + // Subsequent build with duplicated keys. + await _buildWithKey(key1); + expect( + tester.takeException(), + isA().having( + (FlutterError error) => error.message, + 'error.message', + startsWith('Duplicate keys found.'), + ), + ); + }); } // Do not use tester.renderObjectList(find.byType(RenderParagraph). That returns