Commit 93018391 authored by Adam Barth's avatar Adam Barth Committed by GitHub

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

We now attempt to explain what went wrong.

Fixes #9509
parent 39d9bcda
...@@ -2668,6 +2668,30 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -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. /// Provides a child model for a render object subclass that has a unique child.
abstract class RenderObjectWithChildMixin<ChildType extends RenderObject> implements RenderObject { 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; ChildType _child;
/// The render object's unique child /// The render object's unique child
ChildType get child => _child; ChildType get child => _child;
...@@ -2770,6 +2794,30 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent ...@@ -2770,6 +2794,30 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent
/// The number of children. /// The number of children.
int get childCount => _childCount; 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 _firstChild;
ChildType _lastChild; ChildType _lastChild;
void _insertIntoChildList(ChildType child, { ChildType after }) { void _insertIntoChildList(ChildType child, { ChildType after }) {
......
...@@ -599,6 +599,7 @@ class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObje ...@@ -599,6 +599,7 @@ class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObje
@override @override
void insertChildRenderObject(RenderObject child, dynamic slot) { void insertChildRenderObject(RenderObject child, dynamic slot) {
assert(slot == _rootChildSlot); assert(slot == _rootChildSlot);
assert(renderObject.debugValidateChild(child));
renderObject.child = child; renderObject.child = child;
} }
......
...@@ -4096,6 +4096,7 @@ class SingleChildRenderObjectElement extends RenderObjectElement { ...@@ -4096,6 +4096,7 @@ class SingleChildRenderObjectElement extends RenderObjectElement {
void insertChildRenderObject(RenderObject child, dynamic slot) { void insertChildRenderObject(RenderObject child, dynamic slot) {
final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject; final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
assert(slot == null); assert(slot == null);
assert(renderObject.debugValidateChild(child));
renderObject.child = child; renderObject.child = child;
assert(renderObject == this.renderObject); assert(renderObject == this.renderObject);
} }
...@@ -4144,6 +4145,7 @@ class MultiChildRenderObjectElement extends RenderObjectElement { ...@@ -4144,6 +4145,7 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
@override @override
void insertChildRenderObject(RenderObject child, Element slot) { void insertChildRenderObject(RenderObject child, Element slot) {
final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this.renderObject; final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this.renderObject;
assert(renderObject.debugValidateChild(child));
renderObject.insert(child, after: slot?.renderObject); renderObject.insert(child, after: slot?.renderObject);
assert(renderObject == this.renderObject); assert(renderObject == this.renderObject);
} }
......
...@@ -131,6 +131,7 @@ class _LayoutBuilderElement extends RenderObjectElement { ...@@ -131,6 +131,7 @@ class _LayoutBuilderElement extends RenderObjectElement {
void insertChildRenderObject(RenderObject child, dynamic slot) { void insertChildRenderObject(RenderObject child, dynamic slot) {
final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject; final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
assert(slot == null); assert(slot == null);
assert(renderObject.debugValidateChild(child));
renderObject.child = child; renderObject.child = child;
assert(renderObject == this.renderObject); assert(renderObject == this.renderObject);
} }
......
...@@ -422,6 +422,7 @@ class _TheatreElement extends RenderObjectElement { ...@@ -422,6 +422,7 @@ class _TheatreElement extends RenderObjectElement {
@override @override
void insertChildRenderObject(RenderBox child, dynamic slot) { void insertChildRenderObject(RenderBox child, dynamic slot) {
assert(renderObject.debugValidateChild(child));
if (slot == _onstageSlot) { if (slot == _onstageSlot) {
assert(child is RenderStack); assert(child is RenderStack);
renderObject.child = child; renderObject.child = child;
......
...@@ -647,6 +647,7 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render ...@@ -647,6 +647,7 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
void insertChildRenderObject(covariant RenderObject child, int slot) { void insertChildRenderObject(covariant RenderObject child, int slot) {
assert(slot != null); assert(slot != null);
assert(_currentlyUpdatingChildIndex == slot); assert(_currentlyUpdatingChildIndex == slot);
assert(renderObject.debugValidateChild(child));
renderObject.insert(child, after: _currentBeforeChild); renderObject.insert(child, after: _currentBeforeChild);
assert(() { assert(() {
final SliverMultiBoxAdaptorParentData childParentData = child.parentData; final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
......
...@@ -125,6 +125,7 @@ class _SliverPersistentHeaderElement extends RenderObjectElement { ...@@ -125,6 +125,7 @@ class _SliverPersistentHeaderElement extends RenderObjectElement {
@override @override
void insertChildRenderObject(covariant RenderObject child, Null slot) { void insertChildRenderObject(covariant RenderObject child, Null slot) {
assert(renderObject.debugValidateChild(child));
renderObject.child = child; renderObject.child = child;
} }
......
// 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);
});
}
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