flutter_map icon indicating copy to clipboard operation
flutter_map copied to clipboard

[BUG] Exception "`MapCamera` is no longer within the `cameraConstraint` after an option change" when using particular camera setups

Open dumabg opened this issue 7 months ago • 17 comments

What is the bug?

A FlutterMap with a cameraConstraint CameraConstraint.contain(bounds: widget.bounds) gives the assert error "MapCamera is no longer within the cameraConstraint after an option change"

How can we reproduce it?

Put a ValueListenableBuilder and a FlutterMap with a cameraConstraint. Change the ValueListenableBuilder value to trigger a new repaint.

Do you have a potential solution?

I've comment the assert and seems that it works

Platforms

Android

Severity

Erroneous: Prevents normal functioning and causes errors in the console

dumabg avatar Dec 06 '23 12:12 dumabg

Hi @dumabg, Can I just double check what version you are running?

JaffaKetchup avatar Dec 06 '23 12:12 JaffaKetchup

It happens the same to me.

pubspect.yaml:

  flutter_map: ^6.1.0
  flutter_map_cancellable_tile_provider: ^2.0.0
  flutter_map_marker_cluster: ^1.3.4

My code:

 MapOptions _buildMapOptions() => MapOptions(
    initialCenter: widget._parametersMap.currentPosition.center,
    initialZoom: widget._parametersMap.initialZoom,
    maxZoom: widget._parametersMap.maxZoom,
    minZoom: widget._parametersMap.minZoom,
    initialRotation: widget._parametersMap.rotation,
    cameraConstraint: widget._parametersMap.cameraConstraint,
    initialCameraFit: markers.isNotEmpty &&
            UserPreferences.getInstance().currentPosition == null
        ? markers.getCameraFit(
            minZoom: widget._parametersMap.minZoom,
            maxZoom: widget._parametersMap.initialZoom,
          )
        : null,
    keepAlive: true,
    onMapReady: () {
      // logger.d('onMapReady !!');
    },
    onPositionChanged: (MapPosition position, bool hasGesture) {
      // logger.d('onPositionChanged hasGesture: $hasGesture,
      // position: $position');

      if (hasGesture) {
        ref.read(mapChangedProvider.notifier).changePosition();
      }
    },
    onTap: (_, __) => widget._popupController.hideAllPopups(),
  );

  @override
  Widget build(BuildContext context) {
    final markersAsync = ref.watch(markersProvider);

    return markersAsync.when(
      data: (values) {
        if (values.isNotEmpty) {
          setState(() {
            markers = List.from(values);
          });
        }

        return PopupScope(
          popupController: widget._popupController,
          child: Stack(
            children: [
              FlutterMap(
                mapController: widget._mapController,
                options: _buildMapOptions(),
                children: [
                     ...
				],
          ),
        );
      },
      loading: () => const CustomProgress(),
      error: (error, stackTrace) {
        context.showSnackbar(
          message: error.toString(),
          type: MessageType.error,
        );

        return const SizedBox.shrink();
      },
    );
  }

Error:

MapCamera is no longer within the cameraConstraint after an option change.
'package:flutter_map/src/map/controller/internal.dart':
Failed assertion: line 276 pos 7: 'newOptions.cameraConstraint.constrain(newCamera) == newCamera'

What is the reason for the error?

thorito avatar Jan 09 '24 08:01 thorito

I can't tell exactly without a more minimal reproducible example, but there's a few things that could be causing issues:

  • Building the map options in a function
  • Not sure if initialCameraFit changes, but changing it will have no effect and could cause issues
  • Not sure what PopupScope is, but maybe that's causing issues?

We need an MRE to investigate this further (standalone, no state from other parts of app, no dependencies, etc.).

JaffaKetchup avatar Jan 09 '24 09:01 JaffaKetchup

@JaffaKetchup @josxha here is a complete single file minimum reproducible example

// ignore_for_file: prefer_const_constructors, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables

import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int cnt = 0;

  @override
  void initState() {
    super.initState();
    Future(() async {
      await Future.delayed(Duration(seconds: 1));
      cnt++;
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    final camConstraint = cnt > 0
        ? CameraConstraint.contain(
            bounds: LatLngBounds(LatLng(43.6884447292, 20.2201924985),
                LatLng(48.2208812526, 29.62654341)))
        : null;
    return Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: Text(widget.title),
        ),
        body: FlutterMap(
            options: MapOptions(
                interactionOptions: InteractionOptions(
                    flags: InteractiveFlag.pinchZoom |
                        InteractiveFlag.drag |
                        InteractiveFlag.doubleTapZoom |
                        InteractiveFlag.scrollWheelZoom,
                    rotationWinGestures: MultiFingerGesture.none,
                    rotationThreshold: double.infinity,
                    cursorKeyboardRotationOptions:
                        CursorKeyboardRotationOptions.disabled()),
                minZoom: 1,
                maxZoom: 20,
                initialZoom: 5.9,
                initialCenter: LatLng(45.80565, 24.937853),
                cameraConstraint: camConstraint),
            children: []));
  }
}

pubspec.yaml

name: flutter_map_constraint_issue
description: "A new Flutter project."

publish_to: "none"

version: 1.0.0+1

environment:
  sdk: ">=3.2.5 <4.0.0"

dependencies:
  cupertino_icons: ^1.0.2
  flutter:
    sdk: flutter
  flutter_map: ^6.1.0

dev_dependencies:
  flutter_lints: ^2.0.0
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

the example above produces after 1 second the error

════════ Exception caught by widgets library ═══════════════════════════════════
The following assertion was thrown building KeyedSubtree-[GlobalKey#de288]:
MapCamera is no longer within the cameraConstraint after an option change.
'package:flutter_map/src/map/controller/internal.dart':
Failed assertion: line 276 pos 7: 'newOptions.cameraConstraint.constrain(newCamera) == newCamera'

The relevant error-causing widget was:
    Scaffold Scaffold:file:///D:/temp/flutter_map_constraint_issue/lib/main.dart:56:12

let me know if i can assist or provide anything else.

iulian0512 avatar Jan 26 '24 15:01 iulian0512

Thanks @iulian0512, we'll look into this :)

JaffaKetchup avatar Jan 31 '24 21:01 JaffaKetchup

any update on this ? In my case it's not just error in console, but map is not displayed at all (I'm using 6.1.0 as well)

EDIT: using CameraConstraint.containCenter() instead of CameraConstraint.contain() fixed the issue for me (.containerCenter is slightly different than .contain, but in my case it doesn't matter)

palicka avatar Feb 26 '24 18:02 palicka