Compare commits

..

2 commits

Author SHA1 Message Date
8b53075a92 Merge pull request 'fix: stop syncing gesture positions back to mapProvider' (#2) from claude/relaxed-wright into main
Reviewed-on: #2
2026-04-04 14:16:35 +00:00
Shautvast
a3aaf1b6de fix: stop syncing gesture positions back to mapProvider
Removed the updateCamera() call from onPositionChanged. Previously,
every gesture-driven pan/zoom frame updated the provider state, which
triggered ref.listen to call _mapController.move() redundantly.
This caused flutter_map's TileLayer to cancel in-flight tile loads
on every frame — producing partially-rendered maps and crashes when
zooming out during tile loading.

The ref.listen now only fires for programmatic changes (locateUser,
zoomIn/zoomOut), which are infrequent single-shot moves.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 16:15:02 +02:00

View file

@ -41,20 +41,14 @@ class _MapScreenState extends ConsumerState<MapScreen> {
final apiClient = ref.watch(apiClientProvider); final apiClient = ref.watch(apiClientProvider);
final styleAsync = ref.watch(mapStyleProvider(apiClient.baseUrl)); final styleAsync = ref.watch(mapStyleProvider(apiClient.baseUrl));
// Listen for zoom/center changes from the provider and move the map. // Move the map only for programmatic state changes (locateUser,
// Skip the move if the map controller is already at the target position // zoomIn/zoomOut). Gesture-driven pans no longer call updateCamera(),
// (meaning the state change came from a user gesture via onPositionChanged). // so this listener only fires for intentional moves and won't interfere
// Without this guard, every gesture-driven pan calls _mapController.move() // with the TileLayer's in-flight tile loads.
// with the position the map is already at, which triggers a tile
// recalculation that cancels in-flight tile loads and produces a
// partially-rendered (diagonal) map.
ref.listen<MapState>(mapProvider, (previous, next) { ref.listen<MapState>(mapProvider, (previous, next) {
if (previous?.center != next.center || previous?.zoom != next.zoom) { if (previous?.center != next.center || previous?.zoom != next.zoom) {
final camera = _mapController.camera;
if (camera.center != next.center || camera.zoom != next.zoom) {
_mapController.move(next.center, next.zoom); _mapController.move(next.center, next.zoom);
} }
}
}); });
return Scaffold( return Scaffold(
@ -69,12 +63,12 @@ class _MapScreenState extends ConsumerState<MapScreen> {
minZoom: 0, minZoom: 0,
maxZoom: 18, maxZoom: 18,
onPositionChanged: (position, hasGesture) { onPositionChanged: (position, hasGesture) {
if (hasGesture) { // Intentionally not syncing gesture-driven position changes
ref.read(mapProvider.notifier).updateCamera( // back to the provider. Doing so caused ref.listen to call
position.center, // _mapController.move() on every pan/zoom frame, which made
position.zoom, // flutter_map's TileLayer cancel in-flight tile loads —
); // producing a partially-rendered (diagonal) map and crashes
} // when zooming out during tile loading.
}, },
onTap: (tapPosition, point) { onTap: (tapPosition, point) {
ref.read(mapProvider.notifier).clearSelectedPlace(); ref.read(mapProvider.notifier).clearSelectedPlace();