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

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:

yaml
# Before (Google Maps)
dependencies:
  google_maps_flutter: ^2.5.0

# After (MapMetrics)
dependencies:
  mapmetrics: ^1.0.6

Then run:

bash
flutter pub get

Step 2: Update Imports

dart
// 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:

dart
// 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:

  • GoogleMapMapMetricsView
  • GoogleMapControllerMapController
  • LatLng(lat, lng)Position(lng, lat)the argument order swaps (GeoJSON is longitude-first)
  • CameraPosition(target:, zoom:) inside the widget → MapOptions(initCenter:, initZoom:)
  • myLocationEnabled: true → call controller.enableLocation() after onMapCreated, there is no boolean widget flag
  • You must provide initStyle (get a style URL from the MapMetrics Portal)
  • markers/polylines/polygons/circles sets on the widget → layers: [MarkerLayer(...), PolylineLayer(...), PolygonLayer(...), CircleLayer(...)]

Step 4: Get MapMetrics Credentials

  1. Go to portal.mapmetrics.org and create an account
  2. Create an API Key under the "Keys" section
  3. Create a Map Style under the "Styles" section
  4. 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

FeatureGoogle MapsMapMetrics
WidgetGoogleMapMapMetricsView
ControllerGoogleMapControllerMapController
Style/ThememapType: MapType.normalMapOptions(initStyle: '...')
Camera PositionCameraPosition (widget param)MapOptions(initCenter:, initZoom:, initBearing:, initPitch:)
CoordinatesLatLng(lat, lng)Position(lng, lat)order swaps
MarkersSet<Marker> widget paramMarkerLayer(points: [Point(...)]) in layers:, or WidgetLayer(markers: [Marker(...)]) in mapChildren: for tappable/draggable markers
PolylinesSet<Polyline> widget paramPolylineLayer(polylines: [LineString(...)]) in layers:
PolygonsSet<Polygon> widget paramPolygonLayer(polygons: [Polygon(...)]) in layers:
CirclesSet<Circle> widget paramCircleLayer(points: [Point(...)]) in layers:
Animate cameraanimateCamera(CameraUpdate...)controller.animateCamera(center:, zoom:, bearing:, pitch:) — no CameraUpdate factory
Move cameramoveCamera(CameraUpdate...)controller.moveCamera(center:, zoom:, bearing:, pitch:)
User locationmyLocationEnabled: true widget flagawait controller.enableLocation() + controller.trackLocation() called after onMapCreated
Zoom/pan/rotate gesturesindividual *GesturesEnabled flagsMapOptions(gestures: MapGestures(pan:, zoom:, rotate:, pitch:))
API KeyIn AndroidManifest / AppDelegateIn initStyle URL

What Actually Carries Over

  • The overall app structure (StatefulWidget holding a controller reference) is the same shape
  • moveCamera vs animateCamera is 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:

ChangeDetails
No LatLngUse Position(lng, lat) from geotypes — longitude first, arguments swap
No CameraPosition class passed to the widgetCamera setup moves into MapOptions(initCenter:, initZoom:, ...)
No CameraUpdate factoryCall moveCamera()/animateCamera() directly with named parameters
No Marker/Polyline/Polygon/Circle + Set<...> widget paramsUse MarkerLayer/PolylineLayer/PolygonLayer/CircleLayer in the declarative layers: list, or WidgetLayer in mapChildren: for interactive Flutter-widget markers
No InfoWindow/onMarkerTappedBuild tap-to-show-info yourself with WidgetLayer + GestureDetector, or onEvent + queryLayers()
No Google API keyReplace with MapMetrics style URL
Custom stylesUse MapMetrics Portal instead of Google Cloud Console
Map typesUse different style URLs instead of MapType enum
No Android API key in manifestRemove com.google.android.geo.API_KEY from AndroidManifest
No iOS API key in AppDelegateRemove GMSServices.provideAPIKey from AppDelegate
Community dataMapMetrics uses community-contributed map data

Remove Google Maps Config

Android

Remove from android/app/src/main/AndroidManifest.xml:

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:

swift
// Remove this
GMSServices.provideAPIKey("YOUR_GOOGLE_API_KEY")

Complete Migration Example

dart
// 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'),
        ),
      },
    );
  }
}
dart
// 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


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.