Implement Real-time Location Updates on Google Maps in your Flutter Apps

Roman Jaquez
Flutter Community
Published in
9 min readDec 6, 2019

Move your Google Map pins Uber-style!

For those of you who want to develop the next generation of location-based apps, such as the next ride-hailing application, building it with Flutter couldn’t be easier, thanks to the help of available Flutter packages that facilitate its execution such as the Flutter Location Plugin that wraps the complexities of location handling from your device and exposes its capabilities in a simple to use API.

Let’s proceed!

Note: This post assumes you already have the maps set up in your project using the Google Maps Flutter Package, as well as have your own Google Maps API key. If not, follow this link on how to set up your Flutter project to work with Google Maps. Other dependencies include the Flutter Polyline Points package as well as the Flutter Location Plugin mentioned above. Github project for this tutorial here.

Initial Setup

Make sure you prep your environment accordingly in order to enable location tracking on both IOS and Android by following the steps in the package’s README regarding the Android manifest file and the iOS Info.plist.

Once setup, make sure your dependencies are correctly stated on your pubspec.yaml.

...
dependencies:
flutter:
sdk: flutter
google_maps_flutter: ^0.5.11+1
location: ^2.3.5

flutter_polyline_points: ^0.1.0
...

Note: As of this writing, the versions of the packages above were the ones available — please update accordingly.

Setting up the Map in a Stateful Widget

I defined a StatefulWidget called MapPage with its corresponding State class, where I imported the required packages, as well as some upfront constants I’ll be using throughout my widget, such as the camera related configurations (I wanted to give my map a tilted look and a specific zoom) plus some hardcoded source and destination location (for the sake of this tutorial).

import ‘package:flutter_polyline_points/flutter_polyline_points.dart’;
import ‘package:google_maps_flutter/google_maps_flutter.dart’;
import ‘package:location/location.dart’;
import ‘package:flutter/material.dart’;
import ‘dart:async’;
const double CAMERA_ZOOM = 16;
const double CAMERA_TILT = 80;
const double CAMERA_BEARING = 30;
const LatLng SOURCE_LOCATION = LatLng(42.747932,-71.167889);
const LatLng DEST_LOCATION = LatLng(37.335685,-122.0605916);
class MapPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => MapPageState();
}
class MapPageState extends State<MapPage> {... (rest of the code will live here) ...}

I also need references to my GoogleMapController, as well as a collection of markers that I’ll be updating frequently. I’ll also be showing some custom pin icons / markers as well as polylines to draw the route on my map between source and destination, and of course I’ll be needing my Google Maps API key.

Remember to enable Directions API, Maps SDK for Android and Maps SDK for iOS for your API key.

But the most important properties to pay attention to are the Location and LocationData properties below. The Location’s location property will hold a reference to the location information in a very encapsulated way, by providing a set of APIs to retrieve the user’s current location as well as hooks to obtain notifications to real-time changes in their location. The LocationData’s currentLocation is how the Location reference exposes the user’s location information, providing properties such as the lat, long, speed, accuracy, altitude, etc. I’ll also hold on to the destination location in a reference of type LocationData for consistency.

...(rest of the code omitted for brevity) ...Completer<GoogleMapController> _controller = Completer();
Set<Marker> _markers = Set<Marker>();
// for my drawn routes on the map
Set<Polyline> _polylines = Set<Polyline>();
List<LatLng> polylineCoordinates = [];
PolylinePoints polylinePoints;
String googleAPIKey = “<YOUR_API_KEY>”;// for my custom marker pins
BitmapDescriptor sourceIcon;
BitmapDescriptor destinationIcon;
// the user's initial location and current location
// as it moves
LocationData currentLocation;
// a reference to the destination location
LocationData destinationLocation;
// wrapper around the location API
Location location;

Let’s proceed to handle the initial state of this widget and set some listeners.

In the code below, I’m overriding the initState() as commonly done in a StatefulWidget, where I create an instance of Location so I can get the user’s location (setInitialLocation)and listen for updates on location changes (onLocationChanged). I also set upfront what my custom marker pins will look like for later use (setSourceAndDestinationIcons). Notice how by subscribing to the location’s onLocationChanged event I receive the current user’s position (the cLoc parameter of type LocationData) , from which I can later on extract the lat and long position and use it to move the pin across the map as the user moves in almost real time. We’ll look at the updatePinOnMap method further down below.

... (rest of the code omitted for brevity) ...@override
void initState() {
super.initState();

// create an instance of Location
location = new Location();
polylinePoints = PolylinePoints();

// subscribe to changes in the user's location
// by "listening" to the location's onLocationChanged event
location.onLocationChanged().listen((LocationData cLoc) {
// cLoc contains the lat and long of the
// current user's position in real time,
// so we're holding on to it
currentLocation = cLoc;
updatePinOnMap();
});
// set custom marker pins
setSourceAndDestinationIcons();
// set the initial location
setInitialLocation();
}
void setSourceAndDestinationIcons() async {
sourceIcon = await BitmapDescriptor.fromAssetImage(
ImageConfiguration(devicePixelRatio: 2.5),
'assets/driving_pin.png');

destinationIcon = await BitmapDescriptor.fromAssetImage(
ImageConfiguration(devicePixelRatio: 2.5),
'assets/destination_map_marker.png');
}
void setInitialLocation() async { // set the initial location by pulling the user's
// current location from the location's getLocation()
currentLocation = await location.getLocation();

// hard-coded destination for this example
destinationLocation = LocationData.fromMap({
"latitude": DEST_LOCATION.latitude,
"longitude": DEST_LOCATION.longitude
});
}

The onLocationChanged event fires as many times as the location receives a position change, and you can change the defaults by calling the location’s changeSettings method and changing values such as the accuracy (how accurate the location tracking measurement is; high by default), interval (how frequent the location is updated, 1 sec. by default, or in milliseconds ,1000) and distantFilter (in meters, by default 0, which ensures the location is being updated only if there’s a change in location within the meters value specified).

Now, let’s proceed to build our widget.

Inserting the Map in our Widget

Let’s override the build method, where the Google Map will be displayed full screen on our widget. I wrap my Google Map inside a Stack as the body of a Scaffold so as not to limit myself and to overlay things on top of my map (more on that later).

... (rest of the code omitted for brevity) ...@override
Widget build(BuildContext context) {
CameraPosition initialCameraPosition = CameraPosition(
zoom: CAMERA_ZOOM,
tilt: CAMERA_TILT,
bearing: CAMERA_BEARING,
target: SOURCE_LOCATION
);
if (currentLocation != null) {
initialCameraPosition = CameraPosition(
target: LatLng(currentLocation.latitude,
currentLocation.longitude),
zoom: CAMERA_ZOOM,
tilt: CAMERA_TILT,
bearing: CAMERA_BEARING
);
}
return Scaffold(
body: Stack(
children: <Widget>[
GoogleMap(
myLocationEnabled: true,
compassEnabled: true,
tiltGesturesEnabled: false,
markers: _markers,
polylines: _polylines,
mapType: MapType.normal,
initialCameraPosition: initialCameraPosition,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
// my map has completed being created;
// i'm ready to show the pins on the map
showPinsOnMap();
})

],
),
);
}
...

Notice how I set up the initial camera position based on the current user’s initial location if it’s available at the time the widget builds the Map (initialCameraPosition), otherwise I have a default start location to go with just in case. The piece of code to pay attention here is the onMapCreated event, which I handle by obtaining the reference to the Google Map’s controller, and since the map is ready to be manipulated when this gets called, I proceed to show my pins on the map (showPinsOnMap).

In the showPinsOnMap, all I’m doing is setting my markers to their initial positions as well as laying down the route lines on the map — more like prepping the stage for the main act: moving the marker along with the user upon the location changing.

void showPinsOnMap() {   // get a LatLng for the source location
// from the LocationData currentLocation object
var pinPosition = LatLng(currentLocation.latitude,
currentLocation.longitude);
// get a LatLng out of the LocationData object
var destPosition = LatLng(destinationLocation.latitude,
destinationLocation.longitude);
// add the initial source location pin
_markers.add(Marker(
markerId: MarkerId('sourcePin'),
position: pinPosition,
icon: sourceIcon
));
// destination pin
_markers.add(Marker(
markerId: MarkerId('destPin'),
position: destPosition,
icon: destinationIcon
));
// set the route lines on the map from source to destination
// for more info follow this tutorial
setPolylines();
}
...

For drawing my polylines / routes on the map, I’m using both LocationData references (currentLocation and destinationLocation) to get the complete route between those two points (follow this tutorial for more reference).

... (rest of the code omitted for brevity)...void setPolylines() async {   List<PointLatLng> result = await polylinePoints.getRouteBetweenCoordinates(
googleAPIKey,
currentLocation.latitude,
currentLocation.longitude,
destinationLocation.latitude,
destinationLocation.longitude
);
if(result.isNotEmpty){
result.forEach((PointLatLng point){
polylineCoordinates.add(
LatLng(point.latitude,point.longitude)
);
});
setState(() {
_polylines.add(Polyline(
width: 5, // set the width of the polylines
polylineId: PolylineId(“poly”),
color: Color.fromARGB(255, 40, 122, 198),
points: polylineCoordinates
));
});
}
}

Moving the Google Map Marker Upon Location Changing

Every movement the user makes within the boundaries specified within a period of time (based on accuracy, interval and distantFilter) will be captured by the device, and a notification will be triggered, exposed by the onLocationChanged event from the Location reference above.

If you recall, we wire up a handler to the onLocationChanged().listen() event, which yields a LocationData object every time there’s a change (this event gets triggered multiple times), containing the last recorded user’s location information. We hold on to it in the currentLocation property, and proceed to call the method updatePinOnMap.

...(rest of the code omitted for brevity)...void updatePinOnMap() async {

// create a new CameraPosition instance
// every time the location changes, so the camera
// follows the pin as it moves with an animation
CameraPosition cPosition = CameraPosition(
zoom: CAMERA_ZOOM,
tilt: CAMERA_TILT,
bearing: CAMERA_BEARING,
target: LatLng(currentLocation.latitude,
currentLocation.longitude
),
);
final GoogleMapController controller = await _controller.future;
controller.animateCamera(CameraUpdate.newCameraPosition(cPosition));
// do this inside the setState() so Flutter gets notified
// that a widget update is due
setState(() {
// updated position
var pinPosition = LatLng(currentLocation.latitude,
currentLocation.longitude)
;

// the trick is to remove the marker (by id)
// and add it again at the updated location
_markers.removeWhere(
(m) => m.markerId.value == ‘sourcePin’);
_markers.add(Marker(
markerId: MarkerId(‘sourcePin’),
position: pinPosition, // updated position
icon: sourceIcon
));
});
}

Let’s dissect it for a bit: the first thing we do upon executing the updatePinOnMap method is create a new CameraLocation object using the updated user’s location (currentLocation)and animating the camera so it moves along with the marker pin. If we don’t do this, we will see the pin just move out of view and we end up staring at a blank map.

Now, the whole trick to simulate the movement of the ping upon the location changing is removing the existing marker and adding it again using the updated coordinates. We remove it by finding it within our list of markers by id (sourcePin) so we don’t remove the destination pin or other pins we may decide to place on our map, then spawning a new one using the updated location coordinates and re-adding it to our markers’ collection. We do all this within the setState() method to notify Flutter that a widget rebuild is due.

Performance Considerations: We mentioned earlier the ways you can tweak how frequently you track your user’s location if performance is something you are concerned about. It will be based on your application’s needs and whether you want to compromise accuracy for performance (as long as you don’t drain your user’s battery in the process!

Other things to consider: Make sure to always implement requesting location permission explicitly from the user and implementing a smooth fallback mechanism for when the user declines sharing their location. This plugin tries to handle most cases for you, but it is always a good practice to implement it.

Bonus: Add Custom Window Upon Tapping on a Moving Marker

I implemented parts of another tutorial I wrote about adding a custom info window to a Google Map’s marker pin to this implementation, and it came out great! As the pin moved, I was able to invoke the custom info window and see the lat and long change in real time as I moved. I encourage you to use it as well if the need arises.

And that’s it! This is a very basic implementation of a location-based application that handles user’s location changes in real-time. A follow-up tutorial would be one where we reflect the user’s location updates on another device using a real-time backend such as Firebase and keep both in sync , so stay tuned!

Here’s the link to the Github Project for this tutorial for your reference.

Hope you have found this tutorial useful for your own projects.
Happy Fluttering!

--

--

Roman Jaquez
Flutter Community

Flutter GDE / GDG Lawrence Lead Organizer / Follow me on Twitter @drcoderz — Subscribe to my YouTube Channel https://tinyurl.com/romanjustcodes