Migrate from Google Maps to MapMetrics in Flutter
This guide helps you switch your existing Flutter app from google_maps_flutter to MapMetrics. The two packages are shaped quite differently — Google Maps is imperative-widget-plus-Set<Marker>, MapMetrics is a declarative layers: list plus an optional imperative StyleController — so expect to restructure map-content code, not just rename classes.
Step 1: Update Dependencies
Replace the Google Maps dependency in your pubspec.yaml:
# Before (Google Maps)
dependencies:
google_maps_flutter: ^2.5.0
# After (MapMetrics)
dependencies:
mapmetrics: ^1.0.6Then run:
flutter pub getStep 2: Update Imports
// Before (Google Maps)
import 'package:google_maps_flutter/google_maps_flutter.dart';
// After (MapMetrics)
import 'package:mapmetrics/mapmetrics.dart';Step 3: Update the Map Widget
The widget shape changes more than the names suggest — camera setup moves into a MapOptions object, and map content (markers/lines/polygons) moves into a declarative layers: list instead of Set<Marker>/Set<Polyline>/etc:
// Before (Google Maps)
GoogleMap(
onMapCreated: (GoogleMapController controller) {
_controller = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(48.8566, 2.3522),
zoom: 13.0,
),
markers: _markers,
polylines: _polylines,
polygons: _polygons,
circles: _circles,
myLocationEnabled: true,
myLocationButtonEnabled: true,
)
// After (MapMetrics)
MapMetricsView(
options: MapOptions(
initCenter: Position(2.3522, 48.8566), // lng, lat — Paris
initZoom: 13,
initStyle:
'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
),
onMapCreated: (MapController controller) {
_controller = controller;
},
layers: [
MarkerLayer(points: _points, textField: 'Name'),
PolylineLayer(polylines: _lines, color: Colors.blue, width: 3),
PolygonLayer(polygons: _polygons, color: Colors.red.withValues(alpha: 0.3)),
CircleLayer(points: _circlePoints, color: Colors.blue, radius: 20),
],
)
// Enable the location puck after the map is created:
// await _controller.enableLocation();Key differences:
GoogleMap→MapMetricsViewGoogleMapController→MapControllerLatLng(lat, lng)→Position(lng, lat)— the argument order swaps (GeoJSON is longitude-first)CameraPosition(target:, zoom:)inside the widget →MapOptions(initCenter:, initZoom:)myLocationEnabled: true→ callcontroller.enableLocation()afteronMapCreated, there is no boolean widget flag- You must provide
initStyle(get a style URL from the MapMetrics Portal) markers/polylines/polygons/circlessets on the widget →layers: [MarkerLayer(...), PolylineLayer(...), PolygonLayer(...), CircleLayer(...)]
Step 4: Get MapMetrics Credentials
- Go to portal.mapmetrics.org and create an account
- Create an API Key under the "Keys" section
- Create a Map Style under the "Styles" section
- Copy your style URL (format:
https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY)
API Comparison Table
| Feature | Google Maps | MapMetrics |
|---|---|---|
| Widget | GoogleMap | MapMetricsView |
| Controller | GoogleMapController | MapController |
| Style/Theme | mapType: MapType.normal | MapOptions(initStyle: '...') |
| Camera Position | CameraPosition (widget param) | MapOptions(initCenter:, initZoom:, initBearing:, initPitch:) |
| Coordinates | LatLng(lat, lng) | Position(lng, lat) — order swaps |
| Markers | Set<Marker> widget param | MarkerLayer(points: [Point(...)]) in layers:, or WidgetLayer(markers: [Marker(...)]) in mapChildren: for tappable/draggable markers |
| Polylines | Set<Polyline> widget param | PolylineLayer(polylines: [LineString(...)]) in layers: |
| Polygons | Set<Polygon> widget param | PolygonLayer(polygons: [Polygon(...)]) in layers: |
| Circles | Set<Circle> widget param | CircleLayer(points: [Point(...)]) in layers: |
| Animate camera | animateCamera(CameraUpdate...) | controller.animateCamera(center:, zoom:, bearing:, pitch:) — no CameraUpdate factory |
| Move camera | moveCamera(CameraUpdate...) | controller.moveCamera(center:, zoom:, bearing:, pitch:) |
| User location | myLocationEnabled: true widget flag | await controller.enableLocation() + controller.trackLocation() called after onMapCreated |
| Zoom/pan/rotate gestures | individual *GesturesEnabled flags | MapOptions(gestures: MapGestures(pan:, zoom:, rotate:, pitch:)) |
| API Key | In AndroidManifest / AppDelegate | In initStyle URL |
What Actually Carries Over
- The overall app structure (
StatefulWidgetholding a controller reference) is the same shape moveCameravsanimateCamerais the same instant-vs-animated distinction- Style URLs still carry your API key, same as Google's manifest-based key did conceptually
What Changes
Almost everything at the API surface is different — this is not a find-and-replace migration:
| Change | Details |
|---|---|
No LatLng | Use Position(lng, lat) from geotypes — longitude first, arguments swap |
No CameraPosition class passed to the widget | Camera setup moves into MapOptions(initCenter:, initZoom:, ...) |
No CameraUpdate factory | Call moveCamera()/animateCamera() directly with named parameters |
No Marker/Polyline/Polygon/Circle + Set<...> widget params | Use MarkerLayer/PolylineLayer/PolygonLayer/CircleLayer in the declarative layers: list, or WidgetLayer in mapChildren: for interactive Flutter-widget markers |
No InfoWindow/onMarkerTapped | Build tap-to-show-info yourself with WidgetLayer + GestureDetector, or onEvent + queryLayers() |
| No Google API key | Replace with MapMetrics style URL |
| Custom styles | Use MapMetrics Portal instead of Google Cloud Console |
| Map types | Use different style URLs instead of MapType enum |
| No Android API key in manifest | Remove com.google.android.geo.API_KEY from AndroidManifest |
| No iOS API key in AppDelegate | Remove GMSServices.provideAPIKey from AppDelegate |
| Community data | MapMetrics uses community-contributed map data |
Remove Google Maps Config
Android
Remove from android/app/src/main/AndroidManifest.xml:
<!-- Remove this -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_GOOGLE_API_KEY"/>iOS
Remove from ios/Runner/AppDelegate.swift:
// Remove this
GMSServices.provideAPIKey("YOUR_GOOGLE_API_KEY")Complete Migration Example
// Before: Google Maps
import 'package:google_maps_flutter/google_maps_flutter.dart';
class MyMapScreen extends StatefulWidget {
@override
_MyMapScreenState createState() => _MyMapScreenState();
}
class _MyMapScreenState extends State<MyMapScreen> {
GoogleMapController? _controller;
@override
Widget build(BuildContext context) {
return GoogleMap(
onMapCreated: (controller) => _controller = controller,
initialCameraPosition: CameraPosition(
target: LatLng(48.8566, 2.3522),
zoom: 13,
),
markers: {
Marker(
markerId: MarkerId('paris'),
position: LatLng(48.8566, 2.3522),
infoWindow: InfoWindow(title: 'Paris'),
),
},
);
}
}// After: MapMetrics (widget shape and content model both change)
import 'package:mapmetrics/mapmetrics.dart';
class MyMapScreen extends StatefulWidget {
@override
_MyMapScreenState createState() => _MyMapScreenState();
}
class _MyMapScreenState extends State<MyMapScreen> {
MapController? _controller;
@override
Widget build(BuildContext context) {
return MapMetricsView(
options: MapOptions(
initCenter: Position(2.3522, 48.8566), // lng, lat — Paris
initZoom: 13,
initStyle:
'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
),
onMapCreated: (controller) => _controller = controller,
layers: [
MarkerLayer(
points: [Point(coordinates: Position(2.3522, 48.8566))],
textField: 'Paris',
textAllowOverlap: true,
),
],
);
}
}There is no InfoWindow in the real API — MarkerLayer draws a label (textField) next to the point, it does not pop up a tappable card. For a Google-Maps-style "tap the marker, see a card" interaction, use WidgetLayer in mapChildren: with a GestureDetector-wrapped Marker child instead — see Markers and Annotations.
Why Switch to MapMetrics?
- No usage fees — No per-load or per-request charges
- Custom styles — Full control over map appearance via the Portal
- Community data — Maps updated with community contributions
- No vendor lock-in — Open-source based solution
- Simple setup — No platform-specific API key configuration
Next Steps
- Flutter Setup Guide — Full setup walkthrough
- Basic Map — Get your first map running
- Custom Styling — Create custom map styles
Tip: The migration is mostly a find-and-replace operation. The biggest change is adding the styleUrl parameter and removing Google-specific API key configuration.