Commit 63e7a0e8 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add BuildContext.size as a convenience getter (#6355)

Developers need to get the size of the BuildContext sufficiently often
that we should provide a convenient getter for the value. Having this
getter is also an opportunity to catch common mistakes and provide
useful error messages that guide developers towards better patterns.

Fixes #2321
parent 4052a366
......@@ -62,11 +62,11 @@ class _GridPhotoViewerState extends State<GridPhotoViewer> with SingleTickerProv
// renderer's box is w,h then the maximum value of the focal point is
// (w * _scale - w)/_scale, (h * _scale - h)/_scale.
Point _clampFocalPoint(Point point) {
final RenderBox box = context.findRenderObject();
final Size size = context.size;
final double inverseScale = (_scale - 1.0) / _scale;
final Point bottomRight = new Point(
box.size.width * inverseScale,
box.size.height * inverseScale
size.width * inverseScale,
size.height * inverseScale,
);
return new Point(point.x.clamp(0.0, bottomRight.x), point.y.clamp(0.0, bottomRight.y));
}
......@@ -101,8 +101,7 @@ class _GridPhotoViewerState extends State<GridPhotoViewer> with SingleTickerProv
if (magnitude < _kMinFlingVelocity)
return;
final Offset direction = details.velocity.pixelsPerSecond / magnitude;
final RenderBox box = context.findRenderObject();
final double distance = (Point.origin & box.size).shortestSide;
final double distance = (Point.origin & context.size).shortestSide;
_flingAnimation = new Tween<Point>(
begin: _focalPoint,
end: _clampFocalPoint(_focalPoint + direction * -distance)
......
......@@ -745,13 +745,11 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
}
void _handleDragUpdate(DragUpdateDetails details) {
final RenderBox box = context.findRenderObject();
_backGestureController?.dragUpdate(details.primaryDelta / box.size.width);
_backGestureController?.dragUpdate(details.primaryDelta / context.size.width);
}
void _handleDragEnd(DragEndDetails details) {
final RenderBox box = context.findRenderObject();
_backGestureController?.dragEnd(details.velocity.pixelsPerSecond.dx / box.size.width);
_backGestureController?.dragEnd(details.velocity.pixelsPerSecond.dx / context.size.width);
_backGestureController = null;
}
......
......@@ -197,10 +197,7 @@ class _DismissableState extends State<Dismissable> with TickerProviderStateMixin
}
double get _overallDragAxisExtent {
final RenderBox box = context.findRenderObject();
assert(box != null);
assert(box.hasSize);
final Size size = box.size;
final Size size = context.size;
return _directionIsXAxis ? size.width : size.height;
}
......@@ -324,8 +321,7 @@ class _DismissableState extends State<Dismissable> with TickerProviderStateMixin
..addListener(_handleResizeProgressChanged);
_resizeController.forward();
setState(() {
RenderBox box = context.findRenderObject();
_sizePriorToCollapse = box.size;
_sizePriorToCollapse = context.size;
_resizeAnimation = new Tween<double>(
begin: 1.0,
end: 0.0
......
......@@ -1340,10 +1340,10 @@ abstract class BuildContext {
/// gesture callbacks) or layout or paint callbacks.
///
/// If the render object is a [RenderBox], which is the common case, then the
/// size of the render object can be obtained from the [RenderBox.size]
/// getter. This is only valid after the layout phase, and should therefore
/// only be examined from paint callbacks or interaction event handlers (e.g.
/// gesture callbacks).
/// size of the render object can be obtained from the [size] getter. This is
/// only valid after the layout phase, and should therefore only be examined
/// from paint callbacks or interaction event handlers (e.g. gesture
/// callbacks).
///
/// For details on the different phases of a frame, see
/// [WidgetsBinding.beginFrame].
......@@ -1354,6 +1354,27 @@ abstract class BuildContext {
/// render object is usually short.
RenderObject findRenderObject();
/// The size of the [RenderBox] returned by [findRenderObject].
///
/// This getter will only return a valid result after the layout phase is
/// complete. It is therefore not valid to call this from the [build] function
/// itself. It should only be called from paint callbacks or interaction event
/// handlers (e.g. gesture callbacks).
///
/// For details on the different phases of a frame, see
/// [WidgetsBinding.beginFrame].
///
/// This getter will only return a valid result if [findRenderObject] actually
/// returns a [RenderBox]. If [findRenderObject] returns a render object that
/// is not a subtype of [RenderBox] (e.g., [RenderView], this getter will
/// throw an exception in checked mode and will return null in release mode.
///
/// Calling this getter is theoretically relatively expensive (O(N) in the
/// depth of the tree), but in practice is usually cheap because the tree
/// usually has many render objects and therefore the distance to the nearest
/// render object is usually short.
Size get size;
/// Obtains the nearest widget of the given type, which must be the type of a
/// concrete [InheritedWidget] subclass, and registers this build context with
/// that widget such that when that widget changes (or a new widget of that
......@@ -2284,6 +2305,85 @@ abstract class Element implements BuildContext {
@override
RenderObject findRenderObject() => renderObject;
@override
Size get size {
assert(() {
if (_debugLifecycleState != _ElementLifecycle.active) {
throw new FlutterError(
'Cannot get size of inactive element.\n'
'In order for an element to have a valid size, the element must be '
'active, which means it is part of the tree. Instead, this element '
'is in the $_debugLifecycleState state.\n'
'The size getter was called for the following element:\n'
' $this\n'
);
}
if (owner._debugBuilding) {
throw new FlutterError(
'Cannot get size during build.\n'
'The size of this render object has not yet been determined because '
'the framework is still in the process of building widgets, which '
'means the render tree for this frame has not youet been determined. '
'The size getter should only be called from paint callbacks or '
'interaction event handlers (e.g. gesture callbacks).\n'
'\n'
'If you need some sizing information during build to decide which '
'widgets to build, consider using a LayoutBuilder widget, which can '
'tell you the layout constraints at a given location in the tree. See '
'<https://docs.flutter.io/flutter/widgets/LayoutBuilder-class.html> '
'for more details.\n'
'\n'
'The size getter was called for the following element:\n'
' $this\n'
);
}
return true;
});
final RenderObject renderObject = findRenderObject();
assert(() {
if (renderObject == null) {
throw new FlutterError(
'Cannot get size without a render object.\n'
'In order for an element to have a valid size, the element must have '
'an assoicated render object. This element does not have an assoicated '
'render object, which typically means that the size getter was called '
'too early in the pipeline (e.g., during the build phase) before the '
'framework has created the render tree.\n'
'The size getter was called for the following element:\n'
' $this\n'
);
}
if (renderObject is! RenderBox) {
throw new FlutterError(
'Cannot get size from a render object that is not a RenderBox.\n'
'Instead of being a subtype of RenderBox, the render object associated '
'with this element is a ${renderObject.runtimeType}. If this type of '
'render object does have a size, consider calling findRenderObject '
'and extracting its size manually.\n'
'The size getter was called for the following element:\n'
' $this\n'
);
}
final RenderBox box = renderObject;
if (!box.hasSize || box.needsLayout) {
throw new FlutterError(
'Cannot get size from a render object that has not been through layout.\n'
'The size of this render object has not yet been determined because '
'this render object has not yet been through layout, which typically '
'means that the size getter was called too early in the pipeline '
'(e.g., during the build pahse) before the framework has determined '
'the size and position of the render objects during layout.\n'
'The size getter was called for the following element:\n'
' $this\n'
);
}
return true;
});
if (renderObject is RenderBox)
return renderObject.size;
return null;
}
Map<Type, InheritedElement> _inheritedWidgets;
Set<InheritedElement> _dependencies;
bool _hadUnsatisfiedDependencies = false;
......
......@@ -197,12 +197,8 @@ class MimicableState extends State<Mimicable> {
}
return true;
});
RenderBox box = context.findRenderObject();
assert(box != null);
assert(box.hasSize);
assert(!box.needsLayout);
setState(() {
_placeholderSize = box.size;
_placeholderSize = context.size;
});
return new MimicableHandle._(this);
}
......
......@@ -595,8 +595,7 @@ class ScrollableState<T extends Scrollable> extends State<T> with SingleTickerPr
/// Returns the snapped offset closest to the given scroll offset.
double snapScrollOffset(double scrollOffset) {
RenderBox box = context.findRenderObject();
return config.snapOffsetCallback == null ? scrollOffset : config.snapOffsetCallback(scrollOffset, box.size);
return config.snapOffsetCallback == null ? scrollOffset : config.snapOffsetCallback(scrollOffset, context.size);
}
Simulation _createSnapSimulation(double scrollVelocity) {
......
......@@ -38,8 +38,8 @@ void main() {
)
);
RenderBox box = alignKey.currentContext.findRenderObject();
expect(box.size.width, equals(800.0));
expect(box.size.height, equals(10.0));
final Size size = alignKey.currentContext.size;
expect(size.width, equals(800.0));
expect(size.height, equals(10.0));
});
}
......@@ -217,9 +217,9 @@ void main() {
await pageRight(tester);
expect(currentPage, equals(5));
RenderBox box = globalKeys[5].currentContext.findRenderObject();
expect(box.size.width, equals(pageSize.width));
expect(box.size.height, equals(pageSize.height));
Size boxSize = globalKeys[5].currentContext.size;
expect(boxSize.width, equals(pageSize.width));
expect(boxSize.height, equals(pageSize.height));
pageSize = new Size(pageSize.height, pageSize.width);
await tester.pumpWidget(buildFrame(itemsWrap: true));
......@@ -231,8 +231,8 @@ void main() {
expect(find.text('4'), findsNothing);
expect(find.text('5'), findsOneWidget);
box = globalKeys[5].currentContext.findRenderObject();
expect(box.size.width, equals(pageSize.width));
expect(box.size.height, equals(pageSize.height));
boxSize = globalKeys[5].currentContext.size;
expect(boxSize.width, equals(pageSize.width));
expect(boxSize.height, equals(pageSize.height));
});
}
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