Skip to content

Both copy text to your clipboard — Build with AI copies a setup prompt to paste into Claude Code, Cursor, Codex or Copilot; Copy page as Markdown copies this page to paste into a chat. How it works

Locate the User in Flutter

Show the user's current GPS location on the map. This guide covers enabling the built-in location puck, tracking the user as they move, and requesting location permissions on both Android and iOS.

Prerequisites

Before you begin, ensure you have:

Platform Permissions

Android

Add these permissions to android/app/src/main/AndroidManifest.xml:

xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

iOS

Add these keys to ios/Runner/Info.plist:

xml
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to your location to show it on the map.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs access to your location to show it on the map.</string>

Requesting Permission at Runtime

There's no myLocationEnabled widget flag in the real API — location is controlled imperatively through MapController and PermissionManager, both called after the map has been created. PermissionManager wraps the platform permission dialogs:

dart
final permissionManager = PermissionManager();

final granted = await permissionManager.requestLocationPermissions(
  explanation: 'Show your location on the map.',
);

if (granted) {
  await mapController.enableLocation();
}

PermissionManager also exposes read-only getters: locationPermissionsGranted, runtimePermissionsRequired, and backgroundLocationPermissionGranted.

Basic User Location

Enable the built-in location puck on the map:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class LocateUserScreen extends StatefulWidget {
  @override
  _LocateUserScreenState createState() => _LocateUserScreenState();
}

class _LocateUserScreenState extends State<LocateUserScreen> {
  final _permissionManager = PermissionManager();
  MapController? mapController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('My Location')),
      body: MapMetricsView(
        options: MapOptions(
          initCenter: Position(2.3522, 48.8566), // lng, lat — Paris
          initZoom: 4,
          initStyle:
              'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
        ),
        onMapCreated: (MapController controller) {
          mapController = controller;
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _goToMyLocation,
        child: Icon(Icons.my_location),
      ),
    );
  }

  Future<void> _goToMyLocation() async {
    final granted = await _permissionManager.requestLocationPermissions(
      explanation: 'Show your location on the map.',
    );
    if (!granted || mapController == null) return;

    await mapController!.enableLocation();
    await mapController!.trackLocation(trackBearing: BearingTrackMode.gps);
  }
}

enableLocation() shows the blue puck. trackLocation() additionally moves (and optionally rotates) the camera to follow the user; pass trackBearing: BearingTrackMode.compass to rotate the camera with the device compass instead of GPS heading, or BearingTrackMode.none to follow position only.

Location Tracking Modes

The real SDK expresses tracking as a BearingTrackMode passed to trackLocation(), not a MyLocationTrackingMode enum on the widget:

BearingTrackModeDescription
BearingTrackMode.noneCamera follows position but doesn't rotate to match heading
BearingTrackMode.gpsCamera rotates to match the GPS-derived bearing
BearingTrackMode.compassCamera rotates to match the device compass heading

To stop following the user but keep the puck visible, call trackLocation(trackLocation: false). To hide the puck entirely, call controller.showUserLocationPuck(show: false).

There is no MyLocationRenderMode (NORMAL / COMPASS / GPS) in the real API — the puck's appearance is fixed; only whether it's shown (showUserLocationPuck) and whether the camera tracks bearing (trackLocation's trackBearing) are configurable.

Complete Example: Locate Me Button

A complete example with a "Locate Me" button that requests permission and enables tracking:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class LocateMeScreen extends StatefulWidget {
  @override
  _LocateMeScreenState createState() => _LocateMeScreenState();
}

class _LocateMeScreenState extends State<LocateMeScreen> {
  final _permissionManager = PermissionManager();
  MapController? mapController;
  String statusMessage = 'Tap the button to find your location';
  bool isLocating = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Locate Me')),
      body: Column(
        children: [
          // Status bar
          Container(
            width: double.infinity,
            padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10),
            color: Colors.grey[100],
            child: Row(
              children: [
                if (isLocating)
                  Padding(
                    padding: EdgeInsets.only(right: 8),
                    child: SizedBox(
                      width: 16,
                      height: 16,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    ),
                  ),
                Expanded(child: Text(statusMessage)),
              ],
            ),
          ),
          // Map
          Expanded(
            child: MapMetricsView(
              options: MapOptions(
                initCenter: Position(0, 0),
                initZoom: 2,
                initStyle:
                    'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
              ),
              onMapCreated: (MapController controller) {
                mapController = controller;
              },
              onEvent: (event) {
                if (event case MapEventMoveCamera()) {
                  if (isLocating) {
                    setState(() {
                      statusMessage =
                          'Lat: ${event.camera.center.lat.toStringAsFixed(5)}, '
                          'Lng: ${event.camera.center.lng.toStringAsFixed(5)}';
                    });
                  }
                }
              },
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: _locateMe,
        icon: Icon(Icons.my_location),
        label: Text('Locate Me'),
      ),
    );
  }

  Future<void> _locateMe() async {
    setState(() {
      isLocating = true;
      statusMessage = 'Getting your location...';
    });

    try {
      final granted = await _permissionManager.requestLocationPermissions(
        explanation: 'Show your location on the map.',
      );
      if (!granted) {
        setState(() {
          isLocating = false;
          statusMessage = 'Location permission was not granted.';
        });
        return;
      }

      await mapController?.enableLocation();
      await mapController?.trackLocation(trackBearing: BearingTrackMode.gps);
    } catch (error) {
      setState(() {
        isLocating = false;
        statusMessage = 'Could not get location: $error';
      });
    }
  }
}

There is no onUserLocationUpdated callback — the SDK doesn't stream raw GPS coordinates into Dart. enableLocation() and trackLocation() show and follow the position natively; the example above reads the coordinates back off MapEventMoveCamera while tracking is active, since the tracked camera's center converges on the user's position. If your app needs the user's raw coordinates independent of the map camera (e.g. to send to a server), use a separate geolocation package such as geolocator alongside MapMetrics.

Handling Permission Errors

Always handle the case where the user denies location permission. requestLocationPermissions returns false rather than throwing, but native calls like enableLocation() can still throw if location services are unavailable — wrap them in try/catch:

dart
Future<void> _enableLocationSafely(MapController controller) async {
  final permissionManager = PermissionManager();
  try {
    final granted = await permissionManager.requestLocationPermissions(
      explanation: 'Show your location on the map.',
    );
    if (!granted) {
      _showPermissionDialog();
      return;
    }
    await controller.enableLocation();
  } catch (error) {
    _showPermissionDialog();
  }
}

void _showPermissionDialog() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: Text('Location Permission Required'),
      content: Text(
        'Please enable location permissions in your device settings '
        'to use this feature.',
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: Text('OK'),
        ),
      ],
    ),
  );
}

Next Steps


Note: Location only works on physical devices or emulators with location simulation enabled. It also requires HTTPS in production web builds.