Route calculator: How to create it by using Google Maps in Flutter

If you want to conveniently convey details of places, directions and route calculator to the end-user, Google Maps is an essential part of your app. Luckily, there is an official Google Maps plugin available for Flutter. In this article, we will show you how to create route calculator by using Google Maps in Flutter.

The route calculator topic includes:

  • 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 bear with me and follow along.

Creating a GCP project

To start route calculator, you will need a GCP project to get access to the Google Maps API and generate an API key.

To create a new GCP project, follow the steps below:

  1. Go to the GCP Console.
  2. Go to the Project Selection dialog box and click New Project.
route calculator

3. Enter a name for your project and click Create.

This will create a new GCP Project.

Enabling APIs

In order to use Google Maps in your app, you will need to enable Maps SDK for both platforms.

  1. Go to APIs & Services from the left menu and select Library.
  2. Now, search for Maps and enable Maps SDK for both platforms.
  3. You will also need the Directions API while drawing the routes. So enable that as well.

Generating an API key

You will need an API key for integrating Google Maps with your app.

  1. Go to APIs & Services from the left menu and select Credentials.
  2. Click CREATE CREDENTIALS and select API key.

This will generate an API key that you will need in the next step.

Set up the Flutter project

  • 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

Android setup

Navigate to the file android/app/src/main/AndroidManifest.xml and add the following code snippet inside the application tag:

<!-- Add your Google Maps API Key here -->

<meta-data android:name="com.google.android.geo.API_KEY"

android:value="YOUR KEY HERE"/>

You will also need the location access in the app. Add the following permission in the same file inside the manifest tag:<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

iOS setup

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 the ios/Runner/Info.plist file:

<key>io.flutter.embedded_views_preview</key> <string>YES</string> 

To get the 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 platforms in Flutter.

Integrating the Google Maps Widget

Now you are ready to add the Google Maps widget to your Flutter app.

You can start with the following code:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Maps',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MapView(),
    );
  }
}

class MapView extends StatefulWidget {
  @override
  _MapViewState createState() => _MapViewState();
}

class _MapViewState extends State<MapView> {
  @override
  Widget build(BuildContext context) {
    // Determining the screen width & height
    var height = MediaQuery.of(context).size.height;
    var width = MediaQuery.of(context).size.width;

    return Container(
      height: height,
      width: width,
      child: Scaffold(
        body: Stack(
          children: <Widget>[
            // TODO: Add Map View
          ],
        ),
      ),
    );
  }
}

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 screen.

I am also 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.

// Import the Google Maps package
import 'package:google_maps_flutter/google_maps_flutter.dart';

// Initial location of the Map view
CameraPosition _initialLocation = CameraPosition(target: LatLng(0.0, 0.0));

// For controlling the view of the Map
GoogleMapController mapController;

// Replace the "TODO" with this widget
GoogleMap(
  initialCameraPosition: _initialLocation,
  myLocationEnabled: true,
  myLocationButtonEnabled: false,
  mapType: MapType.normal,
  zoomGesturesEnabled: true,
  zoomControlsEnabled: false,
  onMapCreated: (GoogleMapController controller) {
    mapController = controller;
  },
),

Let’s take a look at the parameters defined in the Google Maps widget:

  • initialCameraPosition: This is a required parameter that is used for loading the map view on initial start-up.
  • myLocationEnabled: For showing your current location on the map with a blue dot.
  • myLocationButtonEnabled: This button is used to bring the user location to the center of the camera view.
  • mapType: For specifying the displayed map type (normal, satellite, hybrid or terrain).
  • zoomGesturesEnabled: Whether the map view should respond to zoom gestures.
  • zoomControlsEnabled: Whether to show zoom controls (only applicable for Android).
  • onMapCreated: Callback for when the map is ready to use.

I have set the myLocationButtonEnabled and zoomControlsEnabled parameters as false because I am going to show you how to define a custom button with the same functionality but with better control.

mapController would be used to control the camera position of the map view.

By now, the app looks like this:

route calculator

To display the zoom buttons and the current location button you can add them as children to the Stack widget and position them accordingly.

The code for designing a button is given below:

// Design for current location button
 
ClipOval(
  child: Material(
    color: Colors.orange[100], // button color
    child: InkWell(
      splashColor: Colors.orange, // inkwell color
      child: SizedBox(
        width: 56,
        height: 56,
        child: Icon(Icons.my_location),
      ),
      onTap: () {
        // TODO: Add the operation to be performed
        // on button tap
      },
    ),
  ),
),

Other buttons have a similar design.

Using zoom in the map view

You can use the mapController to zoom in and out on a map.

// Zoom In action
mapController.animateCamera(
  CameraUpdate.zoomIn(),
);

// Zoom Out action
mapController.animateCamera(
  CameraUpdate.zoomOut(),
);

Move to a new position

You can use the following code snippet to move to a new position:

// Move camera to the specified latitude & longitude
mapController.animateCamera(
  CameraUpdate.newCameraPosition(
    CameraPosition(
      target: LatLng(
        // Will be fetching in the next step
        _currentPosition.latitude,
        _currentPosition.longitude,
      ),
      zoom: 18.0,
    ),
  ),
);

Here, I have only specified the target and zoom properties of the CameraPosition widget. There are two more properties, bearing and tilt that you can use in a similar manner.

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:

route calculator

Fetching current location

There is a nice plugin for Flutter called Geolocator that can help you fetch the user’s current location.

Add it to your pubspec.yaml file:

geolocator: ^5.3.1

Now let’s get started and go through it step-by-step.

1. Initialize Geolocator and define a variable

final Geolocator _geolocator = Geolocator();
// For storing the current position
Position _currentPosition;

2. 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);
});
}

3. Add this method to the initState to fetch the user’s current location as soon as the app launches, and to move the camera to the detected location.

@override
void initState() {
super.initState();
_getCurrentLocation();
}

Also pass the latitude and longitude to the onTap method of the custom button for fetching the current location.

After completing these, the camera will automatically move to the detected location as the app launches.

Geocoding

When you have the initial app set up and working, you can move on to adding two places and finding the suitable route between them on the map. For this, you will need the latitude and longitude of these places. Since figuring out the geographic coordinates can be pretty complicated, we will use geocoding.

Geocoding is a technique by which the address of a place can be converted into coordinates (latitude and longitude) and vice versa.

The Flutter package called geolocator also supports geocoding. To get started, you need to insert the addresses as user inputs using TextField.

If you want to show the address of the starting location, you have to define a TextEditingController for the starting address and update the text as soon as the address of the user’s current location is retrieved.

final startAddressController = TextEditingController();

You can retrieve the address by using the following method:

// Method for retrieving the address

_getAddress() async {
  try {
    // Places are retrieved using the coordinates
    List<Placemark> p = await _geolocator.placemarkFromCoordinates(
        _currentPosition.latitude, _currentPosition.longitude);

    // Taking the most probable result
    Placemark place = p[0];

    setState(() {
    
      // Structuring the address
      _currentAddress =
          "${place.name}, ${place.locality}, ${place.postalCode}, ${place.country}";
      
      // Update the text of the TextField
      startAddressController.text = _currentAddress;

      // Setting the user's present location as the starting address
      _startAddress = _currentAddress;
    });
  } catch (e) {
    print(e);
  }
}

You can use a similar method to retrieve the coordinates from the starting address and the destination address. You will need these coordinates to draw a route on the map and to place markers.

// Getting the placemarks
List<Placemark> startPlacemark =
    await _geolocator.placemarkFromAddress(_startAddress);
List<Placemark> destinationPlacemark =
    await _geolocator.placemarkFromAddress(_destinationAddress);

// Retrieving coordinates
Position startCoordinates = startPlacemark[0].position;
Position destinationCoordinates = destinationPlacemark[0].position;

Placing Markers

You can use the coordinates retrieved in the previous step to place 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 placed two of them, one at the starting point and the other at the destination.

So what’s happening here?

Actually, both markers are added to the map. But only one of them is visible because the other one is out of the view. If you zoom out a bit, you will notice the other one too.

You can use the following code snippet to reorient the map view to accommodate both markers.

// Define two position variables
Position _northeastCoordinates;
Position _southwestCoordinates;

// Calculating to check that
// southwest coordinate <= northeast coordinate
if (startCoordinates.latitude <= destinationCoordinates.latitude) {
  _southwestCoordinates = startCoordinates;
  _northeastCoordinates = destinationCoordinates;
} else {
  _southwestCoordinates = destinationCoordinates;
  _northeastCoordinates = startCoordinates;
}

// Accommodate the two locations within the
// camera view of the map
mapController.animateCamera(
  CameraUpdate.newLatLngBounds(
    LatLngBounds(
      northeast: LatLng(
        _northeastCoordinates.latitude,
        _northeastCoordinates.longitude,
      ),
      southwest: LatLng(
        _southwestCoordinates.latitude,
        _southwestCoordinates.longitude,
      ),
    ),
    100.0, // padding 
  ),
);

Drawing a route

Polyline is used for drawing routes on Google Maps. It 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 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:

Calculating Distance

You have arrived to the last phase of route calculator 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 that is helpful here. You can use it to make route calculator the distance between two places just by providing their coordinates.

// Calculating the distance between the start and end positions
// with a straight path, without considering any route

double distanceInMeters = await Geolocator().distanceBetween(
  startCoordinates.latitude,
  startCoordinates.longitude,
  destinationCoordinates.latitude,
  destinationCoordinates.longitude,
);

However, this method does not calculate the distance of the actual route. It uses the Haversine formula to calculate the distance based upon the supplied GPS coordinates.

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 used for calculating the distance between two geographic coordinates is as follows:

import 'dart:math' show cos, sqrt, asin;

double _coordinateDistance(lat1, lon1, lat2, lon2) {
    var p = 0.017453292519943295;
    var c = cos;
    var a = 0.5 -
        c((lat2 - lat1) * p) / 2 +
        c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p)) / 2;
    return 12742 * asin(sqrt(a));
  }

Now you can use this formula to calculate the distances between two coordinates and sum them up to find the total distance.

double totalDistance = 0.0;

// Calculating the total distance by adding the distance
// between small segments
for (int i = 0; i < polylineCoordinates.length - 1; i++) {
  totalDistance += _coordinateDistance(
    polylineCoordinates[i].latitude,
    polylineCoordinates[i].longitude,
    polylineCoordinates[i + 1].latitude,
    polylineCoordinates[i + 1].longitude,
  );
}

// Storing the calculated total distance of the route
setState(() {
  _placeDistance = totalDistance.toStringAsFixed(2);
  print('DISTANCE: $_placeDistance km');
});

Show the calculated distance on the UI:

Visibility(
  visible: _placeDistance == null ? false : true,
  child: Text(
    'DISTANCE: $_placeDistance km',
    style: TextStyle(
      fontSize: 16,
      fontWeight: FontWeight.bold,
    ),
  ),
),

The final app will look like this:

route calculator

Conclusion

There’s a lot more you can do using Google Maps and some of its supported route calculator. I hope this article will help you to get started with Google Maps in Flutter.

Want to work with web and mobile app development company?

A web and mobile development company is the best option if you’re planning a mid-sized or large project but don’t have your own team. To clarify, the biggest advantage of a software development company is that they can take ownership of the whole project.

If you’re considering web development services, you can explore more about us – ArrowHiTech. Above all, we are proud to be one of the most well-established outsourcing companies all around the world. In addition, with over 12 years of experience, we can provide the best software development services for your eCommerce business.

So, you can check us out now: ArrowHiTech services. Also, if you want to go through our showcase, you should visit our product page here: MageSolution or our portfolio: ArrowHiTech portfolio.

ArrowHiTech services

So, if you have any questions, don’t hesitate to contact us via:

  • Email: support@arrowhitech.com
  • Phone number: +84 243 7955 813.