← Back to list

Flutter Maps: Google Maps, Mapbox, and OpenStreetMap Guide

Mobile apps today rely heavily on maps — for delivery tracking, ride-hailing, check-ins, travel planning, fitness tracking, and more…

Didin Jamaludin in Stackademic · 2025-12-10 01:28 · 2 claps · 17.1 min read
#flutter #maps #google-maps #mapbox #openstreetmap
Open on Medium ↗
Wiki topics: 📱 · Mobile Development 💪 · Fitness & Wellness ✈️ · Travel

Flutter Maps: Google Maps, Mapbox, and OpenStreetMap Guide

Mobile apps today rely heavily on maps , for delivery tracking, ride-hailing, check-ins, travel planning, fitness tracking, and more. Flutter, with its single codebase and rich ecosystem, offers multiple ways to embed interactive maps into your Android and iOS apps.

In this tutorial, you will learn how to integrate three major map providers in Flutter:

We’ll walk through:

✔ Setting up each map provider ✔ Creating a minimal map widget ✔ Adding markers ✔ Handling user location ✔ Map interactions and gestures ✔ Drawing shapes (polylines, polygons, circles) ✔ Applying styles ✔ Implementing routing/directions ✔ Best practices & provider comparisons

By the end, you’ll have a fully working Flutter app that can switch between Google Maps, Mapbox, and OSM , giving you the flexibility to choose the best mapping solution for your project.

1. Create a New Flutter Project

flutter create flutter_maps_demo cd flutter_maps_demo

Ensure you’re using Flutter 3.22+ for the best plugin compatibility.

2. Add Dependencies

Open pubspec.yaml and add:

dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 # Google Maps google_maps_flutter: ^2.5.0 # Mapbox Maps mapbox_maps_flutter: ^2.17.0 # OpenStreetMap via flutter_map flutter_map: ^8.2.2 latlong2: ^0.9.0 # Location package geolocator: ^14.0.2

Then run:

flutter pub get

3. Android Setup

Google Maps

Add your API key in android/app/src/main/AndroidManifest.xml:

<meta-data android:name="com.google.android.geo.API_KEY" android:value="YOUR_GOOGLE_MAPS_API_KEY"/>

Mapbox

Add your access token:

<meta-data android:name="com.mapbox.token" android:value="YOUR_MAPBOX_ACCESS_TOKEN"/>

Location Permissions

4. iOS Setup

In ios/Runner/AppDelegate.swift, enable Google Maps:

GMSServices.provideAPIKey("YOUR_GOOGLE_MAPS_API_KEY")

In ios/Runner/Info.plist, add:

<key>NSLocationWhenInUseUsageDescription</key> <string>Your location is required to show your current position on the map.</string>

This completes the setup.

Before showing the maps in your Flutter app, you must configure API keys or access tokens for each provider (except OpenStreetMap, which is free and keyless by default). Below are the complete steps to prepare Google Maps, Mapbox, and OSM.

1. Google Maps API Key Setup

Google Maps requires enabling a set of APIs and generating an API key.

Step 1 , Open Google Cloud Console

Go to:

https://console.cloud.google.com/

Create a new project or select an existing one.

Step 2 , Enable the Required APIs

Navigate to: APIs & Services → Library

Enable:

Step 3 , Create an API Key

Go to: APIs & Services → Credentials → Create Credentials → API key

Copy the generated key.

Step 4 , Add Restrictions (Recommended)

Choose:

  • Application restrictionsAndroid apps / iOS apps
  • API restrictions → Restrict key usage to only Maps, Directions, Places

Step 5 , Add to Flutter Project

Android: android/app/src/main/AndroidManifest.xml

<meta-data android:name="com.google.android.geo.API_KEY" android:value="YOUR_GOOGLE_MAPS_API_KEY"/>
GMSServices.provideAPIKey("YOUR_GOOGLE_MAPS_API_KEY")

Google Maps is now ready.

2. Mapbox Access Token Setup

Mapbox requires an Access Token and provides styling & navigation features.

Step 1 , Log in to Mapbox Dashboard

https://account.mapbox.com/

Step 2 , Create an Access Token

Go to: Access tokens → Create new token

Recommended settings:

Copy the token.

Step 3 , Add Token to Flutter App

Android Manifest:

<meta-data android:name="com.mapbox.token" android:value="YOUR_MAPBOX_ACCESS_TOKEN"/>

iOS Info.plist:

<key>MBXAccessToken</key> <string>YOUR_MAPBOX_ACCESS_TOKEN</string>

3. OpenStreetMap Setup

OpenStreetMap does not require an API key , however, you must follow:

A typical tile setup in Flutter (using default OSM tiles):

TileLayer( urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", userAgentPackageName: 'com.example.app', )

4. Summary Table

Your app is now fully prepared to load Google Maps, Mapbox, or OSM tiles.

In this section, you will learn how to display a minimal map for each provider:

We’ll create a simple UI that allows switching between map providers, but first, let’s implement the basic standalone map widgets.

1. Google Maps , Basic Map Widget

Add this to google_map_view.dart:

import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; class GoogleMapView extends StatefulWidget { const GoogleMapView({super.key}); @override State<GoogleMapView> createState() => _GoogleMapViewState(); } class _GoogleMapViewState extends State<GoogleMapView> { late GoogleMapController mapController; final LatLng initialPosition = const LatLng(-6.200000, 106.816666); // Jakarta @override Widget build(BuildContext context) { return GoogleMap( initialCameraPosition: CameraPosition( target: initialPosition, zoom: 12, ), onMapCreated: (controller) { mapController = controller; }, myLocationEnabled: false, zoomControlsEnabled: false, ); } }

This displays a simple Google Map centered on Jakarta.

2. Mapbox , Basic Map Widget

Create mapbox_map_view.dart:

import 'package:flutter/material.dart'; import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart'; class MapboxMapView extends StatefulWidget { const MapboxMapView({super.key}); @override State<MapboxMapView> createState() => _MapboxMapViewState(); } class _MapboxMapViewState extends State<MapboxMapView> { MapboxMap? mapboxMap; @override Widget build(BuildContext context) { return MapWidget( styleUri: MapboxStyles.MAPBOX_STREETS, onMapCreated: (controller) { mapboxMap = controller; mapboxMap!.setCamera( CameraOptions( center: Point(coordinates: Position(106.816666, -6.200000)), zoom: 12, ), ); }, ); } }

Mapbox requires a style URI. Common styles include:

3. OpenStreetMap (flutter_map) , Basic Map Widget

Create osm_map_view.dart:

import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; class OSMMapView extends StatelessWidget { const OSMMapView({super.key}); final LatLng center = const LatLng(-6.200000, 106.816666); // Jakarta @override Widget build(BuildContext context) { return FlutterMap( options: MapOptions( initialCenter: center, initialZoom: 12, ), children: [ TileLayer( urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", userAgentPackageName: "com.example.flutter_maps_demo", ) ], ); } }

This renders OSM tiles with zero configuration required.

4. Optional: Switching Between Map Providers

Create a simple tab view in main.dart:

import 'package:flutter/material.dart'; import 'package:flutter_maps_demo/widgets/google_map_view.dart'; import 'package:flutter_maps_demo/widgets/mapbox_map_view.dart'; import 'package:flutter_maps_demo/widgets/osm_map_view.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: DefaultTabController( length: 3, child: Scaffold( appBar: AppBar( title: const Text("Flutter Maps Comparison"), bottom: const TabBar( tabs: [ Tab(text: "Google"), Tab(text: "Mapbox"), Tab(text: "OSM"), ], ), ), body: const TabBarView( children: [GoogleMapView(), MapboxMapView(), OSMMapView()], ), ), ), ); } }

This provides a clean way to switch between the 3 map implementations during development.

5. Summary

At this point, your Flutter app can display:

You now have a strong foundation to build more advanced map features.

Markers are essential for showing points of interest, pins, user-selected locations, and more. In this section, we’ll cover how to add:

We will implement markers for Google Maps, Mapbox, and OpenStreetMap (flutter_map).

1. Adding Markers in Google Maps

Google Maps allows adding default markers easily. Custom image markers are supported but require asset conversion , we’ll cover that later.

Single Marker

Modify GoogleMapView:

class _GoogleMapViewState extends State<GoogleMapView> { late GoogleMapController mapController; final LatLng initialPosition = const LatLng(-6.200000, 106.816666); final Set<Marker> markers = { Marker( markerId: MarkerId("jakarta"), position: LatLng(-6.200000, 106.816666), infoWindow: InfoWindow(title: "Jakarta"), ), }; @override Widget build(BuildContext context) { return GoogleMap( initialCameraPosition: CameraPosition( target: initialPosition, zoom: 12, ), markers: markers, onMapCreated: (controller) => mapController = controller, ); } }

Multiple Markers

You can generate markers dynamically:

final List<LatLng> locations = [ LatLng(-6.200000, 106.816666), // Jakarta LatLng(-6.914744, 107.609810), // Bandung LatLng(-7.250445, 112.768845), // Surabaya ]; late Set<Marker> markers = locations.map((latLng) { return Marker(markerId: MarkerId(latLng.toString()), position: latLng); }).toSet();

2. Adding Markers in Mapbox

Mapbox uses Annotations instead of markers.

Single Marker

void _addMapboxMarker() async { await _pointAnnotationManager.create( PointAnnotationOptions( geometry: Point(coordinates: Position(106.816666, -6.200000)), iconSize: 1.5, iconImage: "marker-icon", // must be registered first ), ); }

Call it inside onMapCreated:

class _MapboxMapViewState extends State<MapboxMapView> { MapboxMap? mapboxMap; late PointAnnotationManager _pointAnnotationManager; @override Widget build(BuildContext context) { return MapWidget( styleUri: MapboxStyles.MAPBOX_STREETS, onMapCreated: (controller) async { mapboxMap = controller; // Create annotation manager _pointAnnotationManager = await mapboxMap!.annotations .createPointAnnotationManager(); _addMapboxMarker(); _addMultipleMarkers(); mapboxMap!.setCamera( CameraOptions( center: Point(coordinates: Position(106.816666, -6.200000)), zoom: 12, ), ); }, ); } ... }

Using a Custom Asset Marker

Register the image first:

mapboxMap?.loadStyle(uri: MapboxStyles.MAPBOX_STREETS).then((_) async { final bytes = await rootBundle.load('assets/marker.png'); await mapboxMap?.style.addImage( 'marker-icon', bytes.buffer.asUint8List(), ); });

Then the same createPointAnnotation call will use your asset.

Multiple Markers

void _addMultipleMarkers() async { final points = [ Position(106.816666, -6.200000), // Jakarta Position(107.609810, -6.914744), // Bandung Position(112.768845, -7.250445), // Surabaya ]; for (var pos in points) { await _pointAnnotationManager.create( PointAnnotationOptions( geometry: Point(coordinates: pos), iconImage: "marker-icon", ), ); } }

3. Adding Markers in OpenStreetMap (flutter_map)

flutter_map makes marker creation extremely flexible - you can use any Flutter widget.

Single Marker

MarkerLayer( markers: [ Marker( width: 40, height: 40, point: LatLng(-6.200000, 106.816666), child: const Icon(Icons.location_pin, color: Colors.red, size: 40), ), ], )

Multiple Markers

final points = [ LatLng(-6.200000, 106.816666), LatLng(-6.914744, 107.609810), LatLng(-7.250445, 112.768845), ]; MarkerLayer( markers: points.map((p) { return Marker( width: 40, height: 40, point: p, child: const Icon(Icons.location_on, color: Colors.blue), ); }).toList(), )

Custom Marker Widgets

You can use any widget: images, containers, animations, etc.

Marker( width: 50, height: 50, point: LatLng(-6.200000, 106.816666), child: Column( children: [ Image.asset("assets/custom_pin.png", width: 40), const Text("Jakarta", style: TextStyle(fontSize: 12)), ], ), )

This level of customization is not possible in Google Maps, making OSM ideal for UI-heavy maps.

4. Comparison Table

5. Summary

At this point, your Flutter app can show:

Markers are one of the most commonly used map features , and now you have a working implementation for all three providers.

User Location & Permissions

Displaying the user’s current location is one of the most requested features in map-based apps. In this section, we will:

  • Request location permissions
  • Retrieve the user’s current GPS coordinates
  • Move the map camera to the user’s location
  • Show a blue dot or custom user marker, depending on the map provider

We’ll use the **geolocator** package because it provides a unified, reliable API for Android & iOS.

1. Installing & Configuring Geolocator

You already added it earlier:

geolocator: ^14.0.2

Now configure permissions.

Android Permissions

In android/app/src/main/AndroidManifest.xml:

Android 12+ also requires:

(Only if your app needs location in the background.)

iOS Permissions

In ios/Runner/Info.plist:

<key>NSLocationWhenInUseUsageDescription</key> <string>We need your location to show your current position on the map.</string>

2. Requesting Permission in Flutter

Create a helper function:

import 'package:geolocator/geolocator.dart'; Future<Position?> determinePosition() async { bool serviceEnabled; LocationPermission permission; // Check if location service is enabled serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { return Future.error('Location services are disabled.'); } // Check permission status permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { return Future.error('Location permissions are denied.'); } } if (permission == LocationPermission.deniedForever) { return Future.error( 'Location permissions are permanently denied, we cannot request.'); } // Get current position return await Geolocator.getCurrentPosition(); }

3. Showing User Location on Google Maps

Google Maps makes this easy with myLocationEnabled.

Modify your GoogleMapView:

class _GoogleMapViewState extends State<GoogleMapView> { late GoogleMapController mapController; LatLng? _currentPos; @override void initState() { super.initState(); _getCurrentLocation(); } void _getCurrentLocation() async { final pos = await determinePosition(); setState(() { _currentPos = LatLng(pos!.latitude, pos.longitude); }); mapController.animateCamera( CameraUpdate.newLatLng(_currentPos!), ); } @override Widget build(BuildContext context) { return GoogleMap( initialCameraPosition: const CameraPosition( target: LatLng(-6.200000, 106.816666), zoom: 12, ), onMapCreated: (c) => mapController = c, myLocationEnabled: true, myLocationButtonEnabled: true, ); } }

Google Maps automatically displays the blue dot.

4. Showing User Location on Mapbox

Mapbox uses a location puck (a stylized blue dot).

Inside onMapCreated:

mapboxMap!.location.updateSettings(LocationComponentSettings( enabled: true, ));

To move the camera to the user position:

void _moveToUserLocation() async { final pos = await determinePosition(); mapboxMap!.setCamera( CameraOptions( center: Point( coordinates: Position(pos.longitude, pos.latitude), ), zoom: 14, ), ); }

Call it after enabling the location component:

_moveToUserLocation();

5. Showing User Location on OpenStreetMap (flutter_map)

flutter_map has no built-in blue dot, but you can add a custom marker.

Example:

class OSMMapView extends StatefulWidget { const OSMMapView({super.key}); @override State<OSMMapView> createState() => _OSMMapViewState(); } class _OSMMapViewState extends State<OSMMapView> { LatLng? _userLocation; @override void initState() { super.initState(); _getLocation(); } void _getLocation() async { final pos = await determinePosition(); setState(() { _userLocation = LatLng(pos!.latitude, pos.longitude); }); } @override Widget build(BuildContext context) { return FlutterMap( options: MapOptions( initialCenter: _userLocation ?? const LatLng(-6.200000, 106.816666), initialZoom: 13, ), children: [ TileLayer( urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", userAgentPackageName: "com.example.flutter_maps_demo", ), if (_userLocation != null) MarkerLayer( markers: [ Marker( width: 40, height: 40, point: _userLocation!, child: const Icon(Icons.my_location, color: Colors.blue), ) ], ), ], ); } }

6. Comparison Table

7. Summary

At this stage, your Flutter app can:

✔ Request and check location permissions ✔ Get the user’s GPS position ✔ Show the current location on Google Maps ✔ Show the location puck on Mapbox ✔ Render a custom location marker on OSM ✔ Move the map camera dynamically

Map Interactions (Gestures, Taps, Camera Events)

Interactive maps must respond to user actions such as tapping, dragging, zooming, and camera movement. In this section, we’ll cover how to handle:

Each provider handles map interactions differently, so we’ll explore Google Maps, Mapbox, and OpenStreetMap (flutter_map) individually.

1. Google Maps , Gestures & Events

Google Maps provides simple callbacks for events.

Tap Event

onTap: (LatLng position) { print("Tapped: $position"); }

Long-Press Event

onLongPress: (LatLng position) { print("Long pressed: $position"); }

Camera Movement Events

Detect when the camera is moving:

onCameraMove: (CameraPosition position) { print("Camera moving: ${position.target}"); }

Detect when movement stops:

onCameraIdle: () { print("Camera idle"); }

Enable/Disable Gestures

zoomGesturesEnabled: true, rotateGesturesEnabled: true, scrollGesturesEnabled: true, tiltGesturesEnabled: true,

Full Example

GoogleMap( initialCameraPosition: CameraPosition( target: LatLng(-6.2, 106.8), zoom: 12, ), onTap: (pos) => print("Tap at $pos"), onLongPress: (pos) => print("Long press at $pos"), onCameraMove: (camera) => print("Camera moving"), onCameraIdle: () => print("Camera stopped"), zoomGesturesEnabled: true, rotateGesturesEnabled: true, )

2. Mapbox , Gestures & Events

Mapbox offers a richer event system with listeners. Version 1.x of mapbox_maps_flutter uses platform channels with stream-based callbacks.

Tap Listener

mapboxMap!.gestures.addOnMapClickListener((point, screenPos) { print("Tapped at: ${point.coordinates}"); return true; // consume event });

Long Press Listener

mapboxMap!.gestures.addOnMapLongClickListener((point, screenPos) { print("Long pressed: ${point.coordinates}"); return true; });

Camera Change Events

While moving:

mapboxMap!.camera.addCameraChangeListener((cameraChangedEvent) { print("Camera moving..."); });

When movement ends:

mapboxMap!.camera.addCameraIdleListener(() { print("Camera idle"); });

Gesture Control

mapboxMap!.gestures.updateSettings( GestureSettings( rotateEnabled: true, zoomEnabled: true, scrollEnabled: true, pinchToZoomEnabled: true, ), );

Full Example

onMapCreated: (controller) async { mapboxMap = controller; mapboxMap!.gestures.addOnMapClickListener((point, screenPos) { print("Mapbox tap: ${point.coordinates}"); return true; }); mapboxMap!.camera.addCameraIdleListener(() { print("Mapbox camera idle"); }); }

3. OpenStreetMap (flutter_map) , Gestures & Events

flutter_map gives you full control via MapOptions.

Tap Event

onTap: (tapPosition, point) { print("OSM tap: $point"); }

Long-Press Event

onLongPress: (tapPosition, point) { print("OSM long press: $point"); }

Camera Movement Events

While moving:

onPositionChanged: (MapPosition pos, bool hasGesture) { print("Camera moving: ${pos.center}"); }

End movement:

onMapEvent: (event) { if (event is MapEventMoveEnd) { print("OSM camera idle"); } }

Gesture Configurations

interactionOptions: const InteractionOptions( flags: InteractiveFlag.all, // enable everything ),

Disable rotation/zoom:

interactionOptions: const InteractionOptions( flags: InteractiveFlag.pinchZoom | InteractiveFlag.drag, ),

Full Example

FlutterMap( options: MapOptions( onTap: (tapPos, latLng) => print("Tap: $latLng"), onLongPress: (tapPos, latLng) => print("Long press: $latLng"), onPositionChanged: (pos, _) => print("Camera moving"), onMapEvent: (event) { if (event is MapEventMoveEnd) print("Camera idle"); }, ), children: [ TileLayer(...), ], )

4. Comparison Table

5. Summary

So far, your map app can:

✔ Detect tap, long press, and drag interactions ✔ Track camera movement and idle states ✔ Customize gesture behaviors (zoom, rotate, drag) ✔ Handle events differently across 3 major map providers

Drawing Shapes (Polylines, Polygons, Circles)

Maps often need shapes to represent routes, boundaries, service areas, zones, and more. In this section, you will learn how to draw:

For each provider (Google Maps, Mapbox, and OpenStreetMap), the APIs differ-so we’ll cover them independently and then compare.

1. Polylines, Polygons, and Circles in Google Maps

Google Maps supports all three shapes using built-in overlay objects.

✅ Polyline Example

Set<Polyline> polylines = { Polyline( polylineId: const PolylineId("route"), width: 4, color: Colors.blue, points: const [ LatLng(-6.200000, 106.816666), LatLng(-6.914744, 107.609810), LatLng(-7.250445, 112.768845), ], ), };

Add to GoogleMap:

GoogleMap( polylines: polylines, ... )

🔺 Polygon Example

Set<Polygon> polygons = { Polygon( polygonId: const PolygonId("area"), fillColor: Colors.green.withOpacity(0.3), strokeColor: Colors.green, strokeWidth: 2, points: const [ LatLng(-6.18, 106.80), LatLng(-6.20, 106.82), LatLng(-6.22, 106.80), ], ), };

🟢 Circle Example

Set<Circle> circles = { Circle( circleId: const CircleId("radius"), center: const LatLng(-6.200000, 106.816666), radius: 3000, // meters fillColor: Colors.red.withOpacity(0.3), strokeColor: Colors.red, strokeWidth: 2, ) };

2. Polylines, Polygons, and Circles in Mapbox

Mapbox uses Annotation Managers (new SDK). There are separate managers for:

🟦 Polyline Example

late PolylineAnnotationManager _polylineManager; void _addPolyline() async { _polylineManager = await mapboxMap!.annotations.createPolylineAnnotationManager(); await _polylineManager.create( PolylineAnnotationOptions( geometry: LineString( coordinates: [ Position(106.816666, -6.200000), Position(107.609810, -6.914744), Position(112.768845, -7.250445), ], ), lineColor: Colors.blue.value, lineWidth: 4.0, ), ); }

🔺 Polygon Example

late PolygonAnnotationManager _polygonManager; void _addPolygon() async { _polygonManager = await mapboxMap!.annotations.createPolygonAnnotationManager(); await _polygonManager.create( PolygonAnnotationOptions( geometry: Polygon( coordinates: [ [ Position(106.80, -6.18), Position(106.82, -6.20), Position(106.80, -6.22), ], ], ), fillColor: Colors.green.withOpacity(0.3).value, fillOutlineColor: Colors.green.value, ), ); }

🟢 Circle Example

Mapbox renders circles via circle annotations:

late CircleAnnotationManager _circleManager; void _addCircle() async { _circleManager = await mapboxMap!.annotations.createCircleAnnotationManager(); await _circleManager.create( CircleAnnotationOptions( geometry: Point( coordinates: Position(106.816666, -6.200000), ), circleRadius: 30.0, circleColor: Colors.red.withOpacity(0.4).value, circleStrokeColor: Colors.red.value, circleStrokeWidth: 2.0, ), ); }

Note: Mapbox circles are rendered in pixels, not meters. For geospatial radius zones, you must calculate lat/lng buffers manually or use a custom polygon.

3. Polylines, Polygons, and Circles in OpenStreetMap (flutter_map)

flutter_map is highly flexible and supports all shapes via layers.

🟦 Polyline Example

PolylineLayer( polylines: [ Polyline( points: [ LatLng(-6.200000, 106.816666), LatLng(-6.914744, 107.609810), LatLng(-7.250445, 112.768845), ], color: Colors.blue, strokeWidth: 4, ), ], )

🔺 Polygon Example

PolygonLayer( polygons: [ Polygon( points: [ LatLng(-6.18, 106.80), LatLng(-6.20, 106.82), LatLng(-6.22, 106.80), ], color: Colors.green.withOpacity(0.3), borderStrokeWidth: 2, borderColor: Colors.green, ), ], )

🟢 Circle Example (true meters radius)

flutter_map supports CircleLayer with real-world radii:

CircleLayer( circles: [ CircleMarker( point: LatLng(-6.200000, 106.816666), radius: 3000, // meters useRadiusInMeter: true, color: Colors.red.withOpacity(0.3), borderColor: Colors.red, borderStrokeWidth: 2, ), ], )

This is more geospatially accurate than Mapbox’s pixel-based circles.

4. Comparison Table

5. Summary

By now, your app can draw:

✔ Routes using polylines ✔ Regions using polygons ✔ Radius areas using circles ✔ Shapes on Google Maps, Mapbox, and OSM with proper styling

You now have the tools to create:

Using Map Styles (Google Maps, Mapbox, and OpenStreetMap)

Map styling transforms the look and feel of your maps-useful for branding, dark mode, highlighting features, or achieving a unique design language. Each provider offers different levels of customization:

  • Google Maps → JSON-based styling
  • Mapbox → Full styling system with custom style URLs
  • OSM (flutter_map) → Style changes depend on the tile provider

Let’s explore how to apply map styles for all three.

1. Google Maps , JSON Styling

Google Maps supports custom styles using a JSON schema generated by:

You can create themes like dark, night, road-focused, or minimalistic maps.

Step 1 , Create a style JSON file

Example: assets/map_style_dark.json

[ { "elementType": "geometry", "stylers": [ { "color": "#212121" } ] }, { "elementType": "labels.text.fill", "stylers": [ { "color": "#757575" } ] } ]

Step 2 , Load the style in Flutter

Add the file in pubspec.yaml:

assets: - assets/map_style_dark.json

Step 3 , Apply style to Google Map

Inside GoogleMapView:

late GoogleMapController mapController; void _applyMapStyle() async { final style = await rootBundle.loadString('assets/map_style_dark.json'); mapController.setMapStyle(style); }

Call it in onMapCreated:

onMapCreated: (controller) { mapController = controller; _applyMapStyle(); }

✔️ You now have a fully styled Google Map.

Google supports both light and dark modes, plus highly customized color schemes.

2. Mapbox , Custom Styles via Style URIs

Mapbox is the most powerful provider for styling. You can use:

Built-in Styles

styleUri: MapboxStyles.MAPBOX_STREETS styleUri: MapboxStyles.SATELLITE styleUri: MapboxStyles.OUTDOORS styleUri: MapboxStyles.DARK styleUri: MapboxStyles.LIGHT

Custom Style from Mapbox Studio

return MapWidget( styleUri: "mapbox://styles/yourname/custom-style-id", onMapCreated: (controller) { mapboxMap = controller; }, );

Advanced Style Customization (Layers, Colors)

You can update styles dynamically once the style loads:

mapboxMap!.style.onStyleLoaded.listen((style) async { await mapboxMap!.style.setStyleLayerProperty( "water", "fill-color", Colors.blue.value, ); });

✔️ Mapbox Styling Power Highlights

3. OpenStreetMap (OSM) , Tile Provider Based Styling

OSM itself does not support dynamic styling like Google or Mapbox. Instead, styling depends on the tile provider you choose.

Common tile providers:

Example: OSM Default

TileLayer( urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", )

Example: Dark Mode Map (MapTiler)

TileLayer( urlTemplate: "https://api.maptiler.com/maps/darkmatter/{z}/{x}/{y}.png?key=YOUR_KEY", )

Example: Stadia Maps Smooth Style

TileLayer( urlTemplate: "https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}.png", )

Custom Offline Vector Styles

Using OpenMapTiles:

TileLayer( urlTemplate: "https://yourserver.com/styles/basic/{z}/{x}/{y}.png", )

OSM styling is extremely flexible if you control the tile source.

4. Dynamic Map Style Switching

You can add a toggle (light/dark mode):

Google Maps

bool isDark = true; void toggleStyle() { mapController.setMapStyle( isDark ? darkStyleJson : lightStyleJson, ); }

Mapbox

mapboxMap!.setStyleUri( isDark ? MapboxStyles.DARK : MapboxStyles.LIGHT, );

OSM

setState(() { _tileUrl = isDark ? mapTilerDarkUrl : mapTilerLightUrl; });

5. Comparison Table

6. Summary

By now, you can:

✔ Apply advanced JSON styles in Google Maps ✔ Use Mapbox Studio for highly customized map themes ✔ Load styled OSM tiles from external providers ✔ Switch styles dynamically (dark/light mode)

This allows you to create visually consistent and brand-compliant maps across all providers.

Routing & Directions (Google Maps, Mapbox, and OSM Options)

Routing is essential for apps like delivery services, ride-hailing, tourism guides, or anything requiring navigation. Flutter itself does NOT provide built-in routing APIs , you must use each provider’s Directions API or a third-party routing service.

This section covers:

We’ll fetch route data, decode polylines, and display them on the map.

1. Google Directions API (via HTTP request)

Google’s Directions API returns encoded polylines, which must be decoded and drawn on the map.

Step 1 , Enable Directions API

In Google Cloud Console:

✔ Enable Directions API

Step 2 , Make a directions request

Example request:

https://maps.googleapis.com/maps/api/directions/json?origin=-6.2,106.8&destination=-6.914744,107.609810&key=YOUR_KEY

Step 3 , Decode polyline

Install helper:

flutter_polyline_points: ^3.1.0

Decode:

final polylinePoints = PolylinePoints(); List<LatLng> decodePolyline(String encoded) { final result = polylinePoints.decodePolyline(encoded); return result .map((p) => LatLng(p.latitude, p.longitude)) .toList(); }

Step 4 , Draw a polyline on the Google Map

void _getRouteGoogle() async { final url = "https://maps.googleapis.com/maps/api/directions/json?origin=-6.2,106.8&destination=-6.914744,107.609810&key=YOUR_KEY"; final response = await http.get(Uri.parse(url)); final data = jsonDecode(response.body); final encodedPolyline = data["routes"][0]["overview_polyline"]["points"]; final List<LatLng> points = decodePolyline(encodedPolyline); setState(() { _polylines.add(Polyline( polylineId: const PolylineId("route"), color: Colors.blue, width: 5, points: points, )); }); }

2. Mapbox Directions API

Mapbox returns GeoJSON LineString, which is easier to work with than Google’s encoded polyline.

Step 1 , Request route

Example GET request:

https://api.mapbox.com/directions/v5/mapbox/driving/106.816666,-6.2;107.609810,-6.914744?geometries=geojson&access_token=YOUR_MAPBOX_TOKEN

Step 2 , Parse GeoJSON

void _getRouteMapbox() async { final url = "https://api.mapbox.com/directions/v5/mapbox/driving/106.816666,-6.2;107.609810,-6.914744?geometries=geojson&access_token=YOUR_TOKEN"; final response = await http.get(Uri.parse(url)); final data = jsonDecode(response.body); final coords = data["routes"][0]["geometry"]["coordinates"] as List; final List<Position> positions = coords .map((c) => Position(c[0], c[1])) .toList(); _drawPolylineMapbox(positions); }

Step 3 , Draw route as a Mapbox polyline

void _drawPolylineMapbox(List<Position> coords) async { final manager = await mapboxMap!.annotations.createPolylineAnnotationManager(); await manager.create( PolylineAnnotationOptions( geometry: LineString(coordinates: coords), lineColor: Colors.blue.value, lineWidth: 5.0, ), ); }

3. OSM Routing via OpenRouteService

OSM has no built-in routing API. Instead, you can use:

We’ll use OpenRouteService, which returns GeoJSON routes.

Step 1 , Get ORS API key

Step 2 , Request route

https://openrouteservice.org/

https://api.openrouteservice.org/v2/directions/driving-car?api_key=YOUR_KEY&start=106.816666,-6.2&end=107.609810,-6.914744

Step 3 , Parse coordinates

void _getRouteOSM() async { final url = "https://api.openrouteservice.org/v2/directions/driving-car?" "api_key=YOUR_KEY&start=106.816666,-6.2&end=107.609810,-6.914744"; final res = await http.get(Uri.parse(url)); final data = jsonDecode(res.body); final coords = (data["features"][0]["geometry"]["coordinates"] as List); final List<LatLng> points = coords.map((c) => LatLng(c[1], c[0])).toList(); setState(() => _routePoints = points); }

Step 4 , Draw route on OSM

PolylineLayer( polylines: [ Polyline( points: _routePoints, strokeWidth: 5, color: Colors.blue, ), ], )

4. Comparison Table

5. Summary

You now know how to implement routing across all three map providers:

✔ Fetch routes using HTTP from Google, Mapbox, or OpenRouteService ✔ Decode polyline or parse GeoJSON ✔ Draw the route using polylines on your map ✔ Support navigation-style experiences

This unlocks features like:

Best Practices for Flutter Maps (Google Maps, Mapbox, and OSM)

As your map-based Flutter application grows in features-markers, shapes, routing, user location, styles-performance and reliability become critical. This section highlights essential best practices for using Google Maps, Mapbox, and OpenStreetMap efficiently and safely.

1. Performance Best Practices

✅ 1. Avoid Rebuilding the Entire Map Widget

When using setState, unnecessary rebuilds of the map widget cause lag.

setState(() { markers.add(newMarker); });

Good:

setState(() { rebuild full GoogleMap(); });

Bad:

Keep the map widget stable-update only overlays (markers, polylines, etc.).

✅ 2. Batch Marker and Shape Updates

Instead of adding 500 markers one by one:

✅ 3. Lazy Load or Cluster Markers

For thousands of markers:

  • Google Maps → implement marker clustering manually
  • Mapbox → built-in clustering via GeoJSON source
  • OSM → MarkerClusterLayer plugin

Marker clustering improves performance dramatically.

✅ 4. Avoid Large Bitmaps for Custom Icons

Keep icon sizes under 64x64px for best performance.

Large PNGs = slow rendering.

✅ 5. Debounce Camera Events

Camera events fire continuously. Debounce them to avoid excessive API calls:

Timer? _debounce; onCameraMove: (pos) { _debounce?.cancel(); _debounce = Timer(const Duration(milliseconds: 300), () { // handle idle logic }); };

2. API Key Management

🔑 1. Restrict Google Maps API Key

Use:

Always restrict which APIs can be used with the key.

🔑 2. Mapbox Tokens

Use:

🔑 3. Don’t Hardcode Keys in Source Control

Use env vars or build-time configs:

3. User Location Best Practices

📍 1. Request Location Permission Gradually

Explain why you need the location:

📍 2. Avoid Constant High-Accuracy Updates

High-accuracy = battery drain.

Use:

Geolocator.getCurrentPosition()

Only when needed.

📍 3. Handle “Location Disabled” Cases

Show user-friendly prompts:

Please enable GPS to use location features.

Provide a button to open device settings:

Geolocator.openLocationSettings();

4. Routing Best Practices

🚗 1. Cache API Responses

Avoid repeating expensive routing API calls.

🚗 2. Avoid Over-Routing

Call routing APIs:

🚗 3. Use GeoJSON Whenever Possible

Mapbox + ORS return GeoJSON (easier to parse than Google’s encoded polylines).

5. Map Style Best Practices

🎨 1. Pre-load styles

Avoid style flickering by loading styles early.

🎨 2. Use dark/light mode switching

Match system theme for better UX.

🎨 3. Minimize custom JSON styling attributes

Too many overrides = slower Google Maps rendering.

6. Memory & Resource Management

🧹 1. Dispose of controllers properly

@override void dispose() { mapController.dispose(); super.dispose(); }

🧹 2. Clear annotations when switching pages

Mapbox:

manager.deleteAll();

OSM:

polylines.clear(); markers.clear();

7. Offline & Caching Best Practices

📦 Google Maps

No offline tiles allowed (but limited caching happens automatically).

📦 Mapbox

Supports:

Ideal for apps needing offline navigation.

📦 OSM

You can self-host tiles or use:

Best option for truly free offline maps.

8. Comparison Summary

9. Final Recommendations

If you need the easiest setup

Google Maps

If you want full customization

Mapbox

If you want totally free + offline**

OpenStreetMap (flutter_map)

If your app supports multiple map providers

➡ Build a Map Abstraction Layer to avoid writing different logic everywhere.

In this comprehensive guide, you learned how to integrate and work with three major mapping solutions in Flutter-Google Maps, Mapbox, and OpenStreetMap (via flutter_map). Each provider comes with its own strengths, flexibility, and capabilities, giving you the freedom to choose the best mapping approach based on your project's requirements.

Here’s a quick recap of what you accomplished:

✅ You Set Up Three Map Providers

✅ You Displayed Maps & Added Core Map Features

✅ You Built Geospatial Overlays

✅ You Styled Maps for Better UX

✅ You Implemented Routing & Directions

🎉 What You Now Have

A fully functional Flutter mapping foundation that supports:

Whether you’re building:

…you’re now equipped with the skills to implement professional-grade mapping experiences in Flutter.

📌 Final Advice

Each provider has trade-offs-Google for accuracy & simplicity, Mapbox for customization & offline capabilities, and OSM for cost-free flexibility.

Choose the one that aligns with your:

You can get the full source code on our GitHub.

That’s just the basics. If you need more deep learning about Flutter and Dart, you can take the following cheap course:

Thanks!

Originally published at https://www.djamware.com.


메타데이터
post_id
09d5753b81df
slug
flutter-maps-google-maps-mapbox-and-openstreetmap-guide-09d5753b81df
url
https://blog.stackademic.com/flutter-maps-google-maps-mapbox-and-openstreetmap-guide-09d5753b81df
canonical_url
https://blog.stackademic.com/flutter-maps-google-maps-mapbox-and-openstreetmap-guide-09d5753b81df
author_url
https://medium.com/@didinjamaludin
status
ok
fetched_at
2026-06-09 15:37:30