Commit bb4a2e8b authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Implement detachChild for LayoutBuilder (#5860)

Fixes https://github.com/flutter/flutter/issues/5840
parent cb6b4c95
...@@ -471,6 +471,12 @@ class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObje ...@@ -471,6 +471,12 @@ class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObje
visitor(_child); visitor(_child);
} }
@override
void detachChild(Element child) {
assert(child == _child);
_child = null;
}
@override @override
void mount(Element parent, dynamic newSlot) { void mount(Element parent, dynamic newSlot) {
assert(parent == null); assert(parent == null);
......
...@@ -1143,6 +1143,13 @@ class _InactiveElements { ...@@ -1143,6 +1143,13 @@ class _InactiveElements {
void _unmount(Element element) { void _unmount(Element element) {
assert(element._debugLifecycleState == _ElementLifecycle.inactive); assert(element._debugLifecycleState == _ElementLifecycle.inactive);
assert(() {
if (debugPrintGlobalKeyedWidgetLifecycle) {
if (element.widget.key is GlobalKey)
debugPrint('Discarding $element from inactive elements list.');
}
return true;
});
element.unmount(); element.unmount();
assert(element._debugLifecycleState == _ElementLifecycle.defunct); assert(element._debugLifecycleState == _ElementLifecycle.defunct);
element.visitChildren((Element child) { element.visitChildren((Element child) {
...@@ -1720,7 +1727,15 @@ abstract class Element implements BuildContext { ...@@ -1720,7 +1727,15 @@ abstract class Element implements BuildContext {
visitChildren(visitor); visitChildren(visitor);
} }
bool detachChild(Element child) => false; /// Remove the given child.
///
/// This updates the child model such that [visitChildren] does not walk that
/// child anymore.
///
/// The element must have already been deactivated when this is called,
/// meaning that its parent should be null.
@protected
void detachChild(Element child);
/// This method is the core of the system. /// This method is the core of the system.
/// ///
...@@ -1868,11 +1883,14 @@ abstract class Element implements BuildContext { ...@@ -1868,11 +1883,14 @@ abstract class Element implements BuildContext {
return null; return null;
assert(() { assert(() {
if (debugPrintGlobalKeyedWidgetLifecycle) if (debugPrintGlobalKeyedWidgetLifecycle)
debugPrint('Attempting to take $element from ${element._parent ?? "inactive elements list"} to put in $this'); debugPrint('Attempting to take $element from ${element._parent ?? "inactive elements list"} to put in $this.');
return true; return true;
}); });
if (element._parent != null && !element._parent.detachChild(element)) final Element parent = element._parent;
return null; if (parent != null) {
parent.deactivateChild(element);
parent.detachChild(element);
}
assert(element._parent == null); assert(element._parent == null);
owner._inactiveElements.remove(element); owner._inactiveElements.remove(element);
return element; return element;
...@@ -1930,6 +1948,11 @@ abstract class Element implements BuildContext { ...@@ -1930,6 +1948,11 @@ abstract class Element implements BuildContext {
void _activateWithParent(Element parent, dynamic newSlot) { void _activateWithParent(Element parent, dynamic newSlot) {
assert(_debugLifecycleState == _ElementLifecycle.inactive); assert(_debugLifecycleState == _ElementLifecycle.inactive);
_parent = parent; _parent = parent;
assert(() {
if (debugPrintGlobalKeyedWidgetLifecycle)
debugPrint('Reactivating $this (now child of $_parent).');
return true;
});
_updateDepth(_parent.depth); _updateDepth(_parent.depth);
_activateRecursively(this); _activateRecursively(this);
attachRenderObject(newSlot); attachRenderObject(newSlot);
...@@ -1947,11 +1970,6 @@ abstract class Element implements BuildContext { ...@@ -1947,11 +1970,6 @@ abstract class Element implements BuildContext {
/// instead of being unmounted (see [unmount]). /// instead of being unmounted (see [unmount]).
@mustCallSuper @mustCallSuper
void activate() { void activate() {
assert(() {
if (debugPrintGlobalKeyedWidgetLifecycle)
debugPrint('Reactivating $this (child of $_parent).');
return true;
});
assert(_debugLifecycleState == _ElementLifecycle.inactive); assert(_debugLifecycleState == _ElementLifecycle.inactive);
assert(widget != null); assert(widget != null);
assert(owner != null); assert(owner != null);
...@@ -2387,11 +2405,9 @@ abstract class ComponentElement extends BuildableElement { ...@@ -2387,11 +2405,9 @@ abstract class ComponentElement extends BuildableElement {
} }
@override @override
bool detachChild(Element child) { void detachChild(Element child) {
assert(child == _child); assert(child == _child);
deactivateChild(_child);
_child = null; _child = null;
return true;
} }
} }
...@@ -3026,6 +3042,11 @@ abstract class RootRenderObjectElement extends RenderObjectElement { ...@@ -3026,6 +3042,11 @@ abstract class RootRenderObjectElement extends RenderObjectElement {
class LeafRenderObjectElement extends RenderObjectElement { class LeafRenderObjectElement extends RenderObjectElement {
LeafRenderObjectElement(LeafRenderObjectWidget widget): super(widget); LeafRenderObjectElement(LeafRenderObjectWidget widget): super(widget);
@override
void detachChild(Element child) {
assert(false);
}
@override @override
void insertChildRenderObject(RenderObject child, dynamic slot) { void insertChildRenderObject(RenderObject child, dynamic slot) {
assert(false); assert(false);
...@@ -3064,11 +3085,9 @@ class SingleChildRenderObjectElement extends RenderObjectElement { ...@@ -3064,11 +3085,9 @@ class SingleChildRenderObjectElement extends RenderObjectElement {
} }
@override @override
bool detachChild(Element child) { void detachChild(Element child) {
assert(child == _child); assert(child == _child);
deactivateChild(_child);
_child = null; _child = null;
return true;
} }
@override @override
...@@ -3157,10 +3176,10 @@ class MultiChildRenderObjectElement extends RenderObjectElement { ...@@ -3157,10 +3176,10 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
} }
@override @override
bool detachChild(Element child) { void detachChild(Element child) {
assert(_children.contains(child));
assert(!_detachedChildren.contains(child));
_detachedChildren.add(child); _detachedChildren.add(child);
deactivateChild(child);
return true;
} }
@override @override
......
...@@ -69,6 +69,12 @@ class _LayoutBuilderElement extends RenderObjectElement { ...@@ -69,6 +69,12 @@ class _LayoutBuilderElement extends RenderObjectElement {
visitor(_child); visitor(_child);
} }
@override
void detachChild(Element child) {
assert(child == _child);
_child = null;
}
@override @override
void mount(Element parent, dynamic newSlot) { void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot); // Creates the renderObject. super.mount(parent, newSlot); // Creates the renderObject.
......
...@@ -510,6 +510,18 @@ class _LazyBlockElement extends RenderObjectElement { ...@@ -510,6 +510,18 @@ class _LazyBlockElement extends RenderObjectElement {
visitor(child); visitor(child);
} }
@override
void detachChild(Element child) {
assert(() {
// TODO(ianh): implement detachChild for LazyBlock
throw new FlutterError(
'LazyBlock does not yet support GlobalKey reparenting of its children.\n'
'As a temporary workaround, wrap the child with the GlobalKey in a '
'Container or other harmless child.'
);
});
}
@override @override
void mount(Element parent, dynamic newSlot) { void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot); super.mount(parent, newSlot);
...@@ -585,7 +597,7 @@ class _LazyBlockElement extends RenderObjectElement { ...@@ -585,7 +597,7 @@ class _LazyBlockElement extends RenderObjectElement {
void performRebuild() { void performRebuild() {
IndexedWidgetBuilder builder = widget.delegate.buildItem; IndexedWidgetBuilder builder = widget.delegate.buildItem;
List<Widget> widgets = <Widget>[]; List<Widget> widgets = <Widget>[];
for (int i = 0; i < _children.length; ++i) { for (int i = 0; i < _children.length; i += 1) {
int logicalIndex = _firstChildLogicalIndex + i; int logicalIndex = _firstChildLogicalIndex + i;
Widget childWidget = _callBuilder(builder, logicalIndex); Widget childWidget = _callBuilder(builder, logicalIndex);
if (childWidget == null) if (childWidget == null)
......
...@@ -438,9 +438,10 @@ class _TheatreElement extends RenderObjectElement { ...@@ -438,9 +438,10 @@ class _TheatreElement extends RenderObjectElement {
if (child == _onstage) { if (child == _onstage) {
_onstage = null; _onstage = null;
} else { } else {
assert(_offstage.contains(child));
assert(!_detachedOffstageChildren.contains(child));
_detachedOffstageChildren.add(child); _detachedOffstageChildren.add(child);
} }
deactivateChild(child);
return true; return true;
} }
......
...@@ -279,7 +279,6 @@ class _TableElement extends RenderObjectElement { ...@@ -279,7 +279,6 @@ class _TableElement extends RenderObjectElement {
@override @override
bool detachChild(Element child) { bool detachChild(Element child) {
_detachedChildren.add(child); _detachedChildren.add(child);
deactivateChild(child);
return true; return true;
} }
} }
......
...@@ -100,6 +100,18 @@ abstract class VirtualViewportElement extends RenderObjectElement { ...@@ -100,6 +100,18 @@ abstract class VirtualViewportElement extends RenderObjectElement {
visitor(child); visitor(child);
} }
@override
void detachChild(Element child) {
assert(() {
// TODO(ianh): implement detachChild for VirtualViewport
throw new FlutterError(
'$runtimeType does not yet support GlobalKey reparenting of its children.\n'
'As a temporary workaround, wrap the child with the GlobalKey in a '
'Container or other harmless child.'
);
});
}
_WidgetProvider _widgetProvider; _WidgetProvider _widgetProvider;
@override @override
......
// Copyright 2016 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:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart' hide TypeMatcher;
// This is a regression test for https://github.com/flutter/flutter/issues/5840.
class Bar extends StatefulWidget {
@override
BarState createState() => new BarState();
}
class BarState extends State<Bar> {
final GlobalKey _fooKey = new GlobalKey();
bool _mode = false;
void trigger() {
setState(() {
_mode = !_mode;
});
}
@override
Widget build(BuildContext context) {
if (_mode) {
return new SizedBox(
child: new LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) => new StatefulCreationCounter(key: _fooKey),
),
);
} else {
return new LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) => new StatefulCreationCounter(key: _fooKey),
);
}
}
}
class StatefulCreationCounter extends StatefulWidget {
StatefulCreationCounter({ Key key }) : super(key: key);
@override
StatefulCreationCounterState createState() => new StatefulCreationCounterState();
}
class StatefulCreationCounterState extends State<StatefulCreationCounter> {
static int creationCount = 0;
@override
void initState() {
super.initState();
creationCount += 1;
}
@override
Widget build(BuildContext context) => new Container();
}
void main() {
testWidgets('reparent state with layout builder', (WidgetTester tester) async {
expect(StatefulCreationCounterState.creationCount, 0);
await tester.pumpWidget(new Bar());
expect(StatefulCreationCounterState.creationCount, 1);
BarState s = tester.state/*<BarState>*/(find.byType(Bar));
s.trigger();
await tester.pump();
expect(StatefulCreationCounterState.creationCount, 1);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment