bpillon / google_maps_cluster_manager

Simple Flutter clustering library for Google Maps
https://pub.dev/packages/google_maps_cluster_manager
MIT License
119 stars 95 forks source link

Dificutly setting up the lib when using providers. #3

Closed Marek00Malik closed 4 years ago

Marek00Malik commented 4 years ago

My App uses the provider concept which fetches data. Now, if I understand the tool correctly, it's main idea is to update the stateful widgets state when updating/setting the GooglesMaps markers, which will refresh the widget and reload the ClusterItems with proper Markers. In a provider concept this is not advised and the whole widget works in a totally different way.

I'm trying to adapt your concept to handle the ClusterManager and build it with the data when the provider will return my items but this approach does not display any clusters on the screen, it only appears when I force a Hot Refresh.

Could you please help here?

This is my Widget:

class MapWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => MapWidgetState();
}

class MapWidgetState extends State<MapWidget> {
  ClusterManager<RestaurantItem> _manager;

  Set<Marker> _mapMarkers = HashSet();
  List<ClusterItem<RestaurantItem>> _restaurantItems = List();
  GoogleMapController _controller;

  @override
  Widget build(BuildContext context) {
    return Consumer<RestaurantsData>(
      builder: (innerContext, restaurantsData, __) {
        if (restaurantsData.dataNotLoaded) {
          logger.i("Initializing primary data!");
          return _buildMapDetails(innerContext);
        }
        buildRestaurantMarkers(restaurantsData);

        var locationProv = Provider.of<LocationProvider>(context, listen: false);
        _manager = ClusterManager<RestaurantItem>(_restaurantItems, _updateMarkers, markerBuilder: _markerBuilder, initialZoom: locationProv.currentZoom);

        _onLoadingContent(restaurantsData);

        if (restaurantsData.filteringApplied && restaurantsData.getRestaurants().isNotEmpty) {
          Timer(Duration(milliseconds: 1000), () => _controller.animateCamera(CameraUpdate.newLatLngBounds(boundsFromLatLngList(), 30)));
        }
        return _buildMapDetails(innerContext);
      },
    );
  }

  Widget _buildMapDetails(BuildContext context) {
    return Stack(
      children: <Widget>[
        Scaffold(
          body: _manager != null ? _googleMaps() : Container(),
          ..... < rest of widget contents > .......
        ),
      ],
    );
  }

  Widget _googleMaps() {
    LocationProvider locationProvider = Provider.of<LocationProvider>(context, listen: false);
    return GoogleMap(
      mapType: MapType.normal,
      myLocationEnabled: true,
      myLocationButtonEnabled: false,
      mapToolbarEnabled: false,
      zoomGesturesEnabled: true,
      rotateGesturesEnabled: true,
      initialCameraPosition: CameraPosition(
        zoom: locationProvider.currentZoom,
        target: locationProvider.currentPosition,
      ),
      onMapCreated: (GoogleMapController controller) {
        _controller = controller;
        _manager.setMapController(controller, withUpdate: true);
      },
      markers: _mapMarkers,
      onCameraMove: (CameraPosition cameraPosition) {
        _manager.onCameraMove(cameraPosition, forceUpdate: true);
        locationProvider.setZoom(cameraPosition.zoom);
        locationProvider.setPosition(cameraPosition.target);
      },
      onCameraIdle: _manager.updateMap,
    );
  }

  void buildRestaurantMarkers(RestaurantsData restaurantsData) {
    logger.d("Creating map markers");
    var restaurantItems = restaurantsData.getRestaurants().map((item) => ClusterItem(item.latLng, item: item)).toList();
    this._restaurantItems = restaurantItems;
//    _manager.setItems(restaurantItems);
    logger.d("Cluster Items has been set = ${restaurantItems.length}");
  }

  void _updateMarkers(Set<Marker> markers) {
    logger.d("Markers have been updated, size: ${markers.length}");
    _mapMarkers = markers;
  }

  void _onLoadingContent(RestaurantsData restaurantsData) {
    var contentLoading = Provider.of<LoadingContentProvider>(context, listen: false);
    if (contentLoading.isLoading) {
      Timer(Duration(milliseconds: 0), () {
        logger.d("Notify LoadingContentProvider GoogleMap data loading finished!");
        contentLoading.update(false);
      });
    }
  }

  LatLngBounds boundsFromLatLngList() {
    double x0, x1, y0, y1;
    List<LatLng> positions = _mapMarkers.map((marker) => marker.position).toList();
    for (LatLng latLng in positions) {
      if (x0 == null) {
        x0 = x1 = latLng.latitude;
        y0 = y1 = latLng.longitude;
      } else {
        if (latLng.latitude > x1) x1 = latLng.latitude;
        if (latLng.latitude < x0) x0 = latLng.latitude;
        if (latLng.longitude > y1) y1 = latLng.longitude;
        if (latLng.longitude < y0) y0 = latLng.longitude;
      }
    }
    return LatLngBounds(northeast: LatLng(x1, y1), southwest: LatLng(x0, y0));
  }

  Future<Marker> Function(Cluster<RestaurantItem>) get _markerBuilder => (cluster) async {
        if (cluster.isMultiple) {
          return Marker(
            markerId: MarkerId("cluster|${cluster.getId()}"),
            position: cluster.location,
            icon: await _getMarkerBitmap(cluster),
          );
        }

        var item = cluster.items.first;
        var mapMarkerProvider = Provider.of<MapMarkerProvider>(context, listen: false);
        return Marker(
          position: item.latLng,
          markerId: MarkerId("${item.id}|${item.name}"),
          icon: mapMarkerProvider.forObject(item),
          onTap: () async {
            logger.i("Marker clicked: ${item.id}, ${item.name}");
            showRestaurantDetails(item);
//          restaurantsData.selectOne(item);
            if (_controller != null) _controller.animateCamera(CameraUpdate.newLatLng(item.latLng));
          },
        );
      };

  Future<BitmapDescriptor> _getMarkerBitmap(Cluster<RestaurantItem> clusterOfItems) async {
    int size = 125;

    if (clusterOfItems.isMultiple) {
      final PictureRecorder pictureRecorder = PictureRecorder();
      final Canvas canvas = Canvas(pictureRecorder);
      final Paint paint1 = Paint()..color = CustomColor.green;
      final Paint paint2 = Paint()..color = Colors.white;

      canvas.drawCircle(Offset(size / 2, size / 2), size / 2.0, paint1);
      canvas.drawCircle(Offset(size / 2, size / 2), size / 2.2, paint2);
      canvas.drawCircle(Offset(size / 2, size / 2), size / 2.8, paint1);
      TextPainter painter = TextPainter(textDirection: TextDirection.ltr);
      painter.text = TextSpan(
        text: clusterOfItems.count.toString(),
        style: TextStyle(fontSize: size / 3, color: Colors.white, fontWeight: FontWeight.normal),
      );
      painter.layout();
      painter.paint(
        canvas,
        Offset(size / 2 - painter.width / 2, size / 2 - painter.height / 2),
      );

      final img = await pictureRecorder.endRecording().toImage(size, size);
      final data = await img.toByteData(format: ImageByteFormat.png);

      return BitmapDescriptor.fromBytes(data.buffer.asUint8List());
    } else {
      return Provider.of<MapMarkerProvider>(context, listen: false).forObject(clusterOfItems.items.first);
    }
  }
}
Marek00Malik commented 4 years ago

Quick description, Until the provider is ready and fetched data from an external service it will just build an empty Widget. When restaurantsData.dataNotLoaded will return false, then the provider will be able to return data, and the buildRestaurantMarkers will be able to build the list of ClusterItem> items.

This list is then assigned to the ClusterManager when building it.

_mapMarkers, set of Marker items, that are set by the ClusterManager's _updateMarkers method.

Marek00Malik commented 4 years ago

Ok, I found the cause. I have been resetting the Manager but the google map has not been initialized anymore, so the manager didn't have mapController set.

Closing the issue :)

onurc4kir commented 3 years ago

Ok, I found the cause. I have been resetting the Manager but the google map has not been initialized anymore, so the manager didn't have mapController set.

Closing the issue :)

Hello I have the same issue. How you did you fix it ?