# How to Implement Location-Based Services in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 40-55 min
- Compatibility: FlutterFlow Pro+ (code export required for geolocator package)
- Last updated: March 2026

## TL;DR

Build location-based discovery in FlutterFlow by storing each item's location as a Firestore GeoPoint plus a geohash string. When the user searches, get their position with the geolocator package, convert radius to geohash bounds, query Firestore using geohash range filters, then sort results by exact distance using the Haversine formula. Never query all documents and filter on the client — that approach fails at scale.

## Efficient Nearby Search in Firestore for FlutterFlow Apps

Location-based features — find nearby restaurants, connect with people in your area, show local listings — are among the most-requested app features. The naive implementation downloads every document and calculates distance on the client, which works fine for 50 documents but fails catastrophically at 50,000. The correct approach uses geohashing: encode each location's latitude and longitude into a short string that preserves geographic proximity. Firestore can then range-query these strings to return only documents within a bounding box, dramatically reducing both query cost and latency.

## Before you start

- FlutterFlow Pro plan with code export enabled
- Firebase project with Firestore and Google Maps API key configured
- geolocator and geoflutterfire_plus packages added to Custom Dependencies
- Google Maps widget enabled in FlutterFlow (Settings → Integrations → Google Maps)

## Step-by-step guide

### 1. Design the Firestore schema with GeoPoint and geohash

Every document representing a physical location needs two fields stored together. In FlutterFlow's Firestore panel, add to your locations or listings collection: a location field of type LatLng (FlutterFlow's built-in type, which maps to Firestore GeoPoint) and a geohash field of type String. The geohash is a Base32 encoded string like 'u4pruydqqvj' where longer strings represent more precise locations. Store geohash precision 9 (9-character strings) for most use cases — this gives approximately 5-meter accuracy. The geohash is what you query; the GeoPoint is what you use to calculate exact distance after fetching results.

**Expected result:** Your Firestore collection has both a location (LatLng/GeoPoint) field and a geohash (String) field visible in the schema editor.

### 2. Add Custom Dependencies for geolocation and geohash

In FlutterFlow Settings → Custom Code → Dependencies, add three packages: geolocator (^13.0.0) for getting the user's GPS position with proper permission handling, geoflutterfire_plus (^0.0.30) for generating geohash values and creating query bounds, and latlong2 (^0.9.0) for the Haversine distance calculation. These three packages form the complete location services stack. After adding dependencies, create a Custom Action named requestLocationPermission that calls Geolocator.requestPermission() and stores the result. Call this action early in your app flow — on the discovery page's first load — before attempting any location queries.

**Expected result:** Three dependencies appear in your FlutterFlow Dependencies list. A location permission action exists in Custom Code.

### 3. Get the user's current position and store it in App State

Create a Custom Action named getUserLocation. This action calls Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium) with a 10-second timeout. If it succeeds, store the latitude and longitude in App State variables named userLatitude and userLongitude (both Double type). If location permissions are denied, show a Snackbar explaining that the feature requires location access and provide a button to open Settings. Use LocationAccuracy.medium rather than .best — medium accuracy is sufficient for proximity search and uses significantly less battery. Cache the position for 5 minutes using a timestamp App State variable to avoid re-requesting GPS on every page render.

```
// Custom Action: getUserLocation
import 'package:geolocator/geolocator.dart';

Future<Map<String, dynamic>> getUserLocation() async {
  LocationPermission permission = await Geolocator.checkPermission();

  if (permission == LocationPermission.denied) {
    permission = await Geolocator.requestPermission();
    if (permission == LocationPermission.denied) {
      return {'success': false, 'error': 'Location permission denied'};
    }
  }

  if (permission == LocationPermission.deniedForever) {
    return {'success': false, 'error': 'Location permission permanently denied'};
  }

  try {
    final position = await Geolocator.getCurrentPosition(
      desiredAccuracy: LocationAccuracy.medium,
      timeLimit: const Duration(seconds: 10),
    );
    return {
      'success': true,
      'latitude': position.latitude,
      'longitude': position.longitude,
    };
  } catch (e) {
    return {'success': false, 'error': 'Could not get location: $e'};
  }
}
```

**Expected result:** Calling getUserLocation returns the device's current GPS coordinates and stores them in App State. If permission is denied, an error message explains the issue.

### 4. Query nearby documents using geohash bounds

Create a Custom Action named queryNearbyItems. This action generates geohash bounds for the search radius using GeoFirePoint from geoflutterfire_plus, then queries Firestore using a range filter on the geohash field. The bounds are the minimum and maximum geohash strings that fall within your radius. A range query returns all documents whose geohash falls between those bounds — this gives you approximately the right bounding box. After fetching, calculate the exact distance using Geolocator.distanceBetween() and filter out any documents outside the precise radius (geohash bounding boxes are squares, not circles). Sort the results by distance ascending.

```
// Custom Action: queryNearbyItems
import 'package:geoflutterfire_plus/geoflutterfire_plus.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:geolocator/geolocator.dart';

Future<List<Map<String, dynamic>>> queryNearbyItems({
  required double userLat,
  required double userLng,
  required double radiusKm,
}) async {
  // Generate geohash bounds for the search radius
  final center = GeoFirePoint(GeoPoint(userLat, userLng));
  final bounds = GeoFirePoint.queryBounds(
    center: center.geopoint,
    radiusInKm: radiusKm,
  );

  final db = FirebaseFirestore.instance;
  final futures = bounds.map((bound) {
    return db.collection('listings')
        .orderBy('geohash')
        .startAt([bound.startValue])
        .endAt([bound.endValue])
        .limit(50)
        .get();
  });

  final snapshots = await Future.wait(futures);
  final allDocs = snapshots.expand((s) => s.docs).toList();

  // Calculate exact distance and filter/sort
  final results = <Map<String, dynamic>>[];
  for (final doc in allDocs) {
    final data = doc.data();
    final geoPoint = data['location'] as GeoPoint;
    final distanceMeters = Geolocator.distanceBetween(
      userLat, userLng,
      geoPoint.latitude, geoPoint.longitude,
    );
    final distanceKm = distanceMeters / 1000;

    if (distanceKm <= radiusKm) {
      results.add({
        ...data,
        'id': doc.id,
        'distanceKm': distanceKm,
        'distanceLabel': distanceKm < 1
            ? '${(distanceKm * 1000).round()}m away'
            : '${distanceKm.toStringAsFixed(1)}km away',
      });
    }
  }

  results.sort((a, b) =>
      (a['distanceKm'] as double).compareTo(b['distanceKm'] as double));
  return results;
}
```

**Expected result:** Calling queryNearbyItems returns a sorted list of documents within the specified radius, each with a distanceKm and human-readable distanceLabel field.

### 5. Display results on Google Maps with distance-sorted ListView

On your discovery page, add a Google Maps widget and a ListView below it (or use a DraggableScrollableSheet for a bottom-sheet list overlaying the map). Store the query results in a Page State variable named nearbyItems (JSON list). When the page loads, call getUserLocation then queryNearbyItems, and store results in nearbyItems. In the Google Maps widget, add markers for each item using its GeoPoint coordinates. In the ListView, display each item as a card showing the item name, category, distance label, and a thumbnail image. Make list items tappable to navigate to the detail page and animate the map to center on that item's marker.

**Expected result:** The discovery page shows a map with pins for nearby items and a scrollable list below it sorted by distance. Each item card shows the exact distance in meters or kilometers.

## Complete code example

File: `generate_and_save_geohash.dart`

```dart
// Custom Action: saveListingWithGeohash
// Call this when creating or updating a listing with a location
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:geoflutterfire_plus/geoflutterfire_plus.dart';
import 'package:firebase_auth/firebase_auth.dart';

Future<String> saveListingWithGeohash({
  required String title,
  required String description,
  required double latitude,
  required double longitude,
  String? existingId,
}) async {
  final user = FirebaseAuth.instance.currentUser;
  if (user == null) throw Exception('Not authenticated');

  // Generate geohash from coordinates
  final geoPoint = GeoPoint(latitude, longitude);
  final geoFirePoint = GeoFirePoint(geoPoint);
  final geohash = geoFirePoint.geohash;

  final db = FirebaseFirestore.instance;
  final ref = existingId != null
      ? db.collection('listings').doc(existingId)
      : db.collection('listings').doc();

  await ref.set(
    {
      'title': title,
      'description': description,
      'location': geoPoint,       // Stored as Firestore GeoPoint
      'geohash': geohash,         // 9-char string for proximity queries
      'owner_id': user.uid,
      'updated_at': FieldValue.serverTimestamp(),
      if (existingId == null)
        'created_at': FieldValue.serverTimestamp(),
    },
    SetOptions(merge: true),
  );

  return ref.id;
}
```

## Common mistakes

- **Querying all documents from Firestore and filtering by distance on the client** — Downloading all documents works for 100 listings but becomes unusably slow and expensive at 10,000 listings. Every client reads every document on every search, regardless of distance — Firestore bills per read. Fix: Store a geohash string on every location document and use GeoFirePoint.queryBounds() to generate range queries that only fetch documents within the approximate search radius.
- **Storing only a GeoPoint without the geohash string** — Firestore cannot range-query GeoPoints directly. Without a geohash field, there is no way to filter documents by proximity in the database — you are forced to download everything. Fix: When creating or updating any document with a location, generate and store both a GeoPoint (location field) and a geohash string (geohash field) from the same coordinates.
- **Using LocationAccuracy.best for every location request** — GPS best accuracy can take 10-30 seconds to acquire a lock and drains the battery significantly. For proximity search, the difference between 'best' and 'medium' accuracy is rarely more than 50 meters — invisible at search radii of 5-50 km. Fix: Use LocationAccuracy.medium for typical proximity searches. Cache the result for 5 minutes. Only use LocationAccuracy.best for turn-by-turn navigation or precise check-in features.
- **Not creating a Firestore composite index on the geohash field** — Geohash range queries require an ascending index on the geohash field. Without it, Firestore throws an error with a link to create the required index. Fix: Create a Firestore index on the geohash field with ascending order. Either follow the link in the error message or add it in the Firebase console under Firestore → Indexes before going to production.

## Best practices

- Always store both a GeoPoint and a geohash string when saving location data — the GeoPoint is for exact distance calculation, the geohash is for database queries.
- Cache the user's location in App State for 5 minutes to avoid redundant GPS requests on every page render.
- Use LocationAccuracy.medium for proximity searches to balance speed and battery life.
- Run multiple geohash bound queries in parallel with Future.wait() rather than sequentially to minimize total query time.
- Add a Firestore ascending index on the geohash field before going to production to avoid query errors.
- Show a pre-permission explanation dialog before requesting location access — this significantly improves the permission grant rate.
- Implement a fallback for when GPS is unavailable: allow manual address entry that geocodes via Google Maps Geocoding API and uses those coordinates for proximity queries.

## Frequently asked questions

### What is a geohash and why do I need it for Firestore location queries?

A geohash is a Base32 string that encodes latitude and longitude into a format where nearby locations have similar string prefixes. Firestore can range-query strings but cannot do radius queries on GeoPoints. By storing a geohash, you can use Firestore's startAt/endAt operators to retrieve documents within a geographic bounding box.

### Why can I not just query Firestore by latitude range and longitude range?

You can query one range at a time in Firestore. A latitude range query would return all documents with the right latitude but any longitude — a horizontal band across the globe, not a nearby circle. Geohash encodes both dimensions into a single string that can be range-queried to approximate a geographic area.

### How accurate are geohash-based proximity queries?

Geohash queries return a bounding box, not an exact circle. You get all documents within a rectangular area roughly matching your radius, plus a few outside the radius near the corners. This is why you must apply a second exact distance filter using Geolocator.distanceBetween() after fetching the geohash results.

### Does this work with FlutterFlow's free plan?

No. Adding the geolocator and geoflutterfire_plus packages requires custom Dart code via FlutterFlow's Custom Actions feature, which requires code export — a Pro plan feature.

### How do I handle the case where the user denies location permission?

Check the permission result from Geolocator.checkPermission(). If it returns LocationPermission.deniedForever, show a dialog explaining that location access was permanently denied and provide a button that calls Geolocator.openAppSettings() to open the device settings where the user can re-enable it.

### What search radius should I default to?

For urban areas (restaurants, shops), 1-5 km works well. For rural or specialized searches (real estate, events), 20-50 km is more appropriate. Offer a slider and remember the user's last-used radius in App State.

### How do I update a listing's geohash when its location changes?

When updating a listing, regenerate the geohash from the new coordinates using GeoFirePoint(GeoPoint(lat, lng)).geohash and write both the new location GeoPoint and new geohash to Firestore in the same document update. Old geohash values must be overwritten — if only the GeoPoint is updated the geohash remains stale and proximity queries return wrong results.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-implement-location-based-services-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-implement-location-based-services-in-flutterflow
