flutter_map
flutter_map copied to clipboard
Stack Overflow Error in feature_layer_utils
What is the bug?
A crash was reported on one of my user's devices. Unfortunately, no logs were sent with it so I only have the stacktrace, but I don't know what happened...
I am very sorry that I can't provide a minimal reproducible example or anything else, but I thought it's probably good to report this here anyways.
This occurred on a non-rooted Android 15 phone in portrait mode. That's all I know :(
This is what my map looks like:
final MapController _mapController = MapController();
late final StreamController<double?> _alignPositionStreamController;
/// ...
return FlutterMap(
mapController: _mapController,
options: MapOptions(
keepAlive: true,
initialCenter: provider.center,
initialZoom: provider.center.zoom,
initialRotation: provider.center.rotation,
),
children: [
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: _packageName,
tileProvider: NetworkTileProvider(silenceExceptions: true),
),
_floorLayer(provider),
if (locationReady) _currentLocationLayer(),
if (_showMarker) _markerLayer(provider),
],
);
/// here are the layers:
Widget _floorLayer(AppDataProvider provider) {
return Consumer<AppDataProvider>(
builder: (context, provider, _) {
String mapAsset = provider.mapAsset;
return IgnorePointer(
child: OverlayImageLayer(
overlayImages: [
RotatedOverlayImage(
bottomLeftCorner: provider.mapBotLeft,
bottomRightCorner: provider.mapBotRight,
topLeftCorner: provider.mapTopLeft,
imageProvider:
AssetImage(mapAsset),
),
],
),
);
},
);
}
Widget _currentLocationLayer() {
return IgnorePointer(
child: CurrentLocationLayer(
alignPositionStream: _alignPositionStreamController.stream,
style: LocationMarkerStyle(
accuracyCircleColor: AppConfig.instance.color.withAlpha(32),
headingSectorColor: AppConfig.instance.color.withAlpha(32),
headingSectorRadius: 10,
marker: DefaultLocationMarker(color: color),
),
),
);
}
Widget _markerLayer(AppDataProvider provider) {
return IgnorePointer(
child: MarkerLayer(markers: [
Marker(
point: provider.marker,
child: Image.asset(AppConfig.instance.markerIcon),
width: 40,
height: 40)
]),
);
}
It was caused in flutter_map/src/layer/shared/feature_layer_utils.dart:66.
Fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: Stack Overflow. Error thrown .
at FeatureLayerUtils.workAcrossWorlds.protectInfiniteLoop(feature_layer_utils.dart:66)
at FeatureLayerUtils.workAcrossWorlds(feature_layer_utils.dart:87)
at CirclePainter.paint(painter.dart:105)
at RenderCustomPaint._paintWithPainter(custom_paint.dart:593)
at RenderCustomPaint.paint(custom_paint.dart:641)
at RenderObject._paintWithContext(object.dart:3483)
at PaintingContext.paintChild(object.dart:261)
at RenderProxyBoxMixin.paint(proxy_box.dart:140)
at PaintingContext.pushTransform(object.dart:798)
at RenderTransform.paint(proxy_box.dart:2686)
at RenderObject._paintWithContext(object.dart:3483)
at PaintingContext.paintChild(object.dart:261)
at RenderShiftedBox.paint(shifted_box.dart:81)
at RenderObject._paintWithContext(object.dart:3483)
at PaintingContext.paintChild(object.dart:261)
at RenderBoxContainerDefaultsMixin.defaultPaint(box.dart:3372)
at RenderStack.paintStack(stack.dart:704)
at RenderStack.paint(stack.dart:720)
at RenderObject._paintWithContext(object.dart:3483)
at PaintingContext.paintChild(object.dart:261)
at RenderProxyBoxMixin.paint(proxy_box.dart:140)
at RenderObject._paintWithContext(object.dart:3483)
at PaintingContext.paintChild(object.dart:261)
at RenderBoxContainerDefaultsMixin.defaultPaint(box.dart:3372)
at RenderStack.paintStack(stack.dart:704)
at RenderStack.paint(stack.dart:720)
at RenderObject._paintWithContext(object.dart:3483)
at PaintingContext.paintChild(object.dart:261)
at RenderProxyBoxMixin.paint(proxy_box.dart:140)
at PaintingContext.pushLayer(object.dart:507)
at PaintingContext.pushClipRect(object.dart:574)
at RenderClipRect.paint(proxy_box.dart:1576)
at RenderObject._paintWithContext(object.dart:3483)
at PaintingContext.paintChild(object.dart:261)
at RenderProxyBoxMixin.paint(proxy_box.dart:140)
at RenderObject._paintWithContext(object.dart:3483)
at PaintingContext.paintChild(object.dart:261)
at RenderProxyBoxMixin.paint(proxy_box.dart:140)
at RenderObject._paintWithContext(object.dart:3483)
at PaintingContext.paintChild(object.dart:261)
at RenderProxyBoxMixin.paint(proxy_box.dart:140)
at RenderObject._paintWithContext(object.dart:3483)
at PaintingContext.paintChild(object.dart:261)
at RenderProxyBoxMixin.paint(proxy_box.dart:140)
at RenderObject._paintWithContext(object.dart:3483)
at PaintingContext.paintChild(object.dart:261)
at _RenderLayoutBuilder.paint(layout_builder.dart:472)
at RenderObject._paintWithContext(object.dart:3483)
at PaintingContext.paintChild(object.dart:261)
at RenderProxyBoxMixin.paint(proxy_box.dart:140)
at RenderObject._paintWithContext(object.dart:3483)
at PaintingContext._repaintCompositedChild(object.dart:176)
at PaintingContext.repaintCompositedChild(object.dart:121)
at PipelineOwner.flushPaint(object.dart:1312)
at PipelineOwner.flushPaint(object.dart:1322)
at RendererBinding.drawFrame(binding.dart:631)
at WidgetsBinding.drawFrame(binding.dart:1242)
at RendererBinding._handlePersistentFrameCallback(binding.dart:495)
at SchedulerBinding._invokeFrameCallback(binding.dart:1438)
at SchedulerBinding.handleDrawFrame(binding.dart:1351)
at SchedulerBinding._handleDrawFrame(binding.dart:1204)
How can we reproduce it?
I don't know 😭
Do you have a potential solution?
No response
Hi @Finni123,
Do you create a CircleLayer in your app? If not, the location layer is likely creating one for you.
This does look like an issue on our end, as we've had one of these previously. There's two possibilities:
- The computation would actually be broken, causing an infinite loop, and we've caught that correctly (a bug in the computation on our side)
- The computation is working correctly, but looping more than expected, and the catcher is having false positives
We'll need to look into this more. Thanks for the report!
@JaffaKetchup In a first approach, that would mean that the user managed to display 30 worlds, which is rather unlikely.
const maxShiftsCount = 30;
int shiftsCount = 0;
void protectInfiniteLoop() {
if (++shiftsCount > maxShiftsCount) throw const StackOverflowError();
}
At least, we could throw a more verbose exception, stating the current world width.
I've also noticed that in the code we use several times the worldWidth getter in the same method. The computation of worldWidth depends on the camera. Would there be side-effects if there's a zoom animation?
Perhaps it would be safer to store this value in a local variable so that the worldWidth == 0 test is always respected in the rest of the workAcrossWorlds method.
Would it be a side-effect of setting viewportRect to Rect.zero - as opposed to null- in that very strange case when paint hasn't been called yet?
In a first approach, that would mean that the user managed to display 30 worlds, which is rather unlikely.
Ah, I think it's likely running into the old 10 limit. I raised it to 30 since the most recent release (@Finni123 can you please confirm your FM version?) because it was crashing at 10 in the demo in e544978 and added a comment that it seemed to be potentially unstable. Maybe if it is repeating more than expected this indicates a performance issue? Or maybe 10 just isn't enough.
I've also noticed that in the code we use several times the worldWidth getter in the same method. The computation of worldWidth depends on the camera. Would there be side-effects if there's a zoom animation? Perhaps it would be safer to store this value in a local variable so that the worldWidth == 0 test is always respected in the rest of the workAcrossWorlds method.
This might be a good idea even just for performance. Getting worldWidth is expensive.
Would it be a side-effect of setting viewportRect to Rect.zero - as opposed to null- in that very strange case when paint hasn't been called yet?
I'm assuming the reporter would not be using master so this is not a factor yet.
@JaffaKetchup In the pubspec.lock file it says flutter_map version 8.1.1 (f7d0379477...).
I'm not directly creating a CircleLayer, but I am using a CurrentLocationLayer from the flutter_map_location_marker package (version 10.1.0 in pubspeck.lock, 474695ec90...), which creates a CircleLayer.
Thanks. For the time being we're hoping that the slight bump that should work from the next release in the iteration limit should be enough to remove what we hope to be a false positive in an infinite loop protection. We've also changed the error to add a little more information, so if it occurs from the next release, please let us know again :)