Lorcan Bowens
Lorcan Bowens

Reputation: 61

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

Upvotes: 1

Views: 3424

Answers (1)

Lorcan Bowens
Lorcan Bowens

Reputation: 61

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);
                });
          }
        });

Upvotes: 5

Related Questions