Skip to content

Commit 6bba637

Browse files
authored
Avoid accessing bounds of widget that has never been laid out (#424)
Fixes a bug in visibility_detector.
1 parent d30d18e commit 6bba637

File tree

4 files changed

+62
-5
lines changed

4 files changed

+62
-5
lines changed

packages/visibility_detector/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# CHANGELOG
22

3+
## 0.4.0+2
4+
5+
* Fix a bug for updates to render objects that have not been laid out yet.
6+
37
## 0.4.0+1
48

59
* Correct Flutter SDK version dependency to 3.1.0.

packages/visibility_detector/lib/src/render_visibility_detector.dart

+17-4
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,14 @@ mixin RenderVisibilityDetectorBase on RenderObject {
140140
}
141141
bool isFirstUpdate = _updates.isEmpty;
142142
_updates[key] = () {
143-
_fireCallback(layer, bounds);
143+
if (bounds == null) {
144+
// This can happen if set onVisibilityChanged was called with a non-null
145+
// value but this render object has not been laid out. In that case,
146+
// it has no size or geometry, and we should not worry about firing
147+
// an update since it never has been visible.
148+
return;
149+
}
150+
_fireCallback(layer, bounds!);
144151
};
145152
final updateInterval = VisibilityDetectorController.instance.updateInterval;
146153
if (updateInterval == Duration.zero) {
@@ -228,7 +235,9 @@ mixin RenderVisibilityDetectorBase on RenderObject {
228235

229236
/// Used to get the bounds of the render object when it is time to update
230237
/// clients about visibility.
231-
Rect get bounds;
238+
///
239+
/// A null value means bounds are not available.
240+
Rect? get bounds;
232241

233242
Matrix4? _lastPaintTransform;
234243
Rect? _lastPaintClipBounds;
@@ -280,7 +289,7 @@ class RenderVisibilityDetector extends RenderProxyBox
280289
final Key key;
281290

282291
@override
283-
Rect get bounds => semanticBounds;
292+
Rect? get bounds => hasSize ? semanticBounds : null;
284293
}
285294

286295
/// The [RenderObject] corresponding to the [SliverVisibilityDetector] widget.
@@ -302,7 +311,11 @@ class RenderSliverVisibilityDetector extends RenderProxySliver
302311
final Key key;
303312

304313
@override
305-
Rect get bounds {
314+
Rect? get bounds {
315+
if (geometry == null) {
316+
return null;
317+
}
318+
306319
Size widgetSize;
307320
Offset widgetOffset;
308321
switch (applyGrowthDirectionToAxisDirection(

packages/visibility_detector/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: visibility_detector
2-
version: 0.4.0+1
2+
version: 0.4.0+2
33
description: >
44
A widget that detects the visibility of its child and notifies a callback.
55
repository: https://github.com/google/flutter.widgets/tree/master/packages/visibility_detector

packages/visibility_detector/test/render_visibility_detector_test.dart

+40
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,44 @@ void main() {
134134

135135
expect(detector.debugScheduleUpdateCount, 0);
136136
});
137+
138+
testWidgets('RVS can schedule an update for a RO that is not laid out',
139+
(WidgetTester tester) async {
140+
final RenderVisibilityDetector detector = RenderVisibilityDetector(
141+
key: Key('test'),
142+
onVisibilityChanged: (_) {
143+
fail('should not get called');
144+
},
145+
);
146+
147+
// Force an out of band update to get scheduled without laying out.
148+
detector.onVisibilityChanged = (_) {
149+
fail('This should also not get called');
150+
};
151+
152+
expect(detector.debugScheduleUpdateCount, 1);
153+
154+
detector.dispose();
155+
});
156+
157+
testWidgets(
158+
'RVS (Sliver) can schedule an update for a RO that is not laid out',
159+
(WidgetTester tester) async {
160+
final RenderSliverVisibilityDetector detector =
161+
RenderSliverVisibilityDetector(
162+
key: Key('test'),
163+
onVisibilityChanged: (_) {
164+
fail('should not get called');
165+
},
166+
);
167+
168+
// Force an out of band update to get scheduled without laying out.
169+
detector.onVisibilityChanged = (_) {
170+
fail('This should also not get called');
171+
};
172+
173+
expect(detector.debugScheduleUpdateCount, 1);
174+
175+
detector.dispose();
176+
});
137177
}

0 commit comments

Comments
 (0)