What is the bug?
initialCameraFit is meant to fit the camera to a given LatLngBounds once, when the map first appears. If the map is nested under a parent that doesn't have its final size on the very first layout pass (e.g. an Expanded/Flexible inside a not-yet-measured parent, an AnimatedSize, a lazily-revealed tab, ...), the fit can be computed against a Size(0, 0) camera instead of the real, final size. The result is a degenerate (fully zoomed-out, whole-world) camera position - and because the "initial fit applied" flag is set regardless, it is never retried once the real size becomes available a moment later. The map keeps showing the wrong view for as long as that FlutterMap widget stays mounted, until the user manually pans or zooms (the camera itself isn't frozen - only the automatic one-time fit never re-runs).
This doesn't crash the app, but it silently defeats the one thing initialCameraFit is meant to do, with no error or log to point at the cause - I'd classify this as a moderate-severity, silent UX bug rather than critical.
Root cause
_FlutterMapStateContainer._parentConstraintsAreSet (lib/src/map/widget.dart):
/// During Flutter startup the native platform resolution is not immediately
/// available which can cause constraints to be zero before they are updated
/// in a subsequent build to the actual constraints. This check allows us to
/// differentiate zero constraints caused by missing platform resolution vs
/// zero constraints which were actually provided by the parent widget.
bool _parentConstraintsAreSet(
BuildContext context, BoxConstraints constraints) =>
constraints.maxWidth != 0 || MediaQuery.sizeOf(context) != Size.zero;
This is meant to distinguish "constraints are zero because the platform hasn't resolved yet" from "constraints are zero because the parent widget actually gave zero space". In practice, MediaQuery.sizeOf(context) is almost always already non-zero (the device's screen size is known essentially immediately), even while constraints.maxWidth is still 0 for one frame because this specific widget's layout hasn't settled yet - the two are independent signals, and the || makes the check pass in exactly the case it's supposed to catch.
_applyInitialCameraFit then runs with constraints still zero-sized:
void _applyInitialCameraFit(BoxConstraints constraints) {
if (!_initialCameraFitApplied &&
widget.options.initialCameraFit != null &&
_parentConstraintsAreSet(context, constraints)) {
_initialCameraFitApplied = true;
_mapController.fitCamera(widget.options.initialCameraFit!);
}
}
_initialCameraFitApplied is set to true unconditionally once this runs, so even though _updateAndEmitSizeIfConstraintsChanged will be called again with the real, non-zero size a moment later, the fit is never recomputed.
How can we reproduce it?
A minimal reproduction is available at https://github.com/nisenbeck/flutter_map_mre, branch repro/zero-size-initial-fit:
git clone https://github.com/nisenbeck/flutter_map_mre
cd flutter_map_mre
git checkout repro/zero-size-initial-fit
flutter pub get
flutter run
It wraps FlutterMap in a small widget that reports Size.zero for exactly one frame before resizing to fill the screen - mimicking the real-world layout patterns mentioned above.
- Expected: the map opens framed on the configured bounds (roughly Germany/Switzerland/Austria).
- Actual (when the bug reproduces): the map opens zoomed all the way out to the whole world, and stays that way until manual interaction.
This is timing-sensitive - if it doesn't reproduce on the very first launch, a few hot restarts (R in the flutter run terminal) reliably trigger it.
I also have a deterministic widget test (no device/timing dependency) demonstrating the same thing directly against FlutterMap, which I'll include with the fix PR.
Do you have a potential solution?
Fixed in #2223.
What is the bug?
initialCameraFitis meant to fit the camera to a givenLatLngBoundsonce, when the map first appears. If the map is nested under a parent that doesn't have its final size on the very first layout pass (e.g. anExpanded/Flexibleinside a not-yet-measured parent, anAnimatedSize, a lazily-revealed tab, ...), the fit can be computed against aSize(0, 0)camera instead of the real, final size. The result is a degenerate (fully zoomed-out, whole-world) camera position - and because the "initial fit applied" flag is set regardless, it is never retried once the real size becomes available a moment later. The map keeps showing the wrong view for as long as thatFlutterMapwidget stays mounted, until the user manually pans or zooms (the camera itself isn't frozen - only the automatic one-time fit never re-runs).This doesn't crash the app, but it silently defeats the one thing
initialCameraFitis meant to do, with no error or log to point at the cause - I'd classify this as a moderate-severity, silent UX bug rather than critical.Root cause
_FlutterMapStateContainer._parentConstraintsAreSet(lib/src/map/widget.dart):This is meant to distinguish "constraints are zero because the platform hasn't resolved yet" from "constraints are zero because the parent widget actually gave zero space". In practice,
MediaQuery.sizeOf(context)is almost always already non-zero (the device's screen size is known essentially immediately), even whileconstraints.maxWidthis still0for one frame because this specific widget's layout hasn't settled yet - the two are independent signals, and the||makes the check pass in exactly the case it's supposed to catch._applyInitialCameraFitthen runs withconstraintsstill zero-sized:_initialCameraFitAppliedis set totrueunconditionally once this runs, so even though_updateAndEmitSizeIfConstraintsChangedwill be called again with the real, non-zero size a moment later, the fit is never recomputed.How can we reproduce it?
A minimal reproduction is available at https://github.com/nisenbeck/flutter_map_mre, branch
repro/zero-size-initial-fit:git clone https://github.com/nisenbeck/flutter_map_mre cd flutter_map_mre git checkout repro/zero-size-initial-fit flutter pub get flutter runIt wraps
FlutterMapin a small widget that reportsSize.zerofor exactly one frame before resizing to fill the screen - mimicking the real-world layout patterns mentioned above.This is timing-sensitive - if it doesn't reproduce on the very first launch, a few hot restarts (
Rin theflutter runterminal) reliably trigger it.I also have a deterministic widget test (no device/timing dependency) demonstrating the same thing directly against
FlutterMap, which I'll include with the fix PR.Do you have a potential solution?
Fixed in #2223.