Skip to content

Migrate from google_maps_flutter

google_maps_flutter shows Google's maps with a Google Maps Platform API key. maplibre shows vector or raster tiles from any provider or your own server, styled with the MapLibre style spec, and also runs on macOS, Windows and Linux. The widget and its markers, polylines, polygons and circles map closely to maplibre, so most of a migration is renaming.

1. Choose map data

Google's map data is only available through Google's SDKs. Pick a tile provider that serves MapLibre styles, such as those in awesome-maplibre, or host your own tiles, for example as a PMTiles file. Without a style, the map shows the MapLibre demo tiles.

2. Replace the dependency

flutter pub remove google_maps_flutter
flutter pub add maplibre:^0.4.0-pre.2

Remove the Google API key from AndroidManifest.xml, AppDelegate and web/index.html, and follow Platform setup. If your tile provider needs a key, it is usually part of the style URL.

Both packages have a CameraPosition, CameraUpdate and Marker, so don't import both in the same file.

3. The map widget

GoogleMap(
  initialCameraPosition: const CameraPosition(
    target: LatLng(47.68, 9.17),
    zoom: 7,
  ),
  style: myStyleJson,
  minMaxZoomPreference: const MinMaxZoomPreference(3, 18),
  rotateGesturesEnabled: false,
  onMapCreated: (controller) => _controller = controller,
  onTap: (latLng) => print(latLng),
)
MapLibreMap(
  options: MapOptions(
    initCamera: .position(
      center: Geographic(lon: 9.17, lat: 47.68),
      zoom: 7,
    ),
    style: MapStyle.url('https://tiles.openfreemap.org/styles/liberty'),
    minZoom: 3,
    maxZoom: 18,
    movements: MapMovements(rotate: false),
  ),
  onMapCreated: (controller) => _controller = controller,
  onClick: (event) => print(event.point),
)

Coordinates are geobase Geographic(lon: …, lat: …) instead of LatLng(lat, lng); mind the order when you convert.

google_maps_flutter maplibre
initialCameraPosition MapOptions.initCamera
style, mapType, cloud mapId MapOptions.style, see Styles
minMaxZoomPreference MapOptions.minZoom, maxZoom
cameraTargetBounds MapOptions.maxBounds
rotateGesturesEnabled, tiltGesturesEnabled, zoomGesturesEnabled, scrollGesturesEnabled MapOptions.movements (rotate, pitch, zoom, pan)
zoomControlsEnabled, compassEnabled MapControlButtons, MapCompass in children
myLocationEnabled UserLocationLayer in children
myLocationButtonEnabled MapControlButtons with tracking and onTrackingPressed
onTap, onLongPress onClick, onLongClick
onCameraMoveStarted, onCameraMove, onCameraIdle onCameraMoveStart, onCameraMove, onCameraMoveEnd
padding padding of CameraUpdate.position and CameraUpdate.bounds for the camera; widgets in children take their own padding

Google Maps applies its map padding to every later camera move. maplibre has no map-wide padding: pass padding with each camera update that should keep clear of an overlay, including every bounds fit.

Options of MapOptions can change at any time with setState(), except initCamera and fastPforEnabled, which are read once.

4. Camera

await controller.animateCamera(
  CameraUpdate.newLatLngZoom(const LatLng(47.68, 9.17), 10),
);
await controller.moveCamera(
  CameraUpdate.newLatLngBounds(bounds, 32),
);
final region = await controller.getVisibleRegion();
await controller.animateCamera(
  const CameraUpdate.position(center: Geographic(lon: 9.17, lat: 47.68), zoom: 10),
);
await controller.moveCamera(
  CameraUpdate.bounds(box, padding: const EdgeInsets.all(32)),
);
final bounds = await controller.getVisibleBounds();

CameraUpdate.points and CameraUpdate.geometry fit points or a geometry, and animateCamera takes a CameraAnimation.ease or .fly. getCamera() and the camera getter return the camera without a Future. See Map & Camera.

5. Markers, polylines, polygons and circles

Both packages take annotations as widget parameters, so they update with setState(). In maplibre, each layer holds a list of features with one style, instead of one object with its own style per annotation. Features with different styles go into different layers, or the style reads properties of the feature with an expression.

GoogleMap(
  markers: {
    Marker(
      markerId: const MarkerId('office'),
      position: const LatLng(47.68, 9.17),
      onTap: () => print('office'),
    ),
  },
  polylines: {
    Polyline(
      polylineId: const PolylineId('route'),
      points: const [LatLng(47.68, 9.17), LatLng(48, 9.5)],
      color: Colors.red,
      width: 4,
    ),
  },
  circles: {
    Circle(
      circleId: const CircleId('area'),
      center: const LatLng(47.68, 9.17),
      radius: 1000, // meters
      fillColor: Colors.blue.withValues(alpha: 0.3),
    ),
  },
)
MapLibreMap(
  onStyleLoaded: (style) => style.addImageFromIconData(
    id: 'pin',
    iconData: Icons.location_on,
    color: Colors.red,
  ),
  layers: [
    MarkerLayer(
      points: const [
        Feature(
          geometry: Point(Geographic(lon: 9.17, lat: 47.68)),
          properties: {'name': 'office'},
        ),
      ],
      iconImage: const .literal('pin'),
      iconAnchor: const .literal(IconAnchor.bottom),
      onClick: (event) => print(event.index),
    ),
    PolylineLayer(
      polylines: [
        Feature(
          geometry: LineString.from(const [
            Geographic(lon: 9.17, lat: 47.68),
            Geographic(lon: 9.5, lat: 48),
          ]),
        ),
      ],
      color: const .literal(Colors.red),
      width: const .literal(4),
    ),
    CircleLayer(
      points: const [Feature(geometry: Point(Geographic(lon: 9.17, lat: 47.68)))],
      radius: const .literal(20), // logical pixels
      color: .literal(Colors.blue.withValues(alpha: 0.3)),
    ),
  ],
)
google_maps_flutter maplibre
Marker A MarkerLayer with an icon image, or a WidgetLayer for any widget
Marker.icon (BitmapDescriptor) StyleController.addImage, addImageFromAssets, addImageFromIconData, addImageFromWidget
Marker.draggable, onDragEnd draggable: true and onDragEnd on the layer, see Drag
InfoWindow A widget in a WidgetLayer, shown after onClick
Polyline PolylineLayer
Polygon PolygonLayer
Circle CircleLayer; its radius is in logical pixels, not meters. Draw a polygon for a radius in meters.
ClusterManager A GeoJsonSource with a GeoJsonCluster
heatmaps HeatmapStyleLayer
tileOverlays A RasterSource and a RasterStyleLayer
GroundOverlay An ImageSource

For thousands of features, or data from a URL, add a source and a style layer instead, which the map styles on its own.

6. User location

google_maps_flutter shows the location with myLocationEnabled. In maplibre, a UserLocationLayer in children shows a stream of locations, for example from geolocator through the maplibre_geolocator package. Ask for the permission with geolocator or permission_handler.

7. What maplibre doesn't have

  • Google's map data, places, traffic, indoor maps and Street View.
  • Map types such as satellite or terrain as a switch. Load another style, or add a raster source with satellite imagery.

What maplibre adds