Unact / yandex_mapkit

Flutter implementation of YandexMapkit
MIT License
132 stars 151 forks source link

Duplicate placemarks. Flutter issues double update commands with toAdd sections #337

Closed Anfet closed 1 month ago

Anfet commented 6 months ago

Encountered the following strange behavior; After the map initialization I try to add placemarks Here is the code.

residents = List.generate(
        10,
        (index) => first.copyWith(
          id: '$index',
          location: Point(
            longitude: first.requireLocation.longitude + (randomizer.nextDouble() * .25 * (randomizer.nextBool() ? 1 : -1)),
            latitude: first.requireLocation.latitude + (randomizer.nextDouble() * 0.25 * (randomizer.nextBool() ? 1 : -1)),
          ),
        ),
      );

      var placemarks = <PlacemarkMapObject>[];
      for (var r in residents) {
        var bytes = await _buildIconFromResident(r);
        var placemark = PlacemarkMapObject2(
          mapId: MapObjectId(r.id),
          opacity: 1.0,
          point: r.requireLocation,
          isDraggable: false,
          consumeTapEvents: true,
          icon: PlacemarkIcon.single(
            PlacemarkIconStyle(
              anchor: const Offset(.5, .5),
              image: BitmapDescriptor.fromBytes(bytes),
            ),
          ),
        );
        placemarks.add(placemark);
      }

      var collection = MapObjectCollection(
        mapId: MapObjectId('residents'),
        mapObjects: placemarks,
      );

Problem: 1st execution of code = 10 marks on screen. 2nd executions of code = 20 marks on screen. 3rd+ executions = 20 marks.

As you can see, map Ids are the same. The code is issuing changes

I/flutter ( 4670): previous object ids => {MapObjectId(0), MapObjectId(1), MapObjectId(2), MapObjectId(3), MapObjectId(4), MapObjectId(5), MapObjectId(6), MapObjectId(7), MapObjectId(8), MapObjectId(9)}
I/flutter ( 4670): current object ids => {MapObjectId(0), MapObjectId(1), MapObjectId(2), MapObjectId(3), MapObjectId(4), MapObjectId(5), MapObjectId(6), MapObjectId(7), MapObjectId(8), MapObjectId(9)}
I/flutter ( 4670): to add => {}
I/flutter ( 4670): to change => {PlacemarkMapObject(MapObjectId(0), Point(47.02195488129437, 28.93402392661096)

but the internal workings of the map still adds additional marks. I've checked - code everytime is issuing changes.

Tried with clusterized collections and individual placemarks. Same output.

mapkit version: 3.3.3 android dependency: com.yandex.android:maps.mobile:4.3.2-full

Anfet commented 6 months ago

Further investigation revealed that there is a double call to updateMapObjects with toAdd argument from flutter.

Afterwards the android plugin executes commands without any verification, thus creating duplicate controllers and/or placemarks. Easiest hotfix would be to add checks to any add methods and verify that required controller does not exist.

java/com/unact/yandexmapkit/MapObjectCollectionController.java

private void addMapObjectCollection(Map<String, Object> params) {
    String id = (String) params.get("id");

    MapObjectCollectionController mapObjectCollectionController = mapObjectCollections.get(id);
    if (mapObjectCollectionController == null) {
      mapObjectCollectionController = new MapObjectCollectionController(
        mapObjectCollection,
        params,
        controller
      );

      mapObjectCollections.put(mapObjectCollectionController.id, mapObjectCollectionController);
    } else {
      changeMapObjectCollection(params);
    }
  }

private void addPlacemark(Map<String, Object> params) {
    String id = (String) params.get("id");
    PlacemarkMapObjectController placemarkController = placemarks.get(id);

    if (placemarkController == null) {
      placemarkController = new PlacemarkMapObjectController(
        mapObjectCollection,
        params,
        controller
      );

      placemarks.put(placemarkController.id, placemarkController);
    } else {
      changePlacemark(params);
    }
  }

I'd offer a request, but not sure that this is full and correct bugfix. The duplicate command from flutter should not come.

dumptyhumpty2014gmail commented 5 months ago

Thx for solution, but new code: private void addPlacemark(Map<String, Object> params) { String id = (String) params.get("id"); PlacemarkMapObjectController placemarkController = placemarks.get(id);

if (placemarkController == null) {
  placemarkController = new PlacemarkMapObjectController(
  clusterizedPlacemarkCollection,
  params,
  controller
  );

  placemarks.put(placemarkController.id, placemarkController);
} else {
  changePlacemark(params);
}    
// PlacemarkMapObjectController placemarkController = new PlacemarkMapObjectController(
//   clusterizedPlacemarkCollection,
//   params,
//   controller
// );

// placemarks.put(placemarkController.id, placemarkController);

}

dumptyhumpty2014gmail commented 5 months ago

if state changes too quickly than addPlacemark added two or more markers

vyurking commented 5 months ago

For ios, you will need to change in two files as well: 1) MapObjectCollectionController 2) ClusterizedPlacemarkCollectionController

In the first file, you need to change the addPlacemark and addMapObjectCollection functions And in the second only addPlacemark

addPlacemark in MapObjectCollectionController

private func addPlacemark(_ params: [String: Any]) {
      guard let id = params["id"] as? String else { return }

      if placemarks[id] != nil {
          changePlacemark(params)
      } else {
          let newPlacemarkController = PlacemarkMapObjectController(
             parent: mapObjectCollection,
             params: params,
             controller: controller!
          )
          placemarks[newPlacemarkController.id] = newPlacemarkController
      }
  }

addMapObjectCollection in MapObjectCollectionController

private func addMapObjectCollection(_ params: [String: Any]) {
      guard let id = params["id"] as? String else { return }

      if mapObjectCollections[id] != nil {
          changeMapObjectCollection(params)
      } else {
          let newController = MapObjectCollectionController(
              parent: mapObjectCollection,
              params: params,
              controller: controller!
          )
          mapObjectCollections[newController.id] = newController
      }
  }

and addPlacemark in ClusterizedPlacemarkCollectionController

private func addPlacemark(_ params: [String: Any]) {
      guard let id = params["id"] as? String else { return }

      if placemarks[id] != nil {
          changePlacemark(params)
      } else {
          let newPlacemarkController = PlacemarkMapObjectController(
             parent: clusterizedPlacemarkCollection,
             params: params,
             controller: controller!
          )
          placemarks[newPlacemarkController.id] = newPlacemarkController
      }
  }
Anfet commented 5 months ago

if state changes too quickly than addPlacemark added two or more markers

can you please define "quickly"?

any change these bugs will be fixed?

dumptyhumpty2014gmail commented 5 months ago

tap/swipe on element in list add marker on map (if in need than with clusters). if user is swiping fast than we have two markers I d'not know at the moment. Users will say :)

DCrow commented 5 months ago

Hello!

@Anfet Please provide a working example. Current example is incomplete and I'm unable to reproduce your issue.

there is a double call to updateMapObjects

If you are using a MapObjectCollection then it is working correctly since internally it wraps all arguments passed to YandexMap.mapObjects in a MapObjectCollection.