Steps to reproduce
- Embed an Android platform view in virtual-display hosting mode (
AndroidView — in our case via mapbox_maps_flutter's MapWidget, default hosting mode).
- Mount the widget while a route transition is in flight, so the platform view attaches before the new route's first layout completes.
RenderAndroidView._setOffset's self-rescheduling frame callback runs before the render box has a size.
Expected results
The offset update is skipped (or deferred) until the box is laid out — the same way _handleGlobalPointerEvent in the same class handles it.
Actual results
RenderBox was not laid out: RenderAndroidView#... — in production crash reporting this surfaces with no application frames (frame-callback context), 6 events / 3 users on our closed-test cohort within a week.
Analysis
RenderAndroidView._setOffset (packages/flutter/lib/src/rendering/platform_view.dart:200 at 3.44.6 stable / ee80f08) reschedules itself every frame guarded only by _isDisposed and attached:
void _setOffset() {
SchedulerBinding.instance.addPostFrameCallback((_) async {
if (!_isDisposed) {
if (attached) {
await _viewController.setOffset(localToGlobal(Offset.zero));
}
// Schedule a new post frame callback.
_setOffset();
}
}, debugLabel: 'RenderAndroidView.setOffset');
}
localToGlobal requires completed layout — on an attached-but-unlaid-out box it throws RenderBox was not laid out. Its sibling in the same file, _handleGlobalPointerEvent (line 359), carries exactly the guard _setOffset lacks — added for the same crash class on the iOS side in #83481 (RenderUiKitView._handleGlobalPointerEvent is not checking for null size, closed by adding a hasSize check):
// Don't receive pointer events if not laid out. ...
if (!hasSize) {
return;
}
So the fix that closed #83481 for pointer events never reached the offset path. Adding the same if (!hasSize) return; (letting the already-scheduled next-frame callback retry after layout) removes the crash without behavior change.
Notes
- Reproduces on stable; the unguarded code is present at current
master.
- Workaround for map users:
androidHostingMode other than virtual display (only VD produces RenderAndroidView), but that is a performance-profile change, not a fix.
Steps to reproduce
AndroidView— in our case viamapbox_maps_flutter'sMapWidget, default hosting mode).RenderAndroidView._setOffset's self-rescheduling frame callback runs before the render box has a size.Expected results
The offset update is skipped (or deferred) until the box is laid out — the same way
_handleGlobalPointerEventin the same class handles it.Actual results
RenderBox was not laid out: RenderAndroidView#...— in production crash reporting this surfaces with no application frames (frame-callback context), 6 events / 3 users on our closed-test cohort within a week.Analysis
RenderAndroidView._setOffset(packages/flutter/lib/src/rendering/platform_view.dart:200 at 3.44.6 stable / ee80f08) reschedules itself every frame guarded only by_isDisposedandattached:localToGlobalrequires completed layout — on an attached-but-unlaid-out box it throwsRenderBox was not laid out. Its sibling in the same file,_handleGlobalPointerEvent(line 359), carries exactly the guard_setOffsetlacks — added for the same crash class on the iOS side in #83481 (RenderUiKitView._handleGlobalPointerEvent is not checking for null size, closed by adding ahasSizecheck):So the fix that closed #83481 for pointer events never reached the offset path. Adding the same
if (!hasSize) return;(letting the already-scheduled next-frame callback retry after layout) removes the crash without behavior change.Notes
master.androidHostingModeother than virtual display (only VD producesRenderAndroidView), but that is a performance-profile change, not a fix.