Google Maps Flutter - Update Markers on data change or movement

15 views Asked by At

I am not sure if this is possible to do or not but I am trying to figure out how to update markers on camera movement, device movement which i am using the centerLongitude and centerLongitude for or data change, is this possible with the current structure of my app? I have tried to add a listener for my locations document in firebase but it didn't work.

class FirestoreMap extends StatefulWidget {
  const FirestoreMap({
    super.key,
    this.width,
    this.height,
    this.places,
    required this.centerLatitude,
    required this.centerLongitude,
    required this.showLocation,
    required this.showCompass,
    required this.showMapToolbar,
    required this.showTraffic,
    required this.allowZoom,
    required this.showZoomControls,
    required this.defaultZoom,
    this.mapStyleJson,
    this.onClickMarker,
  });

  final double? width;
  final double? height;
  final List<LocationsRecord>? places;
  final double centerLatitude;
  final double centerLongitude;
  final bool showLocation;
  final bool showCompass;
  final bool showMapToolbar;
  final bool showTraffic;
  final bool allowZoom;
  final bool showZoomControls;
  final double defaultZoom;
  final Future Function(LocationsRecord? placeRow)? onClickMarker;
  final String? mapStyleJson;

  @override
  State<FirestoreMap> createState() => _FirestoreMapState();
}

class _FirestoreMapState extends State<FirestoreMap> {
  Completer<google_maps_flutter.GoogleMapController> _controller = Completer();
  Map<String, google_maps_flutter.BitmapDescriptor> _customIcons = {};
  Set<google_maps_flutter.Marker> _markers = {};
  // ignore: unused_field
  List<LocationsRecord> _list = [];
  // ignore: unused_field
  StreamSubscription? _locationsSubscription;

  late google_maps_flutter.LatLng _center;

  @override
  void initState() {
    super.initState();
    _center = google_maps_flutter.LatLng(
        widget.centerLatitude, widget.centerLongitude);
    _loadMarkerIcons().then((_) {
      _listenToLocations();
    });
  }

  void _listenToLocations() {
    _locationsSubscription = FirebaseFirestore.instance
        .collection('locations')
        .snapshots()
        .listen((snapshot) {
      final locations = snapshot.docs
          .map((doc) => LocationsRecord.fromSnapshot(doc))
          .toList();
      setState(() {
        _list = locations;
        // Removed the call to _loadMarkerIcons()
      });
    });
  }

  Future<void> _loadMarkerIcons() async {
    Set<String> uniqueIconPaths =
        widget.places?.map((data) => data.coverImage).toSet() ?? {};

    for (String urlString in uniqueIconPaths) {
      if (urlString.isNotEmpty) {
        // Fetch the bytes from the network URL using http package
        final response = await http.get(Uri.parse(urlString));
        if (response.statusCode == 200) {
          // Decode the image from the bytes
          img.Image? image = img.decodeImage(response.bodyBytes);
          if (image != null) {
            // Resize the image
            img.Image thumbnail =
                img.copyResize(image, width: 30); // Set the desired size

            // Create the BitmapDescriptor from resized bytes
            final descriptor = google_maps_flutter.BitmapDescriptor.fromBytes(
              img.encodePng(thumbnail),
            );

            // Store the descriptor with the URL as the key for later use with markers
            _customIcons[urlString] = descriptor;
          } else {
            print('Failed to decode image at $urlString.');
          }
        } else {
          // Handle the case when the image is not retrieved from the network
          print(
              'Failed to load image at $urlString, status code: ${response.statusCode}');
        }
      }
    }
    _updateMarkers(); // Update markers once icons are loaded
  }

  void _updateMarkers() {
    setState(() {
      _markers = _createMarkers();
    });
  }

  void _onMapCreated(google_maps_flutter.GoogleMapController controller) {
    _controller.complete(controller);
    if (widget.mapStyleJson != null) {
      // Apply the map style if provided
      controller.setMapStyle(widget.mapStyleJson);
    }
  }

  Set<google_maps_flutter.Marker> _createMarkers() {
    var tmp = <google_maps_flutter.Marker>{};
    for (int i = 0; i < (widget.places ?? []).length; i++) {
      var place = widget.places?[i];
      final latlng.LatLng coordinates = latlng.LatLng(
          place?.tempAddress?.latitude ?? 0.0,
          place?.tempAddress?.longitude ?? 0.0);
      final google_maps_flutter.LatLng googleMapsLatLng =
          google_maps_flutter.LatLng(
              coordinates.latitude, coordinates.longitude);
      google_maps_flutter.BitmapDescriptor icon =
          _customIcons[place?.coverImage] ??
              google_maps_flutter.BitmapDescriptor.defaultMarker;

      final google_maps_flutter.Marker marker = google_maps_flutter.Marker(
        markerId: google_maps_flutter.MarkerId('${place?.name ?? "Marker"}_$i'),
        position: googleMapsLatLng,
        icon: icon,
        infoWindow: google_maps_flutter.InfoWindow(
            title: place?.name, snippet: place?.description),
        onTap: () async {
          // Animate camera to the marker's position when tapped
          final google_maps_flutter.GoogleMapController controller =
              await _controller.future;
          controller
              .animateCamera(google_maps_flutter.CameraUpdate.newCameraPosition(
            google_maps_flutter.CameraPosition(
              target: googleMapsLatLng,
              zoom: 15.0, // Adjust zoom level as needed
            ),
          ));
          // Execute additional callback if provided
          final callback = widget.onClickMarker;
          if (callback != null) {
            await callback(place);
          }
        },
      );

      tmp.add(marker);
    }
    return tmp;
  }

  @override
  Widget build(BuildContext context) {
    return google_maps_flutter.GoogleMap(
      onMapCreated: _onMapCreated,
      onCameraMove: (cameraPosition) {
        // This callback is triggered on every camera move
        // If you need to update the map based on camera movement, add your logic here
      },
      onCameraIdle: () {
        // This callback is triggered when the camera stops moving
        // You can fetch and update locations here if necessary
      },
      zoomGesturesEnabled: widget.allowZoom,
      zoomControlsEnabled: widget.showZoomControls,
      myLocationEnabled: widget.showLocation,
      myLocationButtonEnabled: true,
      compassEnabled: widget.showCompass,
      mapToolbarEnabled: widget.showMapToolbar,
      mapType: google_maps_flutter.MapType.normal,
      trafficEnabled: widget.showTraffic,
      initialCameraPosition: google_maps_flutter.CameraPosition(
        target: _center,
        zoom: widget.defaultZoom,
      ),
      markers: _markers,
    );
  }
}

I have tried to use different scenarios but all result in the app crashing.

0

There are 0 answers