Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
d2b66dbf
Unverified
Commit
d2b66dbf
authored
Jan 31, 2020
by
Jonah Williams
Committed by
GitHub
Jan 31, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Revert "Refactors global key duplication detection (#46183)" (#49847)
This reverts commit
340b193f
.
parent
340b193f
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
84 additions
and
756 deletions
+84
-756
action_sheet.dart
packages/flutter/lib/src/cupertino/action_sheet.dart
+0
-1
dialog.dart
packages/flutter/lib/src/cupertino/dialog.dart
+0
-1
chip.dart
packages/flutter/lib/src/material/chip.dart
+0
-1
input_decorator.dart
packages/flutter/lib/src/material/input_decorator.dart
+0
-1
list_tile.dart
packages/flutter/lib/src/material/list_tile.dart
+0
-1
binding.dart
packages/flutter/lib/src/widgets/binding.dart
+0
-1
framework.dart
packages/flutter/lib/src/widgets/framework.dart
+72
-161
layout_builder.dart
packages/flutter/lib/src/widgets/layout_builder.dart
+0
-1
list_wheel_scroll_view.dart
packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart
+0
-1
sliver.dart
packages/flutter/lib/src/widgets/sliver.dart
+0
-1
sliver_persistent_header.dart
...ges/flutter/lib/src/widgets/sliver_persistent_header.dart
+0
-1
table.dart
packages/flutter/lib/src/widgets/table.dart
+0
-1
diagnostics_json_test.dart
packages/flutter/test/foundation/diagnostics_json_test.dart
+5
-0
framework_test.dart
packages/flutter/test/widgets/framework_test.dart
+7
-584
No files found.
packages/flutter/lib/src/cupertino/action_sheet.dart
View file @
d2b66dbf
...
...
@@ -469,7 +469,6 @@ class _CupertinoAlertRenderElement extends RenderObjectElement {
}
else
if
(
_actionsElement
==
child
)
{
_actionsElement
=
null
;
}
super
.
forgetChild
(
child
);
}
@override
...
...
packages/flutter/lib/src/cupertino/dialog.dart
View file @
d2b66dbf
...
...
@@ -464,7 +464,6 @@ class _CupertinoDialogRenderElement extends RenderObjectElement {
assert
(
_actionsElement
==
child
);
_actionsElement
=
null
;
}
super
.
forgetChild
(
child
);
}
@override
...
...
packages/flutter/lib/src/material/chip.dart
View file @
d2b66dbf
...
...
@@ -2072,7 +2072,6 @@ class _RenderChipElement extends RenderObjectElement {
final
_ChipSlot
slot
=
childToSlot
[
child
];
childToSlot
.
remove
(
child
);
slotToChild
.
remove
(
slot
);
super
.
forgetChild
(
child
);
}
void
_mountChild
(
Widget
widget
,
_ChipSlot
slot
)
{
...
...
packages/flutter/lib/src/material/input_decorator.dart
View file @
d2b66dbf
...
...
@@ -1511,7 +1511,6 @@ class _RenderDecorationElement extends RenderObjectElement {
final
_DecorationSlot
slot
=
childToSlot
[
child
];
childToSlot
.
remove
(
child
);
slotToChild
.
remove
(
slot
);
super
.
forgetChild
(
child
);
}
void
_mountChild
(
Widget
widget
,
_DecorationSlot
slot
)
{
...
...
packages/flutter/lib/src/material/list_tile.dart
View file @
d2b66dbf
...
...
@@ -992,7 +992,6 @@ class _ListTileElement extends RenderObjectElement {
final
_ListTileSlot
slot
=
childToSlot
[
child
];
childToSlot
.
remove
(
child
);
slotToChild
.
remove
(
slot
);
super
.
forgetChild
(
child
);
}
void
_mountChild
(
Widget
widget
,
_ListTileSlot
slot
)
{
...
...
packages/flutter/lib/src/widgets/binding.dart
View file @
d2b66dbf
...
...
@@ -1016,7 +1016,6 @@ class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObje
void
forgetChild
(
Element
child
)
{
assert
(
child
==
_child
);
_child
=
null
;
super
.
forgetChild
(
child
);
}
@override
...
...
packages/flutter/lib/src/widgets/framework.dart
View file @
d2b66dbf
...
...
@@ -132,20 +132,7 @@ abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
static
final
Map
<
GlobalKey
,
Element
>
_registry
=
<
GlobalKey
,
Element
>{};
static
final
Set
<
Element
>
_debugIllFatedElements
=
HashSet
<
Element
>();
// This map keeps track which child reserve the global key with the parent.
// Parent, child -> global key.
// This provides us a way to remove old reservation while parent rebuilds the
// child in the same slot.
static
final
Map
<
Element
,
Map
<
Element
,
GlobalKey
>>
_debugReservations
=
<
Element
,
Map
<
Element
,
GlobalKey
>>{};
static
void
_debugRemoveReservationFor
(
Element
parent
,
Element
child
)
{
assert
(()
{
assert
(
parent
!=
null
);
assert
(
child
!=
null
);
_debugReservations
[
parent
]?.
remove
(
child
);
return
true
;
}());
}
static
final
Map
<
GlobalKey
,
Element
>
_debugReservations
=
<
GlobalKey
,
Element
>{};
void
_register
(
Element
element
)
{
assert
(()
{
...
...
@@ -173,83 +160,46 @@ abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
_registry
.
remove
(
this
);
}
void
_debugReserveFor
(
Element
parent
,
Element
child
)
{
void
_debugReserveFor
(
Element
parent
)
{
assert
(()
{
assert
(
parent
!=
null
);
assert
(
child
!=
null
);
_debugReservations
[
parent
]
??=
<
Element
,
GlobalKey
>{};
_debugReservations
[
parent
][
child
]
=
this
;
return
true
;
}());
}
static
void
_debugVerifyGlobalKeyReservation
()
{
assert
(()
{
final
Map
<
GlobalKey
,
Element
>
keyToParent
=
<
GlobalKey
,
Element
>{};
_debugReservations
.
forEach
((
Element
parent
,
Map
<
Element
,
GlobalKey
>
chidToKey
)
{
// We ignore parent that are detached.
if
(
parent
.
renderObject
?.
attached
==
false
)
return
;
chidToKey
.
forEach
((
Element
child
,
GlobalKey
key
)
{
// If parent = null, the node is deactivated by its parent and is
// not re-attached to other part of the tree. We should ignore this
// node.
if
(
child
.
_parent
==
null
)
return
;
// It is possible the same key registers to the same parent twice
// with different children. That is illegal, but it is not in the
// scope of this check. Such error will be detected in
// _debugVerifyIllFatedPopulation or
// _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans.
if
(
keyToParent
.
containsKey
(
key
)
&&
keyToParent
[
key
]
!=
parent
)
{
// We have duplication reservations for the same global key.
final
Element
older
=
keyToParent
[
key
];
final
Element
newer
=
parent
;
FlutterError
error
;
if
(
older
.
toString
()
!=
newer
.
toString
())
{
error
=
FlutterError
.
fromParts
(<
DiagnosticsNode
>[
ErrorSummary
(
'Multiple widgets used the same GlobalKey.'
),
ErrorDescription
(
'The key
$key
was used by multiple widgets. The parents of those widgets were:
\n
'
'-
${older.toString()}
\n
'
'-
${newer.toString()}
\n
'
'A GlobalKey can only be specified on one widget at a time in the widget tree.'
),
]);
}
else
{
error
=
FlutterError
.
fromParts
(<
DiagnosticsNode
>[
ErrorSummary
(
'Multiple widgets used the same GlobalKey.'
),
ErrorDescription
(
'The key
$key
was used by multiple widgets. The parents of those widgets were '
'different widgets that both had the following description:
\n
'
'
${parent.toString()}
\n
'
'A GlobalKey can only be specified on one widget at a time in the widget tree.'
),
]);
}
// Fix the tree by removing the duplicated child from one of its
// parents to resolve the duplicated key issue. This allows us to
// tear down the tree during testing without producing additional
// misleading exceptions.
if
(
child
.
_parent
!=
older
)
{
older
.
visitChildren
((
Element
currentChild
)
{
if
(
currentChild
==
child
)
older
.
forgetChild
(
child
);
});
}
if
(
child
.
_parent
!=
newer
)
{
newer
.
visitChildren
((
Element
currentChild
)
{
if
(
currentChild
==
child
)
newer
.
forgetChild
(
child
);
});
}
throw
error
;
}
else
{
keyToParent
[
key
]
=
parent
;
}
});
});
_debugReservations
.
clear
();
if
(
_debugReservations
.
containsKey
(
this
)
&&
_debugReservations
[
this
]
!=
parent
)
{
// Reserving a new parent while the old parent is not attached is ok.
// This can happen when a renderObject detaches and re-attaches to rendering
// tree multiple times.
if
(
_debugReservations
[
this
].
renderObject
?.
attached
==
false
)
{
_debugReservations
[
this
]
=
parent
;
return
true
;
}
// It's possible for an element to get built multiple times in one
// frame, in which case it'll reserve the same child's key multiple
// times. We catch multiple children of one widget having the same key
// by verifying that an element never steals elements from itself, so we
// don't care to verify that here as well.
final
String
older
=
_debugReservations
[
this
].
toString
();
final
String
newer
=
parent
.
toString
();
if
(
older
!=
newer
)
{
throw
FlutterError
.
fromParts
(<
DiagnosticsNode
>[
ErrorSummary
(
'Multiple widgets used the same GlobalKey.'
),
ErrorDescription
(
'The key
$this
was used by multiple widgets. The parents of those widgets were:
\n
'
'-
$older
\n
'
'-
$newer
\n
'
'A GlobalKey can only be specified on one widget at a time in the widget tree.'
),
]);
}
throw
FlutterError
.
fromParts
(<
DiagnosticsNode
>[
ErrorSummary
(
'Multiple widgets used the same GlobalKey.'
),
ErrorDescription
(
'The key
$this
was used by multiple widgets. The parents of those widgets were '
'different widgets that both had the following description:
\n
'
'
$parent
\n
'
'A GlobalKey can only be specified on one widget at a time in the widget tree.'
),
]);
}
_debugReservations
[
this
]
=
parent
;
return
true
;
}());
}
...
...
@@ -265,13 +215,13 @@ abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
final
GlobalKey
key
=
element
.
widget
.
key
as
GlobalKey
;
assert
(
_registry
.
containsKey
(
key
));
duplicates
??=
<
GlobalKey
,
Set
<
Element
>>{};
// Uses ordered set to produce consistent error message.
final
Set
<
Element
>
elements
=
duplicates
.
putIfAbsent
(
key
,
()
=>
LinkedHashSet
<
Element
>());
final
Set
<
Element
>
elements
=
duplicates
.
putIfAbsent
(
key
,
()
=>
HashSet
<
Element
>());
elements
.
add
(
element
);
elements
.
add
(
_registry
[
key
]);
}
}
_debugIllFatedElements
.
clear
();
_debugReservations
.
clear
();
if
(
duplicates
!=
null
)
{
final
List
<
DiagnosticsNode
>
information
=
<
DiagnosticsNode
>[];
information
.
add
(
ErrorSummary
(
'Multiple widgets used the same GlobalKey.'
));
...
...
@@ -2689,7 +2639,6 @@ class BuildOwner {
});
assert
(()
{
try
{
GlobalKey
.
_debugVerifyGlobalKeyReservation
();
GlobalKey
.
_debugVerifyIllFatedPopulation
();
if
(
_debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans
!=
null
&&
_debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans
.
isNotEmpty
)
{
...
...
@@ -3144,12 +3093,18 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
/// | **child != null** | Old child is removed, returns null. | Old child updated if possible, returns child or new [Element]. |
@protected
Element
updateChild
(
Element
child
,
Widget
newWidget
,
dynamic
newSlot
)
{
assert
(()
{
final
Key
key
=
newWidget
?.
key
;
if
(
key
is
GlobalKey
)
{
key
.
_debugReserveFor
(
this
);
}
return
true
;
}());
if
(
newWidget
==
null
)
{
if
(
child
!=
null
)
deactivateChild
(
child
);
return
null
;
}
Element
newChild
;
if
(
child
!=
null
)
{
bool
hasSameSuperclass
=
true
;
// When the type of a widget is changed between Stateful and Stateless via
...
...
@@ -3173,40 +3128,28 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
hasSameSuperclass
=
oldElementClass
==
newWidgetClass
;
return
true
;
}());
if
(
child
.
widget
==
newWidget
&&
hasSameSuperclass
)
{
if
(
child
.
slot
!=
newSlot
)
updateSlotForChild
(
child
,
newSlot
);
newChild
=
child
;
}
else
if
(
Widget
.
canUpdate
(
child
.
widget
,
newWidget
)
&&
hasSameSuperclass
)
{
if
(
child
.
slot
!=
newSlot
)
updateSlotForChild
(
child
,
newSlot
);
child
.
update
(
newWidget
);
assert
(
child
.
widget
==
newWidget
);
assert
(()
{
child
.
owner
.
_debugElementWasRebuilt
(
child
);
return
true
;
}());
newChild
=
child
;
}
else
{
deactivateChild
(
child
);
assert
(
child
.
_parent
==
null
);
newChild
=
inflateWidget
(
newWidget
,
newSlot
);
if
(
hasSameSuperclass
)
{
if
(
child
.
widget
==
newWidget
)
{
if
(
child
.
slot
!=
newSlot
)
updateSlotForChild
(
child
,
newSlot
);
return
child
;
}
if
(
Widget
.
canUpdate
(
child
.
widget
,
newWidget
))
{
if
(
child
.
slot
!=
newSlot
)
updateSlotForChild
(
child
,
newSlot
);
child
.
update
(
newWidget
);
assert
(
child
.
widget
==
newWidget
);
assert
(()
{
child
.
owner
.
_debugElementWasRebuilt
(
child
);
return
true
;
}());
return
child
;
}
}
}
else
{
newChild
=
inflateWidget
(
newWidget
,
newSlot
);
deactivateChild
(
child
);
assert
(
child
.
_parent
==
null
);
}
assert
(()
{
if
(
child
!=
null
)
_debugRemoveGlobalKeyReservation
(
child
);
final
Key
key
=
newWidget
?.
key
;
if
(
key
is
GlobalKey
)
{
key
.
_debugReserveFor
(
this
,
newChild
);
}
return
true
;
}());
return
newChild
;
return
inflateWidget
(
newWidget
,
newSlot
);
}
/// Add this element to the tree in the given slot of the given parent.
...
...
@@ -3244,9 +3187,6 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
}());
}
void
_debugRemoveGlobalKeyReservation
(
Element
child
)
{
GlobalKey
.
_debugRemoveReservationFor
(
this
,
child
);
}
/// Change the widget used to configure this element.
///
/// The framework calls this function when the parent wishes to use a
...
...
@@ -3265,16 +3205,6 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
&&
depth
!=
null
&&
_active
&&
Widget
.
canUpdate
(
widget
,
newWidget
));
// This Element was told to update and we can now release all the global key
// reservations of forgotten children. We cannot do this earlier because the
// forgotten children still represent global key duplications if the element
// never updates (the forgotten children are not removed from the tree
// until the call to update happens)
assert
(()
{
_debugForgottenChildrenWithGlobalKey
.
forEach
(
_debugRemoveGlobalKeyReservation
);
_debugForgottenChildrenWithGlobalKey
.
clear
();
return
true
;
}());
_widget
=
newWidget
;
}
...
...
@@ -3471,10 +3401,6 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
}());
}
// The children that have been forgotten by forgetChild. This will be used in
// [update] to remove the global key reservations of forgotten children.
final
Set
<
Element
>
_debugForgottenChildrenWithGlobalKey
=
HashSet
<
Element
>();
/// Remove the given child from the element's child list, in preparation for
/// the child being reused elsewhere in the element tree.
///
...
...
@@ -3483,23 +3409,12 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
///
/// The element will still have a valid parent when this is called. After this
/// is called, [deactivateChild] is called to sever the link to this object.
///
/// The [update] is responsible for updating or creating the new child that
/// will replace this [child].
@protected
@mustCallSuper
void
forgetChild
(
Element
child
)
{
// This method is called on the old parent when the given child (with a
// global key) is given a new parent. We cannot remove the global key
// reservation directly in this method because the forgotten child is not
// removed from the tree until this Element is updated in [update]. If
// [update] is never called, the forgotten child still represents a global
// key duplication that we need to catch.
assert
(()
{
if
(
child
.
widget
.
key
is
GlobalKey
)
_debugForgottenChildrenWithGlobalKey
.
add
(
child
);
return
true
;
}());
// TODO(chunhtai): Creates empty body for subclass to call super. This will
// enable us to fix internal tests pro-actively for upcoming breaking
// change.
// https://github.com/flutter/flutter/issues/43780.
}
void
_activateWithParent
(
Element
parent
,
dynamic
newSlot
)
{
...
...
@@ -4529,7 +4444,6 @@ abstract class ComponentElement extends Element {
void
forgetChild
(
Element
child
)
{
assert
(
child
==
_child
);
_child
=
null
;
super
.
forgetChild
(
child
);
}
}
...
...
@@ -5683,7 +5597,6 @@ class LeafRenderObjectElement extends RenderObjectElement {
@override
void
forgetChild
(
Element
child
)
{
assert
(
false
);
super
.
forgetChild
(
child
);
}
@override
...
...
@@ -5733,7 +5646,6 @@ class SingleChildRenderObjectElement extends RenderObjectElement {
void
forgetChild
(
Element
child
)
{
assert
(
child
==
_child
);
_child
=
null
;
super
.
forgetChild
(
child
);
}
@override
...
...
@@ -5840,7 +5752,6 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
assert
(
_children
.
contains
(
child
));
assert
(!
_forgottenChildren
.
contains
(
child
));
_forgottenChildren
.
add
(
child
);
super
.
forgetChild
(
child
);
}
@override
...
...
packages/flutter/lib/src/widgets/layout_builder.dart
View file @
d2b66dbf
...
...
@@ -60,7 +60,6 @@ class _LayoutBuilderElement<ConstraintType extends Constraints> extends RenderOb
void
forgetChild
(
Element
child
)
{
assert
(
child
==
_child
);
_child
=
null
;
super
.
forgetChild
(
child
);
}
@override
...
...
packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart
View file @
d2b66dbf
...
...
@@ -924,7 +924,6 @@ class ListWheelElement extends RenderObjectElement implements ListWheelChildMana
@override
void
forgetChild
(
Element
child
)
{
_childElements
.
remove
(
child
.
slot
);
super
.
forgetChild
(
child
);
}
}
...
...
packages/flutter/lib/src/widgets/sliver.dart
View file @
d2b66dbf
...
...
@@ -1160,7 +1160,6 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
assert
(
child
.
slot
!=
null
);
assert
(
_childElements
.
containsKey
(
child
.
slot
));
_childElements
.
remove
(
child
.
slot
);
super
.
forgetChild
(
child
);
}
@override
...
...
packages/flutter/lib/src/widgets/sliver_persistent_header.dart
View file @
d2b66dbf
...
...
@@ -230,7 +230,6 @@ class _SliverPersistentHeaderElement extends RenderObjectElement {
void
forgetChild
(
Element
child
)
{
assert
(
child
==
this
.
child
);
this
.
child
=
null
;
super
.
forgetChild
(
child
);
}
@override
...
...
packages/flutter/lib/src/widgets/table.dart
View file @
d2b66dbf
...
...
@@ -344,7 +344,6 @@ class _TableElement extends RenderObjectElement {
@override
bool
forgetChild
(
Element
child
)
{
_forgottenChildren
.
add
(
child
);
super
.
forgetChild
(
child
);
return
true
;
}
}
...
...
packages/flutter/test/foundation/diagnostics_json_test.dart
View file @
d2b66dbf
...
...
@@ -221,6 +221,11 @@ void main() {
class
_TestElement
extends
Element
{
_TestElement
()
:
super
(
const
Placeholder
());
@override
void
forgetChild
(
Element
child
)
{
// Intentionally left empty.
}
@override
void
performRebuild
()
{
// Intentionally left empty.
...
...
packages/flutter/test/widgets/framework_test.dart
View file @
d2b66dbf
...
...
@@ -6,8 +6,6 @@ import 'package:flutter_test/flutter_test.dart';
import
'package:flutter/foundation.dart'
;
import
'package:flutter/widgets.dart'
;
typedef
ElementRebuildCallback
=
void
Function
(
StatefulElement
element
);
class
TestState
extends
State
<
StatefulWidget
>
{
@override
Widget
build
(
BuildContext
context
)
=>
null
;
...
...
@@ -63,358 +61,6 @@ void main() {
expect
(
keyA
,
isNot
(
equals
(
keyB
)));
});
testWidgets
(
'GlobalKey correct case 1 - can move global key from container widget to layoutbuilder'
,
(
WidgetTester
tester
)
async
{
final
Key
key
=
GlobalKey
(
debugLabel:
'correct'
);
await
tester
.
pumpWidget
(
Stack
(
textDirection:
TextDirection
.
ltr
,
children:
<
Widget
>[
Container
(
key:
const
ValueKey
<
int
>(
1
),
child:
SizedBox
(
key:
key
),
),
LayoutBuilder
(
key:
const
ValueKey
<
int
>(
2
),
builder:
(
BuildContext
context
,
BoxConstraints
constraints
)
{
return
const
Placeholder
();
},
),
],
));
await
tester
.
pumpWidget
(
Stack
(
textDirection:
TextDirection
.
ltr
,
children:
<
Widget
>[
Container
(
key:
const
ValueKey
<
int
>(
1
),
child:
const
Placeholder
(),
),
LayoutBuilder
(
key:
const
ValueKey
<
int
>(
2
),
builder:
(
BuildContext
context
,
BoxConstraints
constraints
)
{
return
SizedBox
(
key:
key
);
},
),
],
));
});
testWidgets
(
'GlobalKey correct case 2 - can move global key from layoutbuilder to container widget'
,
(
WidgetTester
tester
)
async
{
final
Key
key
=
GlobalKey
(
debugLabel:
'correct'
);
await
tester
.
pumpWidget
(
Stack
(
textDirection:
TextDirection
.
ltr
,
children:
<
Widget
>[
Container
(
key:
const
ValueKey
<
int
>(
1
),
child:
const
Placeholder
(),
),
LayoutBuilder
(
key:
const
ValueKey
<
int
>(
2
),
builder:
(
BuildContext
context
,
BoxConstraints
constraints
)
{
return
SizedBox
(
key:
key
);
},
),
],
));
await
tester
.
pumpWidget
(
Stack
(
textDirection:
TextDirection
.
ltr
,
children:
<
Widget
>[
Container
(
key:
const
ValueKey
<
int
>(
1
),
child:
SizedBox
(
key:
key
),
),
LayoutBuilder
(
key:
const
ValueKey
<
int
>(
2
),
builder:
(
BuildContext
context
,
BoxConstraints
constraints
)
{
return
const
Placeholder
();
},
),
],
));
});
testWidgets
(
'GlobalKey correct case 3 - can deal with early rebuild in layoutbuilder - move backward'
,
(
WidgetTester
tester
)
async
{
const
Key
key1
=
GlobalObjectKey
(
'Text1'
);
const
Key
key2
=
GlobalObjectKey
(
'Text2'
);
Key
rebuiltKeyOfSecondChildBeforeLayout
;
Key
rebuiltKeyOfFirstChildAfterLayout
;
Key
rebuiltKeyOfSecondChildAfterLayout
;
await
tester
.
pumpWidget
(
LayoutBuilder
(
builder:
(
BuildContext
context
,
BoxConstraints
constraints
)
{
return
Column
(
children:
<
Widget
>[
const
_Stateful
(
child:
Text
(
'Text1'
,
textDirection:
TextDirection
.
ltr
,
key:
key1
,
),
),
_Stateful
(
child:
const
Text
(
'Text2'
,
textDirection:
TextDirection
.
ltr
,
key:
key2
,
),
onElementRebuild:
(
StatefulElement
element
)
{
// We don't want noise to override the result;
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
isNull
);
final
_Stateful
statefulWidget
=
element
.
widget
as
_Stateful
;
rebuiltKeyOfSecondChildBeforeLayout
=
statefulWidget
.
child
.
key
;
},
),
],
);
},
)
);
// Result will be written during first build and need to clear it to remove
// noise.
rebuiltKeyOfSecondChildBeforeLayout
=
null
;
final
_StatefulState
state
=
tester
.
firstState
(
find
.
byType
(
_Stateful
).
at
(
1
));
state
.
rebuild
();
// Reorders the items
await
tester
.
pumpWidget
(
LayoutBuilder
(
builder:
(
BuildContext
context
,
BoxConstraints
constraints
)
{
return
Column
(
children:
<
Widget
>[
_Stateful
(
child:
const
Text
(
'Text2'
,
textDirection:
TextDirection
.
ltr
,
key:
key2
,
),
onElementRebuild:
(
StatefulElement
element
)
{
// Verifies the early rebuild happens before layout.
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
key2
);
// We don't want noise to override the result;
expect
(
rebuiltKeyOfFirstChildAfterLayout
,
isNull
);
final
_Stateful
statefulWidget
=
element
.
widget
as
_Stateful
;
rebuiltKeyOfFirstChildAfterLayout
=
statefulWidget
.
child
.
key
;
},
),
_Stateful
(
child:
const
Text
(
'Text1'
,
textDirection:
TextDirection
.
ltr
,
key:
key1
,
),
onElementRebuild:
(
StatefulElement
element
)
{
// Verifies the early rebuild happens before layout.
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
key2
);
// We don't want noise to override the result;
expect
(
rebuiltKeyOfSecondChildAfterLayout
,
isNull
);
final
_Stateful
statefulWidget
=
element
.
widget
as
_Stateful
;
rebuiltKeyOfSecondChildAfterLayout
=
statefulWidget
.
child
.
key
;
},
),
],
);
},
)
);
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
key2
);
expect
(
rebuiltKeyOfFirstChildAfterLayout
,
key2
);
expect
(
rebuiltKeyOfSecondChildAfterLayout
,
key1
);
});
testWidgets
(
'GlobalKey correct case 4 - can deal with early rebuild in layoutbuilder - move forward'
,
(
WidgetTester
tester
)
async
{
const
Key
key1
=
GlobalObjectKey
(
'Text1'
);
const
Key
key2
=
GlobalObjectKey
(
'Text2'
);
const
Key
key3
=
GlobalObjectKey
(
'Text3'
);
Key
rebuiltKeyOfSecondChildBeforeLayout
;
Key
rebuiltKeyOfSecondChildAfterLayout
;
Key
rebuiltKeyOfThirdChildAfterLayout
;
await
tester
.
pumpWidget
(
LayoutBuilder
(
builder:
(
BuildContext
context
,
BoxConstraints
constraints
)
{
return
Column
(
children:
<
Widget
>[
const
_Stateful
(
child:
Text
(
'Text1'
,
textDirection:
TextDirection
.
ltr
,
key:
key1
,
),
),
_Stateful
(
child:
const
Text
(
'Text2'
,
textDirection:
TextDirection
.
ltr
,
key:
key2
,
),
onElementRebuild:
(
StatefulElement
element
)
{
// We don't want noise to override the result;
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
isNull
);
final
_Stateful
statefulWidget
=
element
.
widget
as
_Stateful
;
rebuiltKeyOfSecondChildBeforeLayout
=
statefulWidget
.
child
.
key
;
},
),
const
_Stateful
(
child:
Text
(
'Text3'
,
textDirection:
TextDirection
.
ltr
,
key:
key3
,
),
),
],
);
},
)
);
// Result will be written during first build and need to clear it to remove
// noise.
rebuiltKeyOfSecondChildBeforeLayout
=
null
;
final
_StatefulState
state
=
tester
.
firstState
(
find
.
byType
(
_Stateful
).
at
(
1
));
state
.
rebuild
();
// Reorders the items
await
tester
.
pumpWidget
(
LayoutBuilder
(
builder:
(
BuildContext
context
,
BoxConstraints
constraints
)
{
return
Column
(
children:
<
Widget
>[
const
_Stateful
(
child:
Text
(
'Text1'
,
textDirection:
TextDirection
.
ltr
,
key:
key1
,
),
),
_Stateful
(
child:
const
Text
(
'Text3'
,
textDirection:
TextDirection
.
ltr
,
key:
key3
,
),
onElementRebuild:
(
StatefulElement
element
)
{
// Verifies the early rebuild happens before layout.
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
key2
);
// We don't want noise to override the result;
expect
(
rebuiltKeyOfSecondChildAfterLayout
,
isNull
);
final
_Stateful
statefulWidget
=
element
.
widget
as
_Stateful
;
rebuiltKeyOfSecondChildAfterLayout
=
statefulWidget
.
child
.
key
;
},
),
_Stateful
(
child:
const
Text
(
'Text2'
,
textDirection:
TextDirection
.
ltr
,
key:
key2
,
),
onElementRebuild:
(
StatefulElement
element
)
{
// Verifies the early rebuild happens before layout.
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
key2
);
// We don't want noise to override the result;
expect
(
rebuiltKeyOfThirdChildAfterLayout
,
isNull
);
final
_Stateful
statefulWidget
=
element
.
widget
as
_Stateful
;
rebuiltKeyOfThirdChildAfterLayout
=
statefulWidget
.
child
.
key
;
},
),
],
);
},
)
);
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
key2
);
expect
(
rebuiltKeyOfSecondChildAfterLayout
,
key3
);
expect
(
rebuiltKeyOfThirdChildAfterLayout
,
key2
);
});
testWidgets
(
'GlobalKey correct case 5 - can deal with early rebuild in layoutbuilder - only one global key'
,
(
WidgetTester
tester
)
async
{
const
Key
key1
=
GlobalObjectKey
(
'Text1'
);
Key
rebuiltKeyOfSecondChildBeforeLayout
;
Key
rebuiltKeyOfThirdChildAfterLayout
;
await
tester
.
pumpWidget
(
LayoutBuilder
(
builder:
(
BuildContext
context
,
BoxConstraints
constraints
)
{
return
Column
(
children:
<
Widget
>[
const
_Stateful
(
child:
Text
(
'Text1'
,
textDirection:
TextDirection
.
ltr
,
),
),
_Stateful
(
child:
const
Text
(
'Text2'
,
textDirection:
TextDirection
.
ltr
,
key:
key1
,
),
onElementRebuild:
(
StatefulElement
element
)
{
// We don't want noise to override the result;
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
isNull
);
final
_Stateful
statefulWidget
=
element
.
widget
as
_Stateful
;
rebuiltKeyOfSecondChildBeforeLayout
=
statefulWidget
.
child
.
key
;
},
),
const
_Stateful
(
child:
Text
(
'Text3'
,
textDirection:
TextDirection
.
ltr
,
),
),
],
);
},
)
);
// Result will be written during first build and need to clear it to remove
// noise.
rebuiltKeyOfSecondChildBeforeLayout
=
null
;
final
_StatefulState
state
=
tester
.
firstState
(
find
.
byType
(
_Stateful
).
at
(
1
));
state
.
rebuild
();
// Reorders the items
await
tester
.
pumpWidget
(
LayoutBuilder
(
builder:
(
BuildContext
context
,
BoxConstraints
constraints
)
{
return
Column
(
children:
<
Widget
>[
const
_Stateful
(
child:
Text
(
'Text1'
,
textDirection:
TextDirection
.
ltr
,
),
),
_Stateful
(
child:
const
Text
(
'Text3'
,
textDirection:
TextDirection
.
ltr
,
),
onElementRebuild:
(
StatefulElement
element
)
{
// Verifies the early rebuild happens before layout.
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
key1
);
},
),
_Stateful
(
child:
const
Text
(
'Text2'
,
textDirection:
TextDirection
.
ltr
,
key:
key1
,
),
onElementRebuild:
(
StatefulElement
element
)
{
// Verifies the early rebuild happens before layout.
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
key1
);
// We don't want noise to override the result;
expect
(
rebuiltKeyOfThirdChildAfterLayout
,
isNull
);
final
_Stateful
statefulWidget
=
element
.
widget
as
_Stateful
;
rebuiltKeyOfThirdChildAfterLayout
=
statefulWidget
.
child
.
key
;
},
),
],
);
},
)
);
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
key1
);
expect
(
rebuiltKeyOfThirdChildAfterLayout
,
key1
);
});
testWidgets
(
'GlobalKey duplication 1 - double appearance'
,
(
WidgetTester
tester
)
async
{
final
Key
key
=
GlobalKey
(
debugLabel:
'problematic'
);
await
tester
.
pumpWidget
(
Stack
(
...
...
@@ -855,173 +501,7 @@ void main() {
],
));
FlutterError
.
onError
=
oldHandler
;
expect
(
count
,
1
);
});
testWidgets
(
'GlobalKey duplication 18 - subtree build duplicate key with same type'
,
(
WidgetTester
tester
)
async
{
final
Key
key
=
GlobalKey
(
debugLabel:
'problematic'
);
final
Stack
stack
=
Stack
(
textDirection:
TextDirection
.
ltr
,
children:
<
Widget
>[
const
SwapKeyWidget
(
childKey:
ValueKey
<
int
>(
0
)),
Container
(
key:
const
ValueKey
<
int
>(
1
)),
Container
(
key:
key
),
],
);
await
tester
.
pumpWidget
(
stack
);
final
SwapKeyWidgetState
state
=
tester
.
state
(
find
.
byType
(
SwapKeyWidget
));
state
.
swapKey
(
key
);
await
tester
.
pump
();
final
dynamic
exception
=
tester
.
takeException
();
expect
(
exception
,
isFlutterError
);
expect
(
exception
.
toString
(),
equalsIgnoringHashCodes
(
'Duplicate GlobalKey detected in widget tree.
\n
'
'The following GlobalKey was specified multiple times in the widget tree. This will lead '
'to parts of the widget tree being truncated unexpectedly, because the second time a key is seen, the '
'previous instance is moved to the new location. The key was:
\n
'
'- [GlobalKey#00000 problematic]
\n
'
'This was determined by noticing that after the widget with the above global key was '
'moved out of its previous parent, that previous parent never updated during this frame, meaning that '
'it either did not update at all or updated before the widget was moved, in either case implying that '
'it still thinks that it should have a child with that global key.
\n
'
'The specific parent that did not update after having one or more children forcibly '
'removed due to GlobalKey reparenting is:
\n
'
'- Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose, '
'overflow: clip, renderObject: RenderStack#00000)
\n
'
'A GlobalKey can only be specified on one widget at a time in the widget tree.'
),
);
});
testWidgets
(
'GlobalKey duplication 19 - subtree build duplicate key with different types'
,
(
WidgetTester
tester
)
async
{
final
Key
key
=
GlobalKey
(
debugLabel:
'problematic'
);
final
Stack
stack
=
Stack
(
textDirection:
TextDirection
.
ltr
,
children:
<
Widget
>[
const
SwapKeyWidget
(
childKey:
ValueKey
<
int
>(
0
)),
Container
(
key:
const
ValueKey
<
int
>(
1
)),
Container
(
child:
SizedBox
(
key:
key
)),
],
);
await
tester
.
pumpWidget
(
stack
);
final
SwapKeyWidgetState
state
=
tester
.
state
(
find
.
byType
(
SwapKeyWidget
));
state
.
swapKey
(
key
);
await
tester
.
pump
();
final
dynamic
exception
=
tester
.
takeException
();
expect
(
exception
,
isFlutterError
);
expect
(
exception
.
toString
(),
equalsIgnoringHashCodes
(
'Multiple widgets used the same GlobalKey.
\n
'
'The key [GlobalKey#95367 problematic] was used by 2 widgets:
\n
'
' SizedBox-[GlobalKey#00000 problematic]
\n
'
' Container-[GlobalKey#00000 problematic]
\n
'
'A GlobalKey can only be specified on one widget at a time in the widget tree.'
),
);
});
testWidgets
(
'GlobalKey duplication 20 - real duplication with early rebuild in layoutbuilder will throw'
,
(
WidgetTester
tester
)
async
{
const
Key
key1
=
GlobalObjectKey
(
'Text1'
);
const
Key
key2
=
GlobalObjectKey
(
'Text2'
);
Key
rebuiltKeyOfSecondChildBeforeLayout
;
Key
rebuiltKeyOfFirstChildAfterLayout
;
Key
rebuiltKeyOfSecondChildAfterLayout
;
await
tester
.
pumpWidget
(
LayoutBuilder
(
builder:
(
BuildContext
context
,
BoxConstraints
constraints
)
{
return
Column
(
children:
<
Widget
>[
const
_Stateful
(
child:
Text
(
'Text1'
,
textDirection:
TextDirection
.
ltr
,
key:
key1
,
),
),
_Stateful
(
child:
const
Text
(
'Text2'
,
textDirection:
TextDirection
.
ltr
,
key:
key2
,
),
onElementRebuild:
(
StatefulElement
element
)
{
// We don't want noise to override the result;
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
isNull
);
final
_Stateful
statefulWidget
=
element
.
widget
as
_Stateful
;
rebuiltKeyOfSecondChildBeforeLayout
=
statefulWidget
.
child
.
key
;
},
),
],
);
},
)
);
// Result will be written during first build and need to clear it to remove
// noise.
rebuiltKeyOfSecondChildBeforeLayout
=
null
;
final
_StatefulState
state
=
tester
.
firstState
(
find
.
byType
(
_Stateful
).
at
(
1
));
state
.
rebuild
();
await
tester
.
pumpWidget
(
LayoutBuilder
(
builder:
(
BuildContext
context
,
BoxConstraints
constraints
)
{
return
Column
(
children:
<
Widget
>[
_Stateful
(
child:
const
Text
(
'Text2'
,
textDirection:
TextDirection
.
ltr
,
key:
key2
,
),
onElementRebuild:
(
StatefulElement
element
)
{
// Verifies the early rebuild happens before layout.
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
key2
);
// We don't want noise to override the result;
expect
(
rebuiltKeyOfFirstChildAfterLayout
,
isNull
);
final
_Stateful
statefulWidget
=
element
.
widget
as
_Stateful
;
rebuiltKeyOfFirstChildAfterLayout
=
statefulWidget
.
child
.
key
;
},
),
_Stateful
(
child:
const
Text
(
'Text1'
,
textDirection:
TextDirection
.
ltr
,
key:
key2
,
),
onElementRebuild:
(
StatefulElement
element
)
{
// Verifies the early rebuild happens before layout.
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
key2
);
// We don't want noise to override the result;
expect
(
rebuiltKeyOfSecondChildAfterLayout
,
isNull
);
final
_Stateful
statefulWidget
=
element
.
widget
as
_Stateful
;
rebuiltKeyOfSecondChildAfterLayout
=
statefulWidget
.
child
.
key
;
},
),
],
);
},
)
);
expect
(
rebuiltKeyOfSecondChildBeforeLayout
,
key2
);
expect
(
rebuiltKeyOfFirstChildAfterLayout
,
key2
);
expect
(
rebuiltKeyOfSecondChildAfterLayout
,
key2
);
final
dynamic
exception
=
tester
.
takeException
();
expect
(
exception
,
isFlutterError
);
expect
(
exception
.
toString
(),
equalsIgnoringHashCodes
(
'Multiple widgets used the same GlobalKey.
\n
'
'The key [GlobalObjectKey String#00000] was used by multiple widgets. The '
'parents of those widgets were:
\n
'
'- _Stateful(state: _StatefulState#00000)
\n
'
'- _Stateful(state: _StatefulState#00000)
\n
'
'A GlobalKey can only be specified on one widget at a time in the widget tree.'
),
);
expect
(
count
,
2
);
});
testWidgets
(
'GlobalKey - dettach and re-attach child to different parents'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -1278,6 +758,9 @@ class NullChildElement extends Element {
visitor
(
null
);
}
@override
void
forgetChild
(
Element
child
)
{
}
@override
void
performRebuild
()
{
}
}
...
...
@@ -1289,6 +772,9 @@ class DirtyElementWithCustomBuildOwner extends Element {
final
BuildOwner
_owner
;
@override
void
forgetChild
(
Element
child
)
{}
@override
void
performRebuild
()
{}
...
...
@@ -1337,66 +823,3 @@ class DependentState extends State<DependentStatefulWidget> {
deactivatedCount
+=
1
;
}
}
class
SwapKeyWidget
extends
StatefulWidget
{
const
SwapKeyWidget
({
this
.
childKey
}):
super
();
final
Key
childKey
;
@override
SwapKeyWidgetState
createState
()
=>
SwapKeyWidgetState
();
}
class
SwapKeyWidgetState
extends
State
<
SwapKeyWidget
>
{
Key
key
;
@override
void
initState
()
{
super
.
initState
();
key
=
widget
.
childKey
;
}
void
swapKey
(
Key
newKey
)
{
setState
(()
{
key
=
newKey
;
});
}
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
key:
key
);
}
}
class
_Stateful
extends
StatefulWidget
{
const
_Stateful
({
Key
key
,
this
.
child
,
this
.
onElementRebuild
})
:
super
(
key:
key
);
final
Text
child
;
final
ElementRebuildCallback
onElementRebuild
;
@override
State
<
StatefulWidget
>
createState
()
=>
_StatefulState
();
@override
StatefulElement
createElement
()
=>
StatefulElementSpy
(
this
);
}
class
_StatefulState
extends
State
<
_Stateful
>
{
void
rebuild
()
=>
setState
(()
{});
@override
Widget
build
(
BuildContext
context
)
{
return
widget
.
child
;
}
}
class
StatefulElementSpy
extends
StatefulElement
{
StatefulElementSpy
(
StatefulWidget
widget
)
:
super
(
widget
);
_Stateful
get
_statefulWidget
=>
widget
as
_Stateful
;
@override
void
rebuild
()
{
if
(
_statefulWidget
.
onElementRebuild
!=
null
)
{
_statefulWidget
.
onElementRebuild
(
this
);
}
super
.
rebuild
();
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment