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:
- Completed the Flutter Setup Guide (includes platform permission setup)
- A MapMetrics API key and style URL from the MapMetrics Portal
Platform Permissions
Android
Add these permissions to android/app/src/main/AndroidManifest.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:
<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:
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:
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:
BearingTrackMode | Description |
|---|---|
BearingTrackMode.none | Camera follows position but doesn't rotate to match heading |
BearingTrackMode.gps | Camera rotates to match the GPS-derived bearing |
BearingTrackMode.compass | Camera 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:
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:
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
- Jump to Locations — Animate the camera to any coordinates
- Markers and Annotations — Show info when tapping markers
- Map Interactions — Handle taps, long presses, and gestures
Note: Location only works on physical devices or emulators with location simulation enabled. It also requires HTTPS in production web builds.