Google Maps is an essential part of your app if you want to conveniently convey details of places, directions, routes to the end-user. There is an Official Google Maps plugin available for Flutter.
In this article, I will show you how to integrate Google Maps in Flutter to find a route between two places and calculate its distance.
The topics that I am going to cover in this article are:
- Create & setup a new Google Cloud Platform (GCP) project
- Add Google Maps SDK (for both Android & iOS) and Directions API to the GCP project
- Create an API Key on GCP
- Setup Flutter project to use Google Maps
- Add Google Maps widget
- Use Geocoding to translate coordinates into a place address and vice versa
- Use Polylines for drawing route between two places
- Calculate distance of that route
There's a lot to learn from this article. So, kindly bear with me and follow along.
You will need a GCP project for getting access to the Google Maps API and for generating the API key.
NOTE: If you have never used GCP before, you will have to setup a Billing account. You can follow the official guide here.
Follow the steps below for creating a new GCP project:
-
Go to GCP Console.
-
Go to Project Selection dialog-box and click New Project.
-
Enter a Project Name and click on Create.
This will create a new GCP Project.
In order to use Google Maps in your app, you will need to enable Maps SDK for both the platforms.
-
Go to APIs & Services from the left menu and select Library.
-
Now, search for Maps and enable Maps SDK for both the platforms.
-
You will also need the Directions API while drawing the routes. So, enable that also.
You will need an API key for integrating Google Maps to your app.
-
Go to APIs & Services from the left menu and select Credentials.
-
Click CREATE CREDENTIALS and select API key.
This will generate an API key for the project, you will need this in the next step.
-
Create a new Flutter project.
flutter create flutter_maps
-
Open the project using your favorite IDE. For opening with VS Code:
code flutter_maps
-
Add the google_maps_flutter plugin to the
pubspec.yaml
file:google_maps_flutter: ^0.5.28+1
-
Navigate to the file
android/app/src/main/AndroidManifest.xml
and add the following code snippet inside theapplication
tag:<!-- Add your Google Maps API Key here --> <meta-data android:name="com.google.android.geo.API_KEY" android:value="YOUR KEY HERE"/>
-
Also, you will need location access in the app. So, add the following permission in the same file inside the
manifest
tag:<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
-
Navigate to the file
ios/Runner/AppDelegate.swift
and replace the whole code with the following:import UIKit import Flutter import GoogleMaps @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { //Add your Google Maps API Key here GMSServices.provideAPIKey("YOUR KEY HERE") GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }
-
Also, add the following to
ios/Runner/Info.plist
file:<key>io.flutter.embedded_views_preview</key> <string>YES</string>
-
For getting location permission, add the following to the same file:
<key>NSLocationWhenInUseUsageDescription</key> <string>This app needs access to location when open.</string>
This completes the setup for both the platforms in Flutter.
Now, you are ready to add Google Maps Widget to your Flutter app.
The starting code that you can use is as follows:
https://gist.github.com/22969957a5ba8bacbe847b514b8624c3
I have defined the Container
height and width to be the size of the screen so that the Google Maps widget takes up the entire space of the screen.
Also, I am using a Stack
to keep Google Maps widget in the background and add other necessary widgets on top of it.
Now, replace the TODO
in the above code snippet with the Google Maps widget.
https://gist.github.com/b2a991dc5d3e4f977565140d668ae7dc
Let's take a look at the parameters that I have defined in the Google Maps widget:
- initialCameraPosition: This is a required parameter that is used for loading the Map view on initial startup.
- myLocationEnabled: For showing your current location on Map with a blue dot.
- myLocationButtonEnabled: Button used for bringing the user location to the center of the camera view.
- mapType: For specifying the type of map to display (normal, satellite, hybrid & terrain).
- zoomGesturesEnabled: Whether the map view should respond to zoom gestures.
- zoomControlsEnabled: Whether to show zoom controls (only applicable for Android platform).
- onMapCreated: Callback for when the map is ready to use.
I have set the myLocationButtonEnabled
and zoomControlsEnabled
to false because I am going to show you how to define a custom button with the same functionality but with better control.
The mapController
would be used for controlling the camera position of the Map view.
The app will look like this now:
For displaying the zoom buttons and the current location button you can add them as a child to the Stack
widget and position them accordingly.
The code for design of a button is given below:
https://gist.github.com/399a5b21175238789ebc11a83cc335c3
The remaining buttons have a similar design.
You can take a look at the code snippet for the design and positioning of the zoom buttons here and current location button here.
You can use the mapController
to perform a simple Zoom In and Zoom Out action.
https://gist.github.com/9723f9e672698f38e4914ed5e8b6f263
For moving to a new position you can use the following code snippet:
https://gist.github.com/ee429c0cae322924a351583b21c5c49e
Here, I have specified only target & zoom property of the CameraPosition widget. But there are two more properties, bearing & tilt that you may use.
In the target property, you have to pass the latitude and longitude of the position you want to move.
Here, we need the Current Location button to move the camera view to the user's present location. Let's see how to achieve that.
After adding the buttons, the app will look like this:
There is a nice plugin for Flutter, known as geolocator that can help you to fetch user's current location easily.
Add it to your pubspec.yaml
file:
https://gist.github.com/6c9230912fd152a1418c2190a7c1e219
-
Initialize Geolocator and define a variable.
final Geolocator _geolocator = Geolocator(); // For storing the current position Position _currentPosition;
-
Get the current location of the user.
// Method for retrieving the current location _getCurrentLocation() async { await _geolocator .getCurrentPosition(desiredAccuracy: LocationAccuracy.high) .then((Position position) async { setState(() { // Store the position in the variable _currentPosition = position; print('CURRENT POS: $_currentPosition'); // For moving the camera to current location mapController.animateCamera( CameraUpdate.newCameraPosition( CameraPosition( target: LatLng(position.latitude, position.longitude), zoom: 18.0, ), ), ); }); }).catchError((e) { print(e); }); }
-
Add this method to the
initState
in order to fetch the current location of the user as soon as the app launches, and also move the camera to the detected location.@override void initState() { super.initState(); _getCurrentLocation(); }
-
Also pass the latitude & longitude to the
onTap
method of the custom button for fetching user's current location.
After completing these, the camera will automatically move to the detected location as the app launches.
As you have the initial app setup and working, now you can move on to adding two places and detecting their route on Map. For this, you will need the latitude and longitude of the two places. But, for any user knowing the geographic coordinates of a place is a hard task. So, here comes Geocoding.
Geocoding is a technique by which the address of a place can be converted to their coordinates (latitude & longitude) and vice versa.
The Flutter package geolocator also supports Geocoding. For getting started with geocoding, you will need to take the addresses as user inputs using TextField
.
You can find the code for the design of the TextField here.
If you want to show the address of the starting location, then you have to define a TextEditingController
for the starting address and update the text as soon as the address of the user's present location is retrieved.
https://gist.github.com/513bcb959c23b83181da5e1dd93bb133
You can retrieve the address of the starting location by using the following method:
https://gist.github.com/87b855a5d459dd69ee2c67faea8fa8f0
You can use a similar method for retrieving the coordinates from the starting address and the destination address. You will require these coordinates in order to draw a route on the Map and for placing Markers.
https://gist.github.com/76dbe3ef7e25a4cecb5cdcf96d8f3209
You can use the coordinates retrieved in the previous step for placing Markers on the Map.
-
First of all, define a variable for storing the Markers:
Set<Marker> markers = {};
-
Create the Markers:
// Start Location Marker Marker startMarker = Marker( markerId: MarkerId('$startCoordinates'), position: LatLng( startCoordinates.latitude, startCoordinates.longitude, ), infoWindow: InfoWindow( title: 'Start', snippet: _startAddress, ), icon: BitmapDescriptor.defaultMarker, ); // Destination Location Marker Marker destinationMarker = Marker( markerId: MarkerId('$destinationCoordinates'), position: LatLng( destinationCoordinates.latitude, destinationCoordinates.longitude, ), infoWindow: InfoWindow( title: 'Destination', snippet: _destinationAddress, ), icon: BitmapDescriptor.defaultMarker, );
-
Add the Markers:
// Add the markers to the list markers.add(startMarker); markers.add(destinationMarker);
-
Display the Markers on the Map:
// Add the markers property to the widget GoogleMap( markers: markers != null ? Set<Marker>.from(markers) : null, // ... ),
If you run the app now, it will look like this:
You will notice that only one of the Markers is visible, although you had placed two Markers, one at the starting point and the other at the destination point.
So, what's happening here?
Actually, both the Markers are added to the Map, but only one of them is visible because the other one is outside the Map View. If you zoom out a bit, you will notice the other one too.
You can use the following code snippet for reorienting the Map View to accommodate both the Markers.
https://gist.github.com/ec9402f37dec341dd8401e6fdce3ca26
Polyline is used for drawing routes on the Google Maps. A Polyline is a list of points, where line segments are drawn between consecutive points.
There is a Flutter package available for drawing polylines, known as flutter_polyline_points. It uses the Directions API that you had enabled in the beginning.
-
Add the package to your
pubspec.yaml
file.flutter_polyline_points: ^0.2.1
-
Define some variables.
// Object for PolylinePoints PolylinePoints polylinePoints; // List of coordinates to join List<LatLng> polylineCoordinates = []; // Map storing polylines created by connecting // two points Map<PolylineId, Polyline> polylines = {};
-
Define a method for creating the Polylines. You have to pass the starting and destination positions.
// Create the polylines for showing the route between two places _createPolylines(Position start, Position destination) async { // Initializing PolylinePoints polylinePoints = PolylinePoints(); // Generating the list of coordinates to be used for // drawing the polylines PolylineResult result = await polylinePoints.getRouteBetweenCoordinates( Secrets.API_KEY, // Google Maps API Key PointLatLng(start.latitude, start.longitude), PointLatLng(destination.latitude, destination.longitude), travelMode: TravelMode.transit, ); // Adding the coordinates to the list if (result.points.isNotEmpty) { result.points.forEach((PointLatLng point) { polylineCoordinates.add(LatLng(point.latitude, point.longitude)); }); } // Defining an ID PolylineId id = PolylineId('poly'); // Initializing Polyline Polyline polyline = Polyline( polylineId: id, color: Colors.red, points: polylineCoordinates, width: 3, ); // Adding the polyline to the map polylines[id] = polyline; }
-
Display polyline on the Map.
// Add the polylines property to the widget GoogleMap( polylines: Set<Polyline>.of(polylines.values), // ... ),
After drawing the route, the app will look like this:
Now, you have arrived at the last phrase of this project. With the markers properly positioned and the route drawn, you might want to calculate the distance between the two locations.
There is a method that comes with the geolocator package using which you can calculate the distance between two places just by providing their coordinates.
https://gist.github.com/fcd45637b9d0f3f41c9d8517897bda82
Yes, this method does not calculate the distance of the actual route, rather it uses the Haversine formula to calculate the distance based upon the supplied GPS coordinates.
More information about this here.
So, what you can do is break the entire route into several small parts and calculate their distances, then sum them up. Remember, you have already calculated the coordinates while drawing the polyline. You can use these coordinates to calculate the total distance of the route.
The formula that I have used for calculating the distance between two geographic coordinates is as follows:
https://gist.github.com/26e6faa8d703fcefc704fad4d2eba492
Now, you can use this formula to calculate the distances between two coordinates and sum them up to find the total distance.
https://gist.github.com/7253762591e4431c65943a08fa192c70
Show the calculated distance on the UI:
https://gist.github.com/f60989f63452df7e1e0c2310638bf6fe
The final app will look like this:
There's a lot more you can do using Google Maps and some of its supported APIs. I hope this article will help you to get started with Google Maps in Flutter.