Commit c130dfff authored by Adam Barth's avatar Adam Barth

Merge pull request #812 from abarth/check_material

Widgets that depend on Material should assert that
parents c0f068ce 8dc607a9
...@@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart'; ...@@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'constants.dart'; import 'constants.dart';
import 'debug.dart';
import 'theme.dart'; import 'theme.dart';
import 'toggleable.dart'; import 'toggleable.dart';
...@@ -35,6 +36,7 @@ class Checkbox extends StatelessComponent { ...@@ -35,6 +36,7 @@ class Checkbox extends StatelessComponent {
final ValueChanged<bool> onChanged; final ValueChanged<bool> onChanged;
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
ThemeData themeData = Theme.of(context); ThemeData themeData = Theme.of(context);
return new _CheckboxRenderObjectWidget( return new _CheckboxRenderObjectWidget(
value: value, value: value,
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
import 'debug.dart';
import 'icon.dart'; import 'icon.dart';
const double _kChipHeight = 32.0; const double _kChipHeight = 32.0;
...@@ -34,6 +35,7 @@ class Chip extends StatelessComponent { ...@@ -34,6 +35,7 @@ class Chip extends StatelessComponent {
final VoidCallback onDeleted; final VoidCallback onDeleted;
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
final bool deletable = onDeleted != null; final bool deletable = onDeleted != null;
double leftPadding = 12.0; double leftPadding = 12.0;
double rightPadding = 12.0; double rightPadding = 12.0;
......
// 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:flutter/widgets.dart';
import 'material.dart';
bool debugCheckHasMaterial(BuildContext context) {
assert(() {
if (context.widget is Material || context.ancestorWidgetOfType(Material) != null)
return true;
Element element = context;
debugPrint('${context.widget} needs to be placed inside a Material widget. Ownership chain:\n${element.debugGetOwnershipChain(10)}');
return false;
});
return true;
}
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'constants.dart'; import 'constants.dart';
import 'debug.dart';
import 'theme.dart'; import 'theme.dart';
// TODO(jackson): This class should usually render the user's // TODO(jackson): This class should usually render the user's
...@@ -16,6 +17,7 @@ class DrawerHeader extends StatelessComponent { ...@@ -16,6 +17,7 @@ class DrawerHeader extends StatelessComponent {
final Widget child; final Widget child;
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
return new Container( return new Container(
height: kStatusBarHeight + kMaterialDrawerHeight, height: kStatusBarHeight + kMaterialDrawerHeight,
decoration: new BoxDecoration( decoration: new BoxDecoration(
......
...@@ -9,6 +9,7 @@ import 'package:flutter/painting.dart'; ...@@ -9,6 +9,7 @@ import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'debug.dart';
import 'icon.dart'; import 'icon.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'shadows.dart'; import 'shadows.dart';
...@@ -262,6 +263,7 @@ class _DropDownButtonState<T> extends State<DropDownButton<T>> { ...@@ -262,6 +263,7 @@ class _DropDownButtonState<T> extends State<DropDownButton<T>> {
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
return new GestureDetector( return new GestureDetector(
onTap: _handleTap, onTap: _handleTap,
child: new Container( child: new Container(
......
...@@ -6,6 +6,7 @@ import 'package:flutter/gestures.dart'; ...@@ -6,6 +6,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'debug.dart';
import 'material.dart'; import 'material.dart';
import 'theme.dart'; import 'theme.dart';
...@@ -137,6 +138,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> { ...@@ -137,6 +138,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> {
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
final bool enabled = config.onTap != null || config.onDoubleTap != null || config.onLongPress != null; final bool enabled = config.onTap != null || config.onDoubleTap != null || config.onLongPress != null;
return new GestureDetector( return new GestureDetector(
onTapDown: enabled ? _handleTapDown : null, onTapDown: enabled ? _handleTapDown : null,
......
...@@ -7,6 +7,7 @@ import 'package:flutter/rendering.dart'; ...@@ -7,6 +7,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'debug.dart';
import 'theme.dart'; import 'theme.dart';
export 'package:flutter/rendering.dart' show ValueChanged; export 'package:flutter/rendering.dart' show ValueChanged;
...@@ -75,6 +76,7 @@ class InputState extends ScrollableState<Input> { ...@@ -75,6 +76,7 @@ class InputState extends ScrollableState<Input> {
} }
Widget buildContent(BuildContext context) { Widget buildContent(BuildContext context) {
assert(debugCheckHasMaterial(context));
ThemeData themeData = Theme.of(context); ThemeData themeData = Theme.of(context);
bool focused = Focus.at(context, config); bool focused = Focus.at(context, config);
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
import 'debug.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'material.dart'; import 'material.dart';
import 'theme.dart'; import 'theme.dart';
...@@ -98,6 +99,7 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> { ...@@ -98,6 +99,7 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> {
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
Widget contents = new InkWell( Widget contents = new InkWell(
onTap: config.onPressed, onTap: config.onPressed,
onHighlightChanged: _handleHighlightChanged, onHighlightChanged: _handleHighlightChanged,
......
...@@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart'; ...@@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'constants.dart'; import 'constants.dart';
import 'debug.dart';
import 'theme.dart'; import 'theme.dart';
import 'toggleable.dart'; import 'toggleable.dart';
...@@ -39,6 +40,7 @@ class Radio<T> extends StatelessComponent { ...@@ -39,6 +40,7 @@ class Radio<T> extends StatelessComponent {
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
ThemeData themeData = Theme.of(context); ThemeData themeData = Theme.of(context);
return new _RadioRenderObjectWidget( return new _RadioRenderObjectWidget(
selected: value == groupValue, selected: value == groupValue,
......
...@@ -10,6 +10,7 @@ import 'package:flutter/widgets.dart'; ...@@ -10,6 +10,7 @@ import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
import 'constants.dart'; import 'constants.dart';
import 'debug.dart';
import 'theme.dart'; import 'theme.dart';
class Slider extends StatelessComponent { class Slider extends StatelessComponent {
...@@ -37,6 +38,7 @@ class Slider extends StatelessComponent { ...@@ -37,6 +38,7 @@ class Slider extends StatelessComponent {
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
return new _SliderRenderObjectWidget( return new _SliderRenderObjectWidget(
value: (value - min) / (max - min), value: (value - min) / (max - min),
primaryColor: Theme.of(context).accentColor, primaryColor: Theme.of(context).accentColor,
......
...@@ -11,6 +11,7 @@ import 'package:flutter/widgets.dart'; ...@@ -11,6 +11,7 @@ import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
import 'constants.dart'; import 'constants.dart';
import 'debug.dart';
import 'shadows.dart'; import 'shadows.dart';
import 'theme.dart'; import 'theme.dart';
import 'toggleable.dart'; import 'toggleable.dart';
...@@ -23,6 +24,7 @@ class Switch extends StatelessComponent { ...@@ -23,6 +24,7 @@ class Switch extends StatelessComponent {
final ValueChanged<bool> onChanged; final ValueChanged<bool> onChanged;
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
ThemeData themeData = Theme.of(context); ThemeData themeData = Theme.of(context);
final isDark = themeData.brightness == ThemeBrightness.dark; final isDark = themeData.brightness == ThemeBrightness.dark;
......
...@@ -76,6 +76,7 @@ class WidgetFlutterBinding extends FlutterBinding { ...@@ -76,6 +76,7 @@ class WidgetFlutterBinding extends FlutterBinding {
void _runApp(Widget app) { void _runApp(Widget app) {
_renderViewElement = new RenderObjectToWidgetAdapter<RenderBox>( _renderViewElement = new RenderObjectToWidgetAdapter<RenderBox>(
container: renderView, container: renderView,
debugShortDescription: '[root]',
child: app child: app
).attachToRenderTree(_renderViewElement); ).attachToRenderTree(_renderViewElement);
beginFrame(); beginFrame();
...@@ -102,11 +103,15 @@ void debugDumpApp() { ...@@ -102,11 +103,15 @@ void debugDumpApp() {
/// RenderObjectWithChildMixin protocol. The type argument T is the kind of /// RenderObjectWithChildMixin protocol. The type argument T is the kind of
/// RenderObject that the container expects as its child. /// RenderObject that the container expects as its child.
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget { class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
RenderObjectToWidgetAdapter({ this.child, RenderObjectWithChildMixin<T> container }) RenderObjectToWidgetAdapter({
: container = container, super(key: new GlobalObjectKey(container)); this.child,
RenderObjectWithChildMixin<T> container,
this.debugShortDescription
}) : container = container, super(key: new GlobalObjectKey(container));
final Widget child; final Widget child;
final RenderObjectWithChildMixin<T> container; final RenderObjectWithChildMixin<T> container;
final String debugShortDescription;
RenderObjectToWidgetElement<T> createElement() => new RenderObjectToWidgetElement<T>(this); RenderObjectToWidgetElement<T> createElement() => new RenderObjectToWidgetElement<T>(this);
...@@ -125,6 +130,8 @@ class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWi ...@@ -125,6 +130,8 @@ class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWi
}, building: true); }, building: true);
return element; return element;
} }
String toStringShort() => debugShortDescription ?? super.toStringShort();
} }
/// This element class is the instantiation of a [RenderObjectToWidgetAdapter]. /// This element class is the instantiation of a [RenderObjectToWidgetAdapter].
......
...@@ -875,6 +875,18 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -875,6 +875,18 @@ abstract class Element<T extends Widget> implements BuildContext {
assert(false); assert(false);
} }
String debugGetOwnershipChain(int limit) {
List<String> chain = <String>[];
Element node = this;
while (chain.length < limit && node != null) {
chain.add(node.toStringShort());
node = node._parent;
}
if (node != null)
chain.add('\u22EF');
return chain.join(' \u2190 ');
}
String toStringShort() { String toStringShort() {
return widget != null ? '${widget.toStringShort()}' : '[$runtimeType]'; return widget != null ? '${widget.toStringShort()}' : '[$runtimeType]';
} }
...@@ -1354,15 +1366,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab ...@@ -1354,15 +1366,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Buildab
} }
void debugUpdateRenderObjectOwner() { void debugUpdateRenderObjectOwner() {
List<String> chain = <String>[]; _renderObject.debugOwner = debugGetOwnershipChain(4);
Element node = this;
while (chain.length < 4 && node != null) {
chain.add(node.toStringShort());
node = node._parent;
}
if (node != null)
chain.add('\u22EF');
_renderObject.debugOwner = chain.join(' \u2190 ');
} }
void performRebuild() { void performRebuild() {
......
...@@ -38,10 +38,12 @@ void main() { ...@@ -38,10 +38,12 @@ void main() {
Widget builder() { Widget builder() {
return new Center( return new Center(
child: new Input( child: new Material(
key: inputKey, child: new Input(
placeholder: 'Placeholder', key: inputKey,
onChanged: (String value) { inputValue = value; } placeholder: 'Placeholder',
onChanged: (String value) { inputValue = value; }
)
) )
); );
} }
...@@ -72,9 +74,11 @@ void main() { ...@@ -72,9 +74,11 @@ void main() {
Widget builder() { Widget builder() {
return new Center( return new Center(
child: new Input( child: new Material(
key: inputKey, child: new Input(
placeholder: 'Placeholder' key: inputKey,
placeholder: 'Placeholder'
)
) )
); );
} }
...@@ -112,9 +116,11 @@ void main() { ...@@ -112,9 +116,11 @@ void main() {
Widget builder() { Widget builder() {
return new Center( return new Center(
child: new Input( child: new Material(
key: inputKey, child: new Input(
placeholder: 'Placeholder' key: inputKey,
placeholder: 'Placeholder'
)
) )
); );
} }
...@@ -145,10 +151,12 @@ void main() { ...@@ -145,10 +151,12 @@ void main() {
Widget builder() { Widget builder() {
return new Center( return new Center(
child: new Input( child: new Material(
key: inputKey, child: new Input(
hideText: true, key: inputKey,
placeholder: 'Placeholder' hideText: true,
placeholder: 'Placeholder'
)
) )
); );
} }
......
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