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.
I have created a form to better understand everyone’s needs. You can also request custom Flutter Flow development and/or Flutter Flow training.
https://coffeebytez.com/contact-me/
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