Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Build a Custom Interactive Map Feature in FlutterFlow

Build an interactive map feature using FlutterFlowGoogleMap with markers loaded from a Firestore locations collection. Add address search via the Google Geocoding API, filter markers by category with ChoiceChips, implement marker clustering with a Custom Widget using google_maps_cluster_manager for large datasets, and show location details in a Bottom Sheet when a marker is tapped. Geospatial queries load only markers within the visible map bounds to keep performance smooth.

What you'll learn

  • How to display Firestore-driven markers on FlutterFlowGoogleMap
  • How to add address search with geocoding and map animation
  • How to filter markers by category and cluster nearby markers at low zoom levels
  • How to show a detail Bottom Sheet with directions when a marker is tapped
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read25-30 minFlutterFlow Free+March 2026RapidDev Engineering Team
TL;DR

Build an interactive map feature using FlutterFlowGoogleMap with markers loaded from a Firestore locations collection. Add address search via the Google Geocoding API, filter markers by category with ChoiceChips, implement marker clustering with a Custom Widget using google_maps_cluster_manager for large datasets, and show location details in a Bottom Sheet when a marker is tapped. Geospatial queries load only markers within the visible map bounds to keep performance smooth.

Building an Interactive Map with Search and Clustering in FlutterFlow

Interactive maps are essential for location-based apps like store locators, property finders, and city guides. This tutorial goes beyond basic map setup to add real-world features: search by address, category filtering, marker clustering for large datasets, and tappable markers that open a detail sheet with a directions button. It is designed for founders building any location-aware application.

Prerequisites

  • A FlutterFlow project with Google Maps enabled in Settings
  • Google Maps API key with Maps SDK, Geocoding API, and Directions API enabled
  • Firestore database with a locations collection containing lat/lng coordinates
  • Basic familiarity with FlutterFlow Backend Queries and Custom Widgets

Step-by-step guide

1

Set up the locations collection and load markers on the map

Create a Firestore locations collection with fields: name (String), lat (Double), lng (Double), category (String), description (String), imageUrl (String), and address (String). Add several sample locations. On your Map page, add a FlutterFlowGoogleMap widget. Create a Backend Query that fetches all locations (or filter by visible bounds for large datasets). For each location document, create a marker using the lat and lng fields. Set the marker title to the location name. Bind the list of markers to the map's markers property.

Expected result: The map displays markers for all locations stored in Firestore. Each marker shows the location name when tapped.

2

Add address search with geocoding and map animation

Add a TextField at the top of the page with a search icon and placeholder text 'Search address or place'. Below the TextField, add a search Button. On tap, call the Google Geocoding API via an API Call action: GET https://maps.googleapis.com/maps/api/geocode/json?address={searchText}&key={API_KEY}. Parse the response to extract results[0].geometry.location.lat and lng. Animate the map to the geocoded coordinates using the Animate Map action with a zoom level of 15. Store the search result in Page State so you can display a marker at the searched location.

Expected result: Users type an address, tap search, and the map smoothly animates to the geocoded location with a marker at the searched position.

3

Filter markers by category using ChoiceChips

Add ChoiceChips below the search bar with category options matching your locations data (e.g., Restaurant, Shopping, Park, Hotel). Bind the selected chip to Page State selectedCategory. Modify your Backend Query to add a where clause filtering by category when selectedCategory is not null. Add an 'All' chip option that clears the filter and shows every marker. When the category changes, the Backend Query re-fires and the map updates with only the matching markers.

Expected result: Selecting a category chip filters the map to show only matching markers. The All chip restores all markers.

4

Implement marker clustering for large datasets with a Custom Widget

For maps with hundreds of markers, create a Custom Widget using the google_maps_cluster_manager package. Add google_maps_cluster_manager: ^0.4.0 and google_maps_flutter: ^2.5.0 to Pubspec Dependencies. In the Custom Widget, initialize a ClusterManager with the list of locations as ClusterItem objects. The ClusterManager groups nearby markers into cluster circles at low zoom levels that break apart as the user zooms in. Pass the locations list as a Component Parameter (JSON list). Configure the cluster appearance to show a circle with the count number inside.

clustered_map_widget.dart
1// Custom Widget: ClusteredMap
2import 'package:flutter/material.dart';
3import 'package:google_maps_flutter/google_maps_flutter.dart';
4import 'package:google_maps_cluster_manager/google_maps_cluster_manager.dart';
5
6class Place with ClusterItem {
7 final String name;
8 final LatLng latLng;
9 Place({required this.name, required this.latLng});
10 @override
11 LatLng get location => latLng;
12}
13
14class ClusteredMapWidget extends StatefulWidget {
15 final double width;
16 final double height;
17 final List<dynamic> locations;
18 final Future Function(String name, double lat, double lng)? onMarkerTap;
19
20 const ClusteredMapWidget({
21 Key? key,
22 required this.width,
23 required this.height,
24 required this.locations,
25 this.onMarkerTap,
26 }) : super(key: key);
27
28 @override
29 State<ClusteredMapWidget> createState() => _ClusteredMapWidgetState();
30}
31
32class _ClusteredMapWidgetState extends State<ClusteredMapWidget> {
33 late ClusterManager<Place> _clusterManager;
34 Set<Marker> _markers = {};
35 GoogleMapController? _controller;
36
37 @override
38 void initState() {
39 super.initState();
40 final places = widget.locations.map((loc) => Place(
41 name: loc['name'] ?? '',
42 latLng: LatLng(loc['lat'] ?? 0, loc['lng'] ?? 0),
43 )).toList();
44 _clusterManager = ClusterManager<Place>(
45 places,
46 _updateMarkers,
47 markerBuilder: _markerBuilder,
48 );
49 }
50
51 void _updateMarkers(Set<Marker> markers) {
52 setState(() => _markers = markers);
53 }
54
55 Future<Marker> _markerBuilder(Cluster<Place> cluster) async {
56 return Marker(
57 markerId: MarkerId(cluster.getId()),
58 position: cluster.location,
59 onTap: () {
60 if (!cluster.isMultiple) {
61 widget.onMarkerTap?.call(
62 cluster.items.first.name,
63 cluster.location.latitude,
64 cluster.location.longitude,
65 );
66 }
67 },
68 icon: cluster.isMultiple
69 ? await _getClusterBitmap(cluster.count)
70 : BitmapDescriptor.defaultMarker,
71 );
72 }
73
74 Future<BitmapDescriptor> _getClusterBitmap(int count) async {
75 return BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueAzure);
76 }
77
78 @override
79 Widget build(BuildContext context) {
80 return SizedBox(
81 width: widget.width,
82 height: widget.height,
83 child: GoogleMap(
84 initialCameraPosition: const CameraPosition(
85 target: LatLng(37.7749, -122.4194),
86 zoom: 12,
87 ),
88 markers: _markers,
89 onMapCreated: (controller) {
90 _controller = controller;
91 _clusterManager.setMapId(controller.mapId);
92 },
93 onCameraMove: _clusterManager.onCameraMove,
94 onCameraIdle: _clusterManager.updateMap,
95 ),
96 );
97 }
98}

Expected result: At low zoom levels, nearby markers merge into cluster circles showing a count. Zooming in splits them into individual markers.

5

Show a detail Bottom Sheet on marker tap with directions button

When a marker is tapped (via the onMarkerTap Action Parameter callback from the Custom Widget, or the built-in FlutterFlowGoogleMap marker tap action), trigger a Show Bottom Sheet action. The Bottom Sheet contains a Component with: location Image, name Text, description Text, address Text, and a 'Get Directions' Button. The Get Directions button uses Launch URL with the value 'https://www.google.com/maps/dir/?api=1&destination={lat},{lng}' to open Google Maps with turn-by-turn directions from the user's current location.

Expected result: Tapping a marker slides up a Bottom Sheet showing the location image, name, description, and a Get Directions button that opens Google Maps navigation.

Complete working example

clustered_map_widget.dart
1// Custom Widget: ClusteredMap with Marker Tap Callback
2import 'package:flutter/material.dart';
3import 'package:google_maps_flutter/google_maps_flutter.dart';
4import 'package:google_maps_cluster_manager/google_maps_cluster_manager.dart';
5
6class Place with ClusterItem {
7 final String id;
8 final String name;
9 final LatLng latLng;
10 Place({required this.id, required this.name, required this.latLng});
11 @override
12 LatLng get location => latLng;
13}
14
15class ClusteredMapWidget extends StatefulWidget {
16 final double width;
17 final double height;
18 final List<dynamic> locations;
19 final Future Function(String id, String name, double lat, double lng)? onMarkerTap;
20
21 const ClusteredMapWidget({
22 Key? key,
23 required this.width,
24 required this.height,
25 required this.locations,
26 this.onMarkerTap,
27 }) : super(key: key);
28
29 @override
30 State<ClusteredMapWidget> createState() => _ClusteredMapWidgetState();
31}
32
33class _ClusteredMapWidgetState extends State<ClusteredMapWidget> {
34 late ClusterManager<Place> _manager;
35 Set<Marker> _markers = {};
36 GoogleMapController? _mapCtrl;
37
38 @override
39 void initState() {
40 super.initState();
41 final places = widget.locations.map((l) => Place(
42 id: l['id'] ?? '',
43 name: l['name'] ?? '',
44 latLng: LatLng((l['lat'] ?? 0).toDouble(), (l['lng'] ?? 0).toDouble()),
45 )).toList();
46
47 _manager = ClusterManager<Place>(
48 places,
49 (markers) => setState(() => _markers = markers),
50 markerBuilder: _buildMarker,
51 );
52 }
53
54 Future<Marker> _buildMarker(Cluster<Place> cluster) async {
55 return Marker(
56 markerId: MarkerId(cluster.getId()),
57 position: cluster.location,
58 onTap: () {
59 if (!cluster.isMultiple) {
60 final p = cluster.items.first;
61 widget.onMarkerTap?.call(p.id, p.name, p.latLng.latitude, p.latLng.longitude);
62 }
63 },
64 icon: cluster.isMultiple
65 ? BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueAzure)
66 : BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed),
67 );
68 }
69
70 void animateTo(double lat, double lng, double zoom) {
71 _mapCtrl?.animateCamera(CameraUpdate.newLatLngZoom(LatLng(lat, lng), zoom));
72 }
73
74 @override
75 Widget build(BuildContext context) {
76 return SizedBox(
77 width: widget.width,
78 height: widget.height,
79 child: GoogleMap(
80 initialCameraPosition: const CameraPosition(
81 target: LatLng(37.7749, -122.4194),
82 zoom: 11,
83 ),
84 markers: _markers,
85 onMapCreated: (ctrl) {
86 _mapCtrl = ctrl;
87 _manager.setMapId(ctrl.mapId);
88 },
89 onCameraMove: _manager.onCameraMove,
90 onCameraIdle: _manager.updateMap,
91 myLocationEnabled: true,
92 myLocationButtonEnabled: true,
93 zoomControlsEnabled: false,
94 ),
95 );
96 }
97}

Common mistakes when building a Custom Interactive Map Feature in FlutterFlow

Why it's a problem: Loading all location markers at once for a dataset with thousands of entries

How to avoid: Use marker clustering to group nearby markers visually, and add geospatial filtering to only load markers within the visible map bounds.

Why it's a problem: Hardcoding the Google Maps API key in client-side code

How to avoid: Restrict your API key in the Google Cloud Console to your app's bundle ID (mobile) and domain (web). Use separate keys for each platform.

Why it's a problem: Not handling the case when geocoding returns zero results

How to avoid: Check that the geocoding response status is 'OK' and results array is not empty before accessing coordinates. Show a 'No results found' SnackBar on failure.

Best practices

  • Restrict your Google Maps API key by platform (Android, iOS, web) in the Cloud Console
  • Use marker clustering when you have more than 50 markers to keep the map readable
  • Filter locations by visible map bounds using geospatial queries to reduce Firestore reads
  • Add a 'My Location' button so users can quickly center the map on their current position
  • Cache geocoding results in Page State to avoid re-querying the same address
  • Use distinct marker colors or custom icons per category for visual clarity
  • Pre-calculate and store the directions URL template on each location document for faster Bottom Sheet loading

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I need to build an interactive map feature in FlutterFlow with markers from Firestore, address search using Google Geocoding API, category filtering with ChoiceChips, and marker clustering for large datasets. Show me the Custom Widget code for the clustered map and the FlutterFlow action flow for search.

FlutterFlow Prompt

Create a map page with Google Maps showing markers from a Firestore locations collection. Add a search bar at the top that geocodes addresses and animates the map. Add ChoiceChips for category filtering. Show a Bottom Sheet with location details and a Get Directions button when a marker is tapped.

Frequently asked questions

Can I use Mapbox instead of Google Maps in FlutterFlow?

FlutterFlow's built-in map widget uses Google Maps. For Mapbox, you would need to create a Custom Widget using the flutter_map or mapbox_maps_flutter package and handle markers and interactions manually.

How do I show the user's current location on the map?

Enable myLocationEnabled on the Google Map widget. In FlutterFlow, this is a toggle in the map properties. Make sure to request location permissions in your app settings.

How many markers can FlutterFlowGoogleMap handle before performance degrades?

Performance starts degrading around 100-200 individual markers on mobile. Use marker clustering to handle larger datasets efficiently. With clustering, you can support thousands of locations.

Can I draw routes between two markers on the map?

Yes. Use the Google Directions API to get route polyline data, then decode the polyline string and overlay it on the map using a Custom Widget that draws Polyline objects on GoogleMap.

How do I make markers update in real time as locations change?

Set Single Time Query to OFF on your locations Backend Query. When a location document is updated in Firestore, the query re-fires and the map markers update automatically.

Can RapidDev build a custom location-based app with advanced map features?

Yes. RapidDev can implement store locators, delivery tracking maps, geofencing, route optimization, and custom map styling tailored to your brand and use case.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.