Flutter Flow: Use Supabase to show custom markers on Google Maps

We previously discussed showing custom markers in Google Maps. We were adding data to the app state manually in that approach.
Flutter Flow: Adding Custom Markers for Google Maps

This might not be the most scalable solution. I’m going to discuss a better approach on how to read the data from Supabase to show custom markers in Google Maps.

If you are looking for the Firebase version, follow this article:
Flutter Flow: Google Map Custom Markers with Firebase

Update : The Supabase Community posted this article on their Twitter/X account. To celebrate I have updated this article with improvements to the code. Most notably adding the ability to load network images as icons.

Custom development

I get a lot of requests for custom tweaks of Google Maps with Flutter.

To better understand everyone’s needs, I have created a survey to capture these requests.

https://coffeebytez.substack.com/survey/639443

I will focus on the most highly requested items first, which will be available on my substack. I will also post exclusive in-depth tutorials there.

Please subscribe to my substack here:

https://coffeebytez.substack.com/?r=gdmkm&utm_campaign=pub-share-checklist

If you have urgent specific requests, please leave your contact information in the survey.Setting up Supabase

You can finish the setup of Supabase and Flutter Flow by following the official Flutter Flow docs
Supabase Setup

In Supabase, go to the Table Editor menu tab. Let’s create a new table called place.

Give it the same columns as our custom data type from the last example, GoogleMapDataStruct. Notice instead of iconPath, we are using icon_name.

Using a type of varchar may be appropriate depending on your use case. Refer to the Reddit post below
https://www.reddit.com/r/PostgreSQL/comments/c38hgy/data_type_when_do_you_choose_varchar_over_text/


If you don’t have another means of adding data yet, you can add it manually by clicking Insert > Insert Row

Also, make sure to set a read policy on the place table. You can do this by going to the Authentication menu tab on the left.

For this example, I’m allowing read access to everyone.

Click New Policy > Get started quickly

You can use the first template given

Querying Supabase from our Custom Widget

Note: Flutter Flow now has the option to pass a list of Supabase Rows as a parameter and allows you have callbacks as parameters. I have submitted the new approach to the Flutter Flow Marketplace. The new approach is in the article below.
Flutter Flow: Google Maps Custom Marker Actions

We could query and then set it to app state and add a listener to FFAppState since it extends ChangeNotifier. The problem with this is that it will listen to every state change, even unrelated state changes.

Another approach is to directly query the table in Supabase. I chose to go with this approach for this example.

Let’s create another custom widget, this time naming it GoogleMapsSupabase.

The main changes I’ve made here are instead of creating the BitmapDescriptor in initState, I am first loading the data from Supabase from the place table.

Notice we are still using the custom data type from the previous example. We are mapping the results from Supabase to the custom data type GoogleMapDataStruct. We have also changed _createMarkers to use the **_fetchedMapData **variable.

Next, create the same parameters we used in the last example except for the mapData parameter. Notice we have also removed that from the example.

Note: Make sure to keep your images small

// Automatic FlutterFlow imports
import '/backend/schema/structs/index.dart';
import '/backend/supabase/supabase.dart';
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/widgets/index.dart'; // Imports other custom widgets
import '/custom_code/actions/index.dart'; // Imports custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom widget code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!

import 'index.dart'; // Imports other custom widgets

import 'package:google_maps_flutter/google_maps_flutter.dart'
    as google_maps_flutter;
import '/flutter_flow/lat_lng.dart' as latlng;
import 'dart:async';
export 'dart:async' show Completer;
export 'package:google_maps_flutter/google_maps_flutter.dart' hide LatLng;
export '/flutter_flow/lat_lng.dart' show LatLng;
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
import 'dart:ui';

// Set your widget name, define your parameter, and then add the
// boilerplate code using the green button on the right!

class GoogleMapsSupabase extends StatefulWidget {
  const GoogleMapsSupabase({
    Key? key,
    this.width,
    this.height,
    this.allowZoom = true,
    this.showZoomControls = true,
    this.showLocation = true,
    this.showCompass = false,
    this.showMapToolbar = false,
    this.showTraffic = false,
    this.centerLat = 0.0,
    this.centerLng = 0.0,
  }) : super(key: key);

  final double? width;
  final double? height;
  final bool allowZoom;
  final bool showZoomControls;
  final bool showLocation;
  final bool showCompass;
  final bool showMapToolbar;
  final bool showTraffic;
  final double centerLat;
  final double centerLng;

  @override
  _GoogleMapsSupabaseState createState() => _GoogleMapsSupabaseState();
}

class _GoogleMapsSupabaseState extends State<GoogleMapsSupabase> {
  Completer<google_maps_flutter.GoogleMapController> _controller = Completer();
  Map<String, google_maps_flutter.BitmapDescriptor> _customIcons = {};
  Set<google_maps_flutter.Marker> _markers = {};
  List<GoogleMapDataStruct> _fetchedMapData = [];

  late google_maps_flutter.LatLng _center;

  @override
  void initState() {
    super.initState();

    _center = google_maps_flutter.LatLng(widget.centerLat, widget.centerLng);

    _loadData();
  }

  void _loadData() async {
    final supabase = SupaFlow.client;
    final response = await supabase.from('place').select().limit(10).execute();
    final data = response.data;

    if (data != null) {
      setState(() {
        _fetchedMapData = List<GoogleMapDataStruct>.from(
          data.map(
            (item) => GoogleMapDataStruct(
              latLng: latlng.LatLng(item['lat'], item['lng']),
              iconPath: item['icon_name'],
              title: item['title'],
              description: item['description'],
            ),
          ),
        );
      });

      _loadMarkerIcons();
    }
  }

  Future<void> _loadMarkerIcons() async {
    Set<String> uniqueIconPaths =
        _fetchedMapData.map((data) => data.iconPath).toSet() ??
            {}; // Extract unique icon paths

    for (String path in uniqueIconPaths) {
      if (path.isNotEmpty) {
        if (path.contains("https")) {
          Uint8List? imageData = await loadNetworkImage(path);
          if (imageData != null) {
            google_maps_flutter.BitmapDescriptor descriptor =
                google_maps_flutter.BitmapDescriptor.fromBytes(imageData);
            _customIcons[path] = descriptor;
          }
        } else {
          google_maps_flutter.BitmapDescriptor descriptor =
              await google_maps_flutter.BitmapDescriptor.fromAssetImage(
            const ImageConfiguration(devicePixelRatio: 2.5),
            "assets/images/$path",
          );
          _customIcons[path] = descriptor;
        }
      }
    }

    _updateMarkers(); // Update markers once icons are loaded
  }

  Future<Uint8List?> loadNetworkImage(String path) async {
    final completer = Completer<ImageInfo>();
    var image = NetworkImage(path);
    image.resolve(const ImageConfiguration()).addListener(ImageStreamListener(
        (ImageInfo info, bool _) => completer.complete(info)));
    final imageInfo = await completer.future;
    final byteData =
        await imageInfo.image.toByteData(format: ImageByteFormat.png);
    return byteData?.buffer.asUint8List();
  }

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

  .....

  Set<google_maps_flutter.Marker> _createMarkers() {
    var tmp = <google_maps_flutter.Marker>{};

    _fetchedMapData.forEach((mapData) {
      // Directly use the latlng.LatLng object
      final latlng.LatLng coordinates = mapData.latLng as latlng.LatLng;

      // Convert to google_maps_flutter.LatLng
      final google_maps_flutter.LatLng googleMapsLatLng =
          google_maps_flutter.LatLng(
              coordinates.latitude, coordinates.longitude);

      google_maps_flutter.BitmapDescriptor icon =
          _customIcons[mapData.iconPath] ??
              google_maps_flutter.BitmapDescriptor.defaultMarker;

      // Create and add the marker
      final google_maps_flutter.Marker marker = google_maps_flutter.Marker(
        markerId: google_maps_flutter.MarkerId(mapData.title),
        position: googleMapsLatLng,
        icon: icon,
        infoWindow: google_maps_flutter.InfoWindow(
            title: mapData.title, snippet: mapData.description),
      );

      tmp.add(marker);
    });

    return tmp;
  }
}

Get the full code here:
Flutter Flow: Use Supabase to show custom markers on Google Maps

Check out my other Flutter Flow and Supabase articles
Migrating away from Firebase, to Netlify and Supabase
FlutterFlow Stepper Form with Firebase Firestore
Flutter Flow: Get Value From Multiple-Choice Choice Chips
Flutter Flow: Carousel Menu
Flutter Flow: Migrating the Carousel Menu to use data types
Flutter Flow: Adding Custom Markers for Google Maps
Flutter Flow: Use Supabase to show custom markers on Google Maps

Recommended Posts

No comment yet, add your voice below!


Add a Comment

Your email address will not be published. Required fields are marked *