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
97eaee34
Commit
97eaee34
authored
Oct 02, 2015
by
Adam Barth
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1447 from abarth/reactivate
GlobalKeys should preserve state across tree mutations
parents
dd6790fc
f31f067e
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
258 additions
and
41 deletions
+258
-41
framework.dart
packages/flutter/lib/src/widgets/framework.dart
+136
-41
reparent_state_test.dart
packages/unit/test/widget/reparent_state_test.dart
+122
-0
No files found.
packages/flutter/lib/src/widgets/framework.dart
View file @
97eaee34
...
...
@@ -93,10 +93,11 @@ abstract class GlobalKey extends Key {
}
}
BuildContext
get
currentContext
=>
_registry
[
this
];
Widget
get
currentWidget
=>
_registry
[
this
]?.
widget
;
Element
get
_currentElement
=>
_registry
[
this
];
BuildContext
get
currentContext
=>
_currentElement
;
Widget
get
currentWidget
=>
_currentElement
?.
widget
;
State
get
currentState
{
Element
element
=
_
registry
[
this
]
;
Element
element
=
_
currentElement
;
if
(
element
is
StatefulComponentElement
)
return
element
.
state
;
return
null
;
...
...
@@ -416,26 +417,26 @@ enum _ElementLifecycle {
defunct
,
}
class
_
Fre
eElements
{
class
_
Inactiv
eElements
{
bool
_locked
=
false
;
final
Set
<
Element
>
_elements
=
new
Set
<
Element
>();
void
_
finalize
(
Element
element
)
{
void
_
unmount
(
Element
element
)
{
assert
(
element
.
_debugLifecycleState
==
_ElementLifecycle
.
inactive
);
element
.
unmount
();
assert
(
element
.
_debugLifecycleState
==
_ElementLifecycle
.
defunct
);
element
.
visitChildren
((
Element
child
)
{
assert
(
child
.
_parent
==
element
);
_
finalize
(
child
);
_
unmount
(
child
);
});
}
void
finalize
All
()
{
void
unmount
All
()
{
BuildableElement
.
lockState
(()
{
try
{
_locked
=
true
;
for
(
Element
element
in
_elements
)
_
finalize
(
element
);
_
unmount
(
element
);
}
finally
{
_elements
.
clear
();
_locked
=
false
;
...
...
@@ -443,22 +444,40 @@ class _FreeElements {
});
}
void
_deactivate
(
Element
element
)
{
assert
(
element
.
_debugLifecycleState
==
_ElementLifecycle
.
active
);
element
.
deactivate
();
assert
(
element
.
_debugLifecycleState
==
_ElementLifecycle
.
inactive
);
element
.
visitChildren
(
_deactivate
);
}
void
add
(
Element
element
)
{
assert
(!
_locked
);
assert
(!
_elements
.
contains
(
element
));
assert
(
element
.
_parent
==
null
);
void
deactivate
(
Element
element
)
{
assert
(
element
.
_debugLifecycleState
==
_ElementLifecycle
.
active
);
element
.
deactivate
();
assert
(
element
.
_debugLifecycleState
==
_ElementLifecycle
.
inactive
);
element
.
visitChildren
(
deactivate
);
}
deactivate
(
element
);
if
(
element
.
_active
)
_deactivate
(
element
);
_elements
.
add
(
element
);
}
void
_reactivate
(
Element
element
)
{
assert
(
element
.
_debugLifecycleState
==
_ElementLifecycle
.
inactive
);
element
.
reactivate
();
assert
(
element
.
_debugLifecycleState
==
_ElementLifecycle
.
active
);
element
.
visitChildren
(
_reactivate
);
}
void
remove
(
Element
element
)
{
assert
(!
_locked
);
assert
(
_elements
.
contains
(
element
));
assert
(
element
.
_parent
==
null
);
_elements
.
remove
(
element
);
assert
(!
element
.
_active
);
_reactivate
(
element
);
}
}
final
_
FreeElements
_freeElements
=
new
_Fre
eElements
();
final
_
InactiveElements
_inactiveElements
=
new
_Inactiv
eElements
();
typedef
void
ElementVisitor
(
Element
element
);
...
...
@@ -496,6 +515,8 @@ abstract class Element<T extends Widget> implements BuildContext {
T
get
widget
=>
_widget
;
T
_widget
;
bool
_active
=
false
;
RenderObject
get
renderObject
{
RenderObject
result
;
void
visit
(
Element
element
)
{
...
...
@@ -515,6 +536,8 @@ abstract class Element<T extends Widget> implements BuildContext {
/// Calls the argument for each child. Must be overridden by subclasses that support having children.
void
visitChildren
(
ElementVisitor
visitor
)
{
}
bool
detachChild
(
Element
child
)
=>
false
;
/// This method is the core of the system.
///
/// It is called each time we are to add, update, or remove a child based on
...
...
@@ -540,7 +563,7 @@ abstract class Element<T extends Widget> implements BuildContext {
Element
updateChild
(
Element
child
,
Widget
newWidget
,
dynamic
newSlot
)
{
if
(
newWidget
==
null
)
{
if
(
child
!=
null
)
_de
tach
Child
(
child
);
_de
activate
Child
(
child
);
return
null
;
}
if
(
child
!=
null
)
{
...
...
@@ -556,17 +579,14 @@ abstract class Element<T extends Widget> implements BuildContext {
assert
(
child
.
widget
==
newWidget
);
return
child
;
}
_de
tach
Child
(
child
);
_de
activate
Child
(
child
);
assert
(
child
.
_parent
==
null
);
}
child
=
newWidget
.
createElement
();
child
.
mount
(
this
,
newSlot
);
assert
(
child
.
_debugLifecycleState
==
_ElementLifecycle
.
active
);
return
child
;
return
_inflateWidget
(
newWidget
,
newSlot
);
}
static
void
finalizeTree
()
{
_
freeElements
.
finalize
All
();
_
inactiveElements
.
unmount
All
();
assert
(
GlobalKey
.
_debugCheckForDuplicates
);
scheduleMicrotask
(
GlobalKey
.
_notifyListeners
);
}
...
...
@@ -582,9 +602,11 @@ abstract class Element<T extends Widget> implements BuildContext {
assert
(
parent
==
null
||
parent
.
_debugLifecycleState
==
_ElementLifecycle
.
active
);
assert
(
slot
==
null
);
assert
(
depth
==
null
);
assert
(!
_active
);
_parent
=
parent
;
_slot
=
newSlot
;
_depth
=
_parent
!=
null
?
_parent
.
depth
+
1
:
1
;
_active
=
true
;
if
(
widget
.
key
is
GlobalKey
)
{
final
GlobalKey
key
=
widget
.
key
;
key
.
_register
(
this
);
...
...
@@ -598,6 +620,7 @@ abstract class Element<T extends Widget> implements BuildContext {
assert
(
widget
!=
null
);
assert
(
newWidget
!=
null
);
assert
(
depth
!=
null
);
assert
(
_active
);
assert
(
_canUpdate
(
widget
,
newWidget
));
_widget
=
newWidget
;
}
...
...
@@ -626,6 +649,16 @@ abstract class Element<T extends Widget> implements BuildContext {
_slot
=
newSlot
;
}
void
_updateDepth
()
{
int
expectedDepth
=
_parent
.
depth
+
1
;
if
(
_depth
<
expectedDepth
)
{
_depth
=
expectedDepth
;
visitChildren
((
Element
child
)
{
child
.
_updateDepth
();
});
}
}
void
detachRenderObject
()
{
visitChildren
((
Element
child
)
{
child
.
detachRenderObject
();
...
...
@@ -633,27 +666,80 @@ abstract class Element<T extends Widget> implements BuildContext {
_slot
=
null
;
}
void
_detachChild
(
Element
child
)
{
void
attachRenderObject
(
dynamic
newSlot
)
{
assert
(
_slot
==
null
);
visitChildren
((
Element
child
)
{
child
.
attachRenderObject
(
newSlot
);
});
_slot
=
newSlot
;
}
Element
_findAndActivateElement
(
GlobalKey
key
,
Widget
newWidget
)
{
Element
element
=
key
.
_currentElement
;
if
(
element
==
null
)
return
null
;
if
(!
_canUpdate
(
element
.
widget
,
newWidget
))
return
null
;
if
(
element
.
_parent
!=
null
&&
!
element
.
_parent
.
detachChild
(
element
))
return
null
;
assert
(
element
.
_parent
==
null
);
_inactiveElements
.
remove
(
element
);
return
element
;
}
Element
_inflateWidget
(
Widget
newWidget
,
dynamic
newSlot
)
{
Key
key
=
newWidget
.
key
;
if
(
key
is
GlobalKey
)
{
Element
newChild
=
_findAndActivateElement
(
key
,
newWidget
);
if
(
newChild
!=
null
)
{
assert
(
newChild
.
_parent
==
null
);
newChild
.
_parent
=
this
;
newChild
.
_updateDepth
();
newChild
.
attachRenderObject
(
newSlot
);
Element
updatedChild
=
updateChild
(
newChild
,
newWidget
,
newSlot
);
assert
(
newChild
==
updatedChild
);
return
updatedChild
;
}
}
Element
newChild
=
newWidget
.
createElement
();
newChild
.
mount
(
this
,
newSlot
);
assert
(
newChild
.
_debugLifecycleState
==
_ElementLifecycle
.
active
);
return
newChild
;
}
void
_deactivateChild
(
Element
child
)
{
assert
(
child
!=
null
);
assert
(
child
.
_parent
==
this
);
child
.
_parent
=
null
;
child
.
detachRenderObject
();
_
fre
eElements
.
add
(
child
);
_
inactiv
eElements
.
add
(
child
);
}
void
deactivate
()
{
assert
(
_debugLifecycleState
==
_ElementLifecycle
.
active
);
assert
(
widget
!=
null
);
assert
(
depth
!=
null
);
assert
(
_active
);
_active
=
false
;
assert
(()
{
_debugLifecycleState
=
_ElementLifecycle
.
inactive
;
return
true
;
});
}
void
reactivate
()
{
assert
(
_debugLifecycleState
==
_ElementLifecycle
.
inactive
);
assert
(
widget
!=
null
);
assert
(
depth
!=
null
);
assert
(!
_active
);
_active
=
true
;
assert
(()
{
_debugLifecycleState
=
_ElementLifecycle
.
active
;
return
true
;
});
}
/// Called when an Element is removed from the tree.
/// Currently, an Element removed from the tree never returns.
void
unmount
()
{
assert
(
_debugLifecycleState
==
_ElementLifecycle
.
inactive
);
assert
(
widget
!=
null
);
assert
(
depth
!=
null
);
assert
(!
_active
);
if
(
widget
.
key
is
GlobalKey
)
{
final
GlobalKey
key
=
widget
.
key
;
key
.
_unregister
(
this
);
...
...
@@ -733,8 +819,6 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
bool
get
dirty
=>
_dirty
;
bool
_dirty
=
true
;
bool
_active
=
false
;
// We to let component authors call setState from initState, didUpdateConfig,
// and build even when state is locked because its convenient and a no-op
// anyway. This flag ensures that this convenience is only allowed on the
...
...
@@ -749,8 +833,7 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
void
mount
(
Element
parent
,
dynamic
newSlot
)
{
super
.
mount
(
parent
,
newSlot
);
assert
(
_child
==
null
);
assert
(!
_active
);
_active
=
true
;
assert
(
_active
);
rebuild
();
assert
(
_child
!=
null
);
}
...
...
@@ -845,10 +928,11 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
visitor
(
_child
);
}
void
deactivate
()
{
super
.
deactivate
();
assert
(
_active
==
true
);
_active
=
false
;
bool
detachChild
(
Element
child
)
{
assert
(
child
==
_child
);
_deactivateChild
(
_child
);
_child
=
null
;
return
true
;
}
void
dependenciesChanged
()
{
...
...
@@ -1039,9 +1123,8 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
void
mount
(
Element
parent
,
dynamic
newSlot
)
{
super
.
mount
(
parent
,
newSlot
);
assert
(
_ancestorRenderObjectElement
==
null
);
_ancestorRenderObjectElement
=
_findAncestorRenderObjectElement
();
_ancestorRenderObjectElement
?.
insertChildRenderObject
(
renderObject
,
newSlot
);
assert
(
_slot
==
newSlot
);
attachRenderObject
(
newSlot
);
ParentDataElement
parentDataElement
=
_findAncestorParentDataElement
();
if
(
parentDataElement
!=
null
)
updateParentData
(
parentDataElement
.
widget
);
...
...
@@ -1141,7 +1224,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
if
(
oldChild
.
widget
.
key
!=
null
)
oldKeyedChildren
[
oldChild
.
widget
.
key
]
=
oldChild
;
else
_de
tach
Child
(
oldChild
);
_de
activate
Child
(
oldChild
);
oldChildrenBottom
-=
1
;
}
}
...
...
@@ -1194,7 +1277,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
// clean up any of the remaining middle nodes from the old list
if
(
haveOldNodes
&&
!
oldKeyedChildren
.
isEmpty
)
{
for
(
Element
oldChild
in
oldKeyedChildren
.
values
)
_de
tach
Child
(
oldChild
);
_de
activate
Child
(
oldChild
);
}
assert
(
renderObject
==
this
.
renderObject
);
// TODO(ianh): Remove this once the analyzer is cleverer
...
...
@@ -1223,6 +1306,13 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
_ancestorRenderObjectElement
.
moveChildRenderObject
(
renderObject
,
slot
);
}
void
attachRenderObject
(
dynamic
newSlot
)
{
assert
(
_ancestorRenderObjectElement
==
null
);
_slot
=
newSlot
;
_ancestorRenderObjectElement
=
_findAncestorRenderObjectElement
();
_ancestorRenderObjectElement
?.
insertChildRenderObject
(
renderObject
,
newSlot
);
}
void
detachRenderObject
()
{
if
(
_ancestorRenderObjectElement
!=
null
)
{
_ancestorRenderObjectElement
.
removeChildRenderObject
(
renderObject
);
...
...
@@ -1270,6 +1360,13 @@ class OneChildRenderObjectElement<T extends OneChildRenderObjectWidget> extends
visitor
(
_child
);
}
bool
detachChild
(
Element
child
)
{
assert
(
child
==
_child
);
_deactivateChild
(
_child
);
_child
=
null
;
return
true
;
}
void
mount
(
Element
parent
,
dynamic
newSlot
)
{
super
.
mount
(
parent
,
newSlot
);
_child
=
updateChild
(
_child
,
widget
.
child
,
null
);
...
...
@@ -1358,9 +1455,7 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
_children
=
new
List
<
Element
>(
widget
.
children
.
length
);
Element
previousChild
;
for
(
int
i
=
_children
.
length
-
1
;
i
>=
0
;
--
i
)
{
Element
newChild
=
widget
.
children
[
i
].
createElement
();
newChild
.
mount
(
this
,
previousChild
);
assert
(
newChild
.
_debugLifecycleState
==
_ElementLifecycle
.
active
);
Element
newChild
=
_inflateWidget
(
widget
.
children
[
i
],
previousChild
);
_children
[
i
]
=
newChild
;
previousChild
=
newChild
;
}
...
...
packages/unit/test/widget/reparent_state_test.dart
0 → 100644
View file @
97eaee34
// 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:sky/widgets.dart'
;
import
'package:test/test.dart'
;
import
'widget_tester.dart'
;
class
StateMarker
extends
StatefulComponent
{
StateMarker
({
Key
key
,
this
.
child
})
:
super
(
key:
key
);
final
Widget
child
;
StateMarkerState
createState
()
=>
new
StateMarkerState
();
}
class
StateMarkerState
extends
State
<
StateMarker
>
{
String
marker
;
Widget
build
(
BuildContext
context
)
{
if
(
config
.
child
!=
null
)
return
config
.
child
;
return
new
Container
();
}
}
void
main
(
)
{
test
(
'can reparent state'
,
()
{
testWidgets
((
WidgetTester
tester
)
{
GlobalKey
left
=
new
GlobalKey
();
GlobalKey
right
=
new
GlobalKey
();
StateMarker
grandchild
=
new
StateMarker
();
tester
.
pumpWidget
(
new
Stack
([
new
Container
(
child:
new
StateMarker
(
key:
left
)
),
new
Container
(
child:
new
StateMarker
(
key:
right
,
child:
grandchild
)
),
])
);
(
left
.
currentState
as
StateMarkerState
).
marker
=
"left"
;
(
right
.
currentState
as
StateMarkerState
).
marker
=
"right"
;
StateMarkerState
grandchildState
=
tester
.
findStateByConfig
(
grandchild
);
expect
(
grandchildState
,
isNotNull
);
grandchildState
.
marker
=
"grandchild"
;
StateMarker
newGrandchild
=
new
StateMarker
();
tester
.
pumpWidget
(
new
Stack
([
new
Container
(
child:
new
StateMarker
(
key:
right
,
child:
newGrandchild
)
),
new
Container
(
child:
new
StateMarker
(
key:
left
)
),
])
);
expect
((
left
.
currentState
as
StateMarkerState
).
marker
,
equals
(
"left"
));
expect
((
right
.
currentState
as
StateMarkerState
).
marker
,
equals
(
"right"
));
StateMarkerState
newGrandchildState
=
tester
.
findStateByConfig
(
newGrandchild
);
expect
(
newGrandchildState
,
isNotNull
);
expect
(
newGrandchildState
,
equals
(
grandchildState
));
expect
(
newGrandchildState
.
marker
,
equals
(
"grandchild"
));
tester
.
pumpWidget
(
new
Center
(
child:
new
Container
(
child:
new
StateMarker
(
key:
left
,
child:
new
Container
()
)
)
)
);
expect
((
left
.
currentState
as
StateMarkerState
).
marker
,
equals
(
"left"
));
expect
(
right
.
currentState
,
isNull
);
});
});
test
(
'can with scrollable list'
,
()
{
testWidgets
((
WidgetTester
tester
)
{
GlobalKey
key
=
new
GlobalKey
();
tester
.
pumpWidget
(
new
StateMarker
(
key:
key
));
(
key
.
currentState
as
StateMarkerState
).
marker
=
"marked"
;
tester
.
pumpWidget
(
new
ScrollableList
<
int
>(
items:
[
0
],
itemExtent:
100.0
,
itemBuilder:
(
BuildContext
context
,
int
item
)
{
return
new
Container
(
key:
new
Key
(
'container'
),
height:
100.0
,
child:
new
StateMarker
(
key:
key
)
);
}
));
expect
((
key
.
currentState
as
StateMarkerState
).
marker
,
equals
(
"marked"
));
tester
.
pumpWidget
(
new
StateMarker
(
key:
key
));
expect
((
key
.
currentState
as
StateMarkerState
).
marker
,
equals
(
"marked"
));
});
});
}
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