Search code examples
google-mapsfluttergoogle-cloud-firestoregoogle-maps-markersstream-builder

Flutter - Provide Real-Time Updates to Google Maps Using StreamBuilder


I'm currently building an application that displays markers on a Google Map. The location of the markers is being pulled from a Firestore database. Currently, I am successfully retrieving and displaying all markers. However, I need to update the icon of each marker based on their boolean value in the database.

Here is how I am currently displaying the markers.

I create the map in the main build:

Container(
          height: MediaQuery.of(context).size.height,
          width: MediaQuery.of(context).size.width,
          child: mapToggle
              ? GoogleMap(
            onMapCreated: onMapCreated,
            compassEnabled: false,
            polylines: route,
            initialCameraPosition: CameraPosition(
                target: LatLng(currentLocation.latitude,
                    currentLocation.longitude),
                zoom: 12),
            markers: markers,
          )
              : Center(
            child: Text('Loading... Please wait...',
                style: TextStyle(fontSize: 20.0)),
          )),

Then I call this function in the initState to grab my markers:

chargePoints() {
_database.collection('charge_points').getDocuments().then((docs) {
  if (docs.documents.isNotEmpty) {
    for (int i = 0; i < docs.documents.length; i++) {
      initMarker(docs.documents[i].data, docs.documents[i].documentID);
    }
  }
});

Finally, the initMarker function adds each marker to the Set marker, taken by the Google Map instance:

void initMarker(chargePoint, documentID) {
var markerIdVal = documentID;
final MarkerId markerId = MarkerId(markerIdVal);

// Creating a new Charge Point Marker
final Marker marker = Marker(
    markerId: markerId,
    icon: markerIcon,
    position: LatLng(chargePoint['location'].latitude,
        chargePoint['location'].longitude),
    infoWindow: InfoWindow(
        title: chargePoint['name'], snippet: chargePoint['type']),
    onTap: () {
      findBookings(context, chargePoint, documentID);
    });

setState(() {
  markers.add(marker);
});

So my question is; how can I return this data as a Stream, and provide a listener that updates the respective marker icon, when the "occupied" field has been changed?

Firestore

Map UI


Solution

  • Okay, I got a solution!

    I'm now returning a StreamBuilder in the main build Widget that takes a Firestore query to return the details of all markers.

    I then use the snapshot provided by the stream to intermittently transform the state of all markers set in the initMarker function above.

    Additionally, the markers Set is now a Map. This makes more sense for accessing the markers to perform updates.

    Map<MarkerId, Marker> markers = <MarkerId, Marker>{};
    

    The markers are managed using their MarkerId, which correlates to the documentID of all results returned in the snapshot.

    I don't believe this solution is perfect, but I hope I've helped someone out!

    StreamBuilder<QuerySnapshot>(
          stream: _servicesObj.getChargePoints(),
          builder:
              (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
            if (!snapshot.hasData) {
              return Text('Loading Points');
            }
    
            snapshot.data.documents.forEach((change) {
              var markerIdVal = change.documentID;
              final MarkerId markerId = MarkerId(markerIdVal);
    
              if (change.data['occupied'] == true) {
                markers[markerId] = Marker(
                    markerId: markerId,
                    icon: pointOccupiedIcon,
                    position: LatLng(change.data['location'].latitude,
                        change.data['location'].longitude),
                    infoWindow: InfoWindow(
                        title: change.data['name'],
                        snippet: change.data['type']),
                    onTap: () {
                      findBookings(context, change.data, change.documentID);
                    });
              } else {
                markers[markerId] = Marker(
                    markerId: markerId,
                    icon: pointAvailableIcon,
                    position: LatLng(change.data['location'].latitude,
                        change.data['location'].longitude),
                    infoWindow: InfoWindow(
                        title: change.data['name'],
                        snippet: change.data['type']),
                    onTap: () {
                      findBookings(context, change.data, change.documentID);
                    });
              }
            });