Block should work inside LazyBlock (#3546)
Previously we were locking down the state even when calling layout in LazyBlock. Now we lock only when building children. Making this work well involved moving the catch out of lockState and into the few callers who actually wanted it. Fixes #3534
This commit is contained in:
parent
00f10da17f
commit
cc9d602b12
@ -285,7 +285,7 @@ class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWi
|
|||||||
} else {
|
} else {
|
||||||
element.update(this);
|
element.update(this);
|
||||||
}
|
}
|
||||||
}, building: true, context: 'while attaching root widget to rendering tree');
|
}, building: true);
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -710,7 +710,7 @@ class BuildOwner {
|
|||||||
///
|
///
|
||||||
/// The context argument is used to describe the scope in case an exception is
|
/// The context argument is used to describe the scope in case an exception is
|
||||||
/// caught while invoking the callback.
|
/// caught while invoking the callback.
|
||||||
void lockState(void callback(), { bool building: false, String context }) {
|
void lockState(void callback(), { bool building: false }) {
|
||||||
bool debugPreviouslyBuilding;
|
bool debugPreviouslyBuilding;
|
||||||
assert(_debugStateLockLevel >= 0);
|
assert(_debugStateLockLevel >= 0);
|
||||||
assert(() {
|
assert(() {
|
||||||
@ -723,8 +723,6 @@ class BuildOwner {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
callback();
|
callback();
|
||||||
} catch (e, stack) {
|
|
||||||
_debugReportException(context, e, stack);
|
|
||||||
} finally {
|
} finally {
|
||||||
assert(() {
|
assert(() {
|
||||||
_debugStateLockLevel -= 1;
|
_debugStateLockLevel -= 1;
|
||||||
@ -758,6 +756,7 @@ class BuildOwner {
|
|||||||
if (_dirtyElements.isEmpty)
|
if (_dirtyElements.isEmpty)
|
||||||
return;
|
return;
|
||||||
Timeline.startSync('Build');
|
Timeline.startSync('Build');
|
||||||
|
try {
|
||||||
lockState(() {
|
lockState(() {
|
||||||
_dirtyElements.sort(_elementSort);
|
_dirtyElements.sort(_elementSort);
|
||||||
int dirtyCount = _dirtyElements.length;
|
int dirtyCount = _dirtyElements.length;
|
||||||
@ -771,11 +770,12 @@ class BuildOwner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(!_dirtyElements.any((BuildableElement element) => element.dirty));
|
assert(!_dirtyElements.any((BuildableElement element) => element.dirty));
|
||||||
|
}, building: true);
|
||||||
|
} finally {
|
||||||
_dirtyElements.clear();
|
_dirtyElements.clear();
|
||||||
}, building: true, context: 'while rebuilding dirty elements');
|
|
||||||
assert(_dirtyElements.isEmpty);
|
|
||||||
Timeline.finishSync();
|
Timeline.finishSync();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Complete the element build pass by unmounting any elements that are no
|
/// Complete the element build pass by unmounting any elements that are no
|
||||||
/// longer active.
|
/// longer active.
|
||||||
@ -789,13 +789,18 @@ class BuildOwner {
|
|||||||
/// about changes to global keys will run.
|
/// about changes to global keys will run.
|
||||||
void finalizeTree() {
|
void finalizeTree() {
|
||||||
Timeline.startSync('Finalize tree');
|
Timeline.startSync('Finalize tree');
|
||||||
|
try {
|
||||||
lockState(() {
|
lockState(() {
|
||||||
_inactiveElements._unmountAll();
|
_inactiveElements._unmountAll();
|
||||||
}, context: 'while finalizing the widget tree');
|
});
|
||||||
assert(GlobalKey._debugCheckForDuplicates);
|
assert(GlobalKey._debugCheckForDuplicates);
|
||||||
scheduleMicrotask(GlobalKey._notifyListeners);
|
scheduleMicrotask(GlobalKey._notifyListeners);
|
||||||
|
} catch (e, stack) {
|
||||||
|
_debugReportException('while finalizing the widget tree', e, stack);
|
||||||
|
} finally {
|
||||||
Timeline.finishSync();
|
Timeline.finishSync();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Cause the entire subtree rooted at the given [Element] to
|
/// Cause the entire subtree rooted at the given [Element] to
|
||||||
/// be entirely rebuilt. This is used by development tools when
|
/// be entirely rebuilt. This is used by development tools when
|
||||||
|
@ -494,7 +494,6 @@ class _LazyBlockElement extends RenderObjectElement {
|
|||||||
void _layout(BoxConstraints constraints) {
|
void _layout(BoxConstraints constraints) {
|
||||||
final double blockExtent = _getMainAxisExtent(renderObject.size);
|
final double blockExtent = _getMainAxisExtent(renderObject.size);
|
||||||
|
|
||||||
owner.lockState(() {
|
|
||||||
final IndexedBuilder builder = widget.delegate.buildItem;
|
final IndexedBuilder builder = widget.delegate.buildItem;
|
||||||
final double startLogicalOffset = widget.startOffset;
|
final double startLogicalOffset = widget.startOffset;
|
||||||
final double endLogicalOffset = startLogicalOffset + blockExtent;
|
final double endLogicalOffset = startLogicalOffset + blockExtent;
|
||||||
@ -524,10 +523,24 @@ class _LazyBlockElement extends RenderObjectElement {
|
|||||||
|
|
||||||
while (currentLogicalIndex > 0 && currentLogicalOffset > startLogicalOffset) {
|
while (currentLogicalIndex > 0 && currentLogicalOffset > startLogicalOffset) {
|
||||||
currentLogicalIndex -= 1;
|
currentLogicalIndex -= 1;
|
||||||
|
Element newElement;
|
||||||
|
owner.lockState(() {
|
||||||
|
// TODO(abarth): Handle exceptions from builder gracefully.
|
||||||
Widget newWidget = builder(this, currentLogicalIndex);
|
Widget newWidget = builder(this, currentLogicalIndex);
|
||||||
assert(newWidget != null);
|
if (newWidget == null) {
|
||||||
|
throw new FlutterError(
|
||||||
|
'buildItem must not return null after returning non-null.\n'
|
||||||
|
'If buildItem for a LazyBlockDelegate returns a non-null widget for a given '
|
||||||
|
'index, it must return non-null widgets for every smaller index as well. The '
|
||||||
|
'buildItem function for ${widget.delegate.runtimeType} returned null for '
|
||||||
|
'index $currentLogicalIndex after having returned a non-null value for index '
|
||||||
|
'${currentLogicalIndex - 1}.'
|
||||||
|
);
|
||||||
|
}
|
||||||
newWidget = new RepaintBoundary.wrap(newWidget, currentLogicalIndex);
|
newWidget = new RepaintBoundary.wrap(newWidget, currentLogicalIndex);
|
||||||
newChildren.add(inflateWidget(newWidget, null));
|
newElement = inflateWidget(newWidget, null);
|
||||||
|
}, building: true);
|
||||||
|
newChildren.add(newElement);
|
||||||
RenderBox child = block.firstChild;
|
RenderBox child = block.firstChild;
|
||||||
assert(child == newChildren.last.renderObject);
|
assert(child == newChildren.last.renderObject);
|
||||||
child.layout(innerConstraints, parentUsesSize: true);
|
child.layout(innerConstraints, parentUsesSize: true);
|
||||||
@ -592,12 +605,19 @@ class _LazyBlockElement extends RenderObjectElement {
|
|||||||
int physicalIndex = currentLogicalIndex - _firstChildLogicalIndex;
|
int physicalIndex = currentLogicalIndex - _firstChildLogicalIndex;
|
||||||
if (physicalIndex >= _children.length) {
|
if (physicalIndex >= _children.length) {
|
||||||
assert(physicalIndex == _children.length);
|
assert(physicalIndex == _children.length);
|
||||||
|
Element newElement;
|
||||||
|
owner.lockState(() {
|
||||||
|
// TODO(abarth): Handle exceptions from builder gracefully.
|
||||||
Widget newWidget = builder(this, currentLogicalIndex);
|
Widget newWidget = builder(this, currentLogicalIndex);
|
||||||
if (newWidget == null)
|
if (newWidget == null)
|
||||||
break;
|
return;
|
||||||
newWidget = new RepaintBoundary.wrap(newWidget, currentLogicalIndex);
|
newWidget = new RepaintBoundary.wrap(newWidget, currentLogicalIndex);
|
||||||
Element previousChild = _children.isEmpty ? null : _children.last;
|
Element previousChild = _children.isEmpty ? null : _children.last;
|
||||||
_children.add(inflateWidget(newWidget, previousChild));
|
newElement = inflateWidget(newWidget, previousChild);
|
||||||
|
}, building: true);
|
||||||
|
if (newElement == null)
|
||||||
|
return;
|
||||||
|
_children.add(newElement);
|
||||||
}
|
}
|
||||||
child = _getNextWithin(block, child);
|
child = _getNextWithin(block, child);
|
||||||
assert(child != null);
|
assert(child != null);
|
||||||
@ -652,7 +672,6 @@ class _LazyBlockElement extends RenderObjectElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_updatePaintOffset();
|
_updatePaintOffset();
|
||||||
}, building: true, context: 'during $runtimeType layout');
|
|
||||||
|
|
||||||
LazyBlockExtentsChangedCallback onExtentsChanged = widget.onExtentsChanged;
|
LazyBlockExtentsChangedCallback onExtentsChanged = widget.onExtentsChanged;
|
||||||
if (onExtentsChanged != null) {
|
if (onExtentsChanged != null) {
|
||||||
|
@ -164,7 +164,7 @@ abstract class VirtualViewportElement extends RenderObjectElement {
|
|||||||
assert(startOffsetBase != null);
|
assert(startOffsetBase != null);
|
||||||
assert(startOffsetLimit != null);
|
assert(startOffsetLimit != null);
|
||||||
_updatePaintOffset();
|
_updatePaintOffset();
|
||||||
owner.lockState(_materializeChildren, building: true, context: 'during $runtimeType layout');
|
owner.lockState(_materializeChildren, building: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _materializeChildren() {
|
void _materializeChildren() {
|
||||||
|
34
packages/flutter/test/widget/lazy_block_test.dart
Normal file
34
packages/flutter/test/widget/lazy_block_test.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 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_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('Block inside LazyBlock', () {
|
||||||
|
testWidgets((WidgetTester tester) {
|
||||||
|
tester.pumpWidget(new LazyBlock(
|
||||||
|
delegate: new LazyBlockChildren(
|
||||||
|
children: <Widget>[
|
||||||
|
new Block(
|
||||||
|
children: <Widget>[
|
||||||
|
new Text('1'),
|
||||||
|
new Text('2'),
|
||||||
|
new Text('3'),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new Block(
|
||||||
|
children: <Widget>[
|
||||||
|
new Text('4'),
|
||||||
|
new Text('5'),
|
||||||
|
new Text('6'),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user