Box/Sliver mismatches should have better error messages (#9525)

We now attempt to explain what went wrong.

Fixes #9509
This commit is contained in:
Adam Barth 2017-04-21 14:09:25 -07:00 committed by GitHub
parent 39d9bcdae8
commit 930183916c
8 changed files with 113 additions and 0 deletions

View file

@ -2668,6 +2668,30 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
///
/// Provides a child model for a render object subclass that has a unique child.
abstract class RenderObjectWithChildMixin<ChildType extends RenderObject> implements RenderObject {
bool debugValidateChild(RenderObject child) {
assert(() {
if (child is! ChildType) {
throw new FlutterError(
'A $runtimeType expected a child of type $ChildType but received a '
'child of type ${child.runtimeType}.\n'
'RenderObjects expect specific types of children because they '
'coordinate with their children during layout and paint. For '
'example, a RenderSliver cannot be the child of a RenderBox because '
'a RenderSliver does not understand the RenderBox layout protocol.\n'
'\n'
'The $runtimeType that expected a $ChildType child was created by:\n'
' $debugCreator\n'
'\n'
'The ${child.runtimeType} that did not match the expected child type '
'was created by:\n'
' ${child.debugCreator}\n'
);
}
return true;
});
return true;
}
ChildType _child;
/// The render object's unique child
ChildType get child => _child;
@ -2770,6 +2794,30 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent
/// The number of children.
int get childCount => _childCount;
bool debugValidateChild(RenderObject child) {
assert(() {
if (child is! ChildType) {
throw new FlutterError(
'A $runtimeType expected a child of type $ChildType but received a '
'child of type ${child.runtimeType}.\n'
'RenderObjects expect specific types of children because they '
'coordinate with their children during layout and paint. For '
'example, a RenderSliver cannot be the child of a RenderBox because '
'a RenderSliver does not understand the RenderBox layout protocol.\n'
'\n'
'The $runtimeType that expected a $ChildType child was created by:\n'
' $debugCreator\n'
'\n'
'The ${child.runtimeType} that did not match the expected child type '
'was created by:\n'
' ${child.debugCreator}\n'
);
}
return true;
});
return true;
}
ChildType _firstChild;
ChildType _lastChild;
void _insertIntoChildList(ChildType child, { ChildType after }) {

View file

@ -599,6 +599,7 @@ class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObje
@override
void insertChildRenderObject(RenderObject child, dynamic slot) {
assert(slot == _rootChildSlot);
assert(renderObject.debugValidateChild(child));
renderObject.child = child;
}

View file

@ -4096,6 +4096,7 @@ class SingleChildRenderObjectElement extends RenderObjectElement {
void insertChildRenderObject(RenderObject child, dynamic slot) {
final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
assert(slot == null);
assert(renderObject.debugValidateChild(child));
renderObject.child = child;
assert(renderObject == this.renderObject);
}
@ -4144,6 +4145,7 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
@override
void insertChildRenderObject(RenderObject child, Element slot) {
final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this.renderObject;
assert(renderObject.debugValidateChild(child));
renderObject.insert(child, after: slot?.renderObject);
assert(renderObject == this.renderObject);
}

View file

@ -131,6 +131,7 @@ class _LayoutBuilderElement extends RenderObjectElement {
void insertChildRenderObject(RenderObject child, dynamic slot) {
final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
assert(slot == null);
assert(renderObject.debugValidateChild(child));
renderObject.child = child;
assert(renderObject == this.renderObject);
}

View file

@ -422,6 +422,7 @@ class _TheatreElement extends RenderObjectElement {
@override
void insertChildRenderObject(RenderBox child, dynamic slot) {
assert(renderObject.debugValidateChild(child));
if (slot == _onstageSlot) {
assert(child is RenderStack);
renderObject.child = child;

View file

@ -647,6 +647,7 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
void insertChildRenderObject(covariant RenderObject child, int slot) {
assert(slot != null);
assert(_currentlyUpdatingChildIndex == slot);
assert(renderObject.debugValidateChild(child));
renderObject.insert(child, after: _currentBeforeChild);
assert(() {
final SliverMultiBoxAdaptorParentData childParentData = child.parentData;

View file

@ -125,6 +125,7 @@ class _SliverPersistentHeaderElement extends RenderObjectElement {
@override
void insertChildRenderObject(covariant RenderObject child, Null slot) {
assert(renderObject.debugValidateChild(child));
renderObject.child = child;
}

View file

@ -0,0 +1,58 @@
// Copyright 2017 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/widgets.dart';
void main() {
testWidgets('Sliver in a box', (WidgetTester tester) async {
await tester.pumpWidget(
new DecoratedBox(
decoration: const BoxDecoration(),
child: new SliverList(
delegate: const SliverChildListDelegate(const <Widget>[]),
),
),
);
expect(tester.takeException(), isFlutterError);
await tester.pumpWidget(
new Row(
children: <Widget>[
new SliverList(
delegate: const SliverChildListDelegate(const <Widget>[]),
),
],
),
);
expect(tester.takeException(), isFlutterError);
});
testWidgets('Box in a sliver', (WidgetTester tester) async {
await tester.pumpWidget(
new CustomScrollView(
slivers: <Widget>[
const SizedBox(),
],
)
);
expect(tester.takeException(), isFlutterError);
await tester.pumpWidget(
new CustomScrollView(
slivers: <Widget>[
const SliverPadding(
padding: EdgeInsets.zero,
sliver: const SizedBox(),
),
],
)
);
expect(tester.takeException(), isFlutterError);
});
}