Introduction
In this tutorial, you will learn how to integrate Google Maps into a Flutter app with live location tracking capabilities. We will cover customizations like setting up custom image markers and drawing route direction polylines. Additionally, you’ll discover how to add real-time location updates to the map, making your app more interactive and user-friendly.
Set Up Google Maps API:
To get started, you’ll need to set up the Google Maps API. Follow these steps:
- Go to the Google Cloud Console at (https://console.cloud.google.com/) and create a new project.
- Enable the Maps SDK for Android and Maps SDK for iOS within your project settings.
- Generate an API KEY for your project. This API KEY will be used to access Google Maps services from your Flutter app.
Add API key in Android and iOS:
In Android’s (android/app/src/main/AndroidManifest.xml)file, include the following:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application <meta-data android:name="com.google.android.geo.API_KEY" android:value="API KEY"/> </application> </manifest>
In iOS(ios/Runner/AppDelegate.m)file, include the following:
override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GMSServices.provideAPIKey("API KEY") GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) }
Adding Dependencies:
In pubspec.yaml file, add the required packages:
dependencies: flutter: sdk: flutter google_maps_flutter: ^2.3.1 flutter_polyline_points: ^1.0.0 geolocator: ^9.0.2
Run the following command to install dependencies
$ flutter pub get
Permission and Platform Setup:
Android(android/app/src/main/AndroidManifest.xml)
For Android, add the necessary location permission in AndroidManifest.xml:
<uses-permission android:name=”android.permission.ACCESS_COARSE_LOCATION”/>
<uses-permission android:name=”android.permission.ACCESS_FINE_LOCATION”/>
If you want to access the user’s location in the background as well, add this permissions:
<uses-permission android:name=”android.permission.ACCESS_BACKGROUND_LOCATION”/>
iOS (ios/Runner/Info.plist)
For iOS, include the following location permissions in Info.plist:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs access to location when open and in the background.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs access to location when in the background.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to current location.</string>
Integrating Google Map:
Now, let’s add the Google Maps widget to the Flutter widget:
import 'package:geolocator/geolocator.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; //Declare Variable final Completer<GoogleMapController> _controller = Completer(); LatLng? startLocation; @override Widget build(BuildContext context) { return Scaffold( Body: GoogleMap(zoomGesturesEnabled: true, initialCameraPosition: CameraPosition( target: startLocation ?? LatLng(0, 0), zoom: 16, ), markers: { if (startLocation != null) Marker( markerId: const MarkerId("startLocation"), position: startLocation ?? LatLng(0, 0), ), }, mapType: MapType.normal, onMapCreated: (mapController) { _controller.complete(mapController); }, ), ); }
Get current location:
Next, let’s get the user’s current location and update the map accordingly:
@override
void initState() {
_getCurrentLocation();
super.initState();
}
Future<Position> _checkPermission() async {
LocationPermission permission;
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.’);
}
return await Geolocator.getCurrentPosition();
}
void _getCurrentLocation() async {
Position position = await _checkPermission();
setState(() {
startLocation = LatLng(position.latitude, position.longitude);
updateMap();
});
}
void updateMap() async {
GoogleMapController googleMapController = await _controller.future;
var tmpZoom = await googleMapController.getZoomLevel();
googleMapController.animateCamera( CameraUpdate.newCameraPosition(
CameraPosition(target: startLocation ?? LatLng(0, 0),
zoom: tmpZoom,),
),);
}
Real-time Location Updates:
Let’s set up real-time location updates using the geolocator package:
//Declare Variable static const LocationSettings locationSettings = LocationSettings(accuracy: LocationAccuracy.high, distanceFilter: 50); StreamSubscription<Position>? currentPostionStream; bool isRouting = false; List<LatLng> arrPointer = []; void _getCurrentLocation() async { Position position = await _checkPermission(); setState(() { arrPointer = []; startLocation = LatLng(position.latitude, position.longitude); arrPointer.add(LatLng(position.latitude, position.longitude)); updateMap(); }); } void startRoutingLocation() async { currentPostionStream = Geolocator.getPositionStream(locationSettings: locationSettings) .listen((Position? position) async { print('loc updated - Listener ${position!.latitude},${position.longitude}'); setState(() { arrPointer.add(LatLng(position.latitude, position.longitude)); }); }); } void stopRoutingLocation() { if(currentPostionStream != null) { currentPostionStream?.cancel(); arrPointer.clear(); } }
Drawing Polyline:
Now, let’s draw the polylines to visualize the route on the map:
markers: { if (startLocation != null) Marker( markerId: const MarkerId("startLocation"), position: startLocation ?? LatLng(0, 0), ), if (arrPointer.length > 1) Marker( markerId: const MarkerId("endLocation"), position: arrPointer.last, ) }, polylines: { Polyline( polylineId: const PolylineId("route"), points: isRouting ? arrPointer : [], color: Colors.black, width: 4, ), },
Start and Stop Tracking Button:
Finally, we’ll add buttons to start and stop the live tracking feature:
//Declare Variable
bool isRouting = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: startLocation == null
? const Center(child: CircularProgressIndicator())
: Stack(
children: [GoogleMap(
zoomGesturesEnabled: true,
initialCameraPosition: CameraPosition(
target: startLocation ?? LatLng(0, 0), zoom: 16,
),
markers: {
if (startLocation != null)
Marker(
markerId: const MarkerId(“startLocation”),
position: startLocation ?? LatLng(0, 0),
),
if (arrPointer.length > 1)
Marker(
markerId: const MarkerId(“endLocation”),
position: arrPointer.last,
)
},
polylines: {
Polyline(
polylineId: const PolylineId(“route”),
points: isRouting ? arrPointer :
[],
color: Colors.black,
width: 4,
),
},
mapType: MapType.normal,
onMapCreated: (mapController) {
_controller.complete(mapController);
},
),
Padding(padding: EdgeInsets.only(top: MediaQuery.of(context).size.height – 100, left: 50,right: 50),
child: SizedBox(height: 50, child:
Row(children: [
Expanded(child: ElevatedButton(onPressed: (){
_getCurrentLocation();
startRoutingLocation();
setState(() {
isRouting = true;
});
}, child: const Text(“Start”))),
const SizedBox(width: 20,),
Expanded(child: ElevatedButton(onPressed: (){
stopRoutingLocation();
_getCurrentLocation();
setState(() {
isRouting = false;
});
}, child: const Text(“Stop”))),
],),
),
),
]),
);
}

Add Custom Marker:
To add a custom marker to the Google Map, follow these steps:
1). Declare the BitmapDescriptor variable in your widget’s state to hold the custom marker icon:
BitmapDescriptor markerIcon = BitmapDescriptor.defaultMarker;
2). In the initState() method, call the addCustomIcon() method to load the custom marker icon:
@override
void initState() {
_getCurrentLocation();
addCustomIcon();
super.initState();
}void addCustomIcon() {
BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(60, 60)), ‘assets/location_delivery.png’)
.then((icon) {
setState(() {
markerIcon = icon;
});
});
}
3). Use the markerIcon in the Marker widget’s icon property to apply the custom marker icon:
markers: {
if (startLocation != null)
Marker(
markerId: const MarkerId(“startLocation”),
position: startLocation ?? LatLng(0, 0),
),
if (arrPointer.length > 1)
Marker(
markerId: const MarkerId(“endLocation”),
position: arrPointer.last,icon: markerIcon, )
},
Note: Make sure to replace ‘assets/location_delivery.png’ with the correct path to your custom marker icon.

Additional information
For access to the full source code, please check the Live tracking Demo