Search This Blog

Sunday, August 26, 2018

Flutter - How can draw route on google map between markers.

Route on google map is the easiest way to reach somewhere. Just enter the address of your source and destination point. After that, you will see the right way to go there and you can control the route map.  Google maps highlight the suggested route in a bright blue color and include other possible routes in gray. It is always safe to use a driving map when you are not aware of the place.

In this post, we'll create a Flutter application for draw route on google map from your current position to the destination address. To get current location, we will use GPS handler plugin and for search destination address, we'll use places API. The final output of the project will be like below.

In our previous post, we have seen the basic setup and implementation of google map plugin in the Flutter Application. If you have never used google map in Flutter. So, you should read our previous post. In this post, we'll not discuss the basic part of google map. We assuming here, you have a google map key with enabled google map API for Android and Ios.

So, let start it and see, how we can draw the route on google map in a Flutter Application. 

Creating a new Project
1. Create a new project from File ⇒ New Flutter Project with your development IDE.

2. Include required following dependency in the pubspec.yaml.
location: ^1.4.1
map_view: "^0.0.14"
google_maps_webservice: ^0.0.6
  • location: ^1.4.1 will manage GPS of the device and get current user position.
  • map_view: "^0.0.14" will display google map. We have discussed it in our previous post
  • We using google_maps_webservice: ^0.0.6 for get google places. By using this, we'll able to get destination Address position.   
3. Open main.dart file and edit it. As we have set our theme and change debug banner property of Application.
import 'package:flutter/material.dart'; import 'package:flutter_google_map_route/map_screen.dart'; import 'package:map_view/map_view.dart'; void main() { MapView.setApiKey("Google_API_Key"); runApp(new MaterialApp( debugShowCheckedModeBanner: false, theme: new ThemeData( primaryColor: const Color(0xFF02BB9F), primaryColorDark: const Color(0xFF167F67), accentColor: const Color(0xFF167F67), ), home: new MapScreen(), )); }

4. After that create home screen map_screen.dart of the application and start designing it. As you can see above, we have created a destination address widget for search destination location that we'll use for draw route on google map.
new GestureDetector( onTap: () { googlePlaces.findPlace(context); }, child: new Container( alignment:, margin: EdgeInsets.all(10.0), padding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0), decoration: new BoxDecoration( color: const Color.fromRGBO(255, 255, 255, 1.0), border: Border.all(color: const Color(0x33A6A6A6)), borderRadius: new BorderRadius.all(const Radius.circular(6.0)), ), child: new Row( children: <Widget>[ new Icon(, new Flexible( child: new Container( padding: new EdgeInsets.only(right: 13.0), child: new Text( locationAddress, overflow: TextOverflow.ellipsis, style: new TextStyle(color:, ), ), ), ], ), ), ),
now create another widget that will display static google map image.
new Container( height: 230.0, child: new Stack( children: <Widget>[ new Center( child: Container( child: new Text( "Google Map Box", textAlign:, ), padding: const EdgeInsets.all(20.0), ), ), new GestureDetector( onTap: () => mapUtil.showMap(), child: new Center( child: new, ), ), ], ), ),
as you can see the final map_screen.dart file below. We have used a Text and Button for get route steps from google API.
import 'package:flutter/material.dart'; import 'package:flutter_google_map_route/progress_hud.dart'; import 'package:flutter_google_map_route/utils/google_place_util.dart'; import 'package:flutter_google_map_route/utils/map_util.dart'; import 'package:map_view/map_view.dart'; class MapScreen extends StatefulWidget { @override _MapScreenState createState() => new _MapScreenState(); } class _MapScreenState extends State<MapScreen> implements ScreenListener, GooglePlacesListener { MapUtil mapUtil; String locationAddress = "Search destination"; String myLocation = ""; GooglePlaces googlePlaces; bool _isLoading = false; double _destinationLat; double _destinationLng; @override void initState() { super.initState(); mapUtil = new MapUtil(this); mapUtil.init(); googlePlaces = new GooglePlaces(this); } @override Widget build(BuildContext context) { var screenWidget = new Column( children: <Widget>[ new GestureDetector( onTap: () { googlePlaces.findPlace(context); }, child: new Container( alignment:, margin: EdgeInsets.all(10.0), padding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0), decoration: new BoxDecoration( color: const Color.fromRGBO(255, 255, 255, 1.0), border: Border.all(color: const Color(0x33A6A6A6)), borderRadius: new BorderRadius.all(const Radius.circular(6.0)), ), child: new Row( children: <Widget>[ new Icon(, new Flexible( child: new Container( padding: new EdgeInsets.only(right: 13.0), child: new Text( locationAddress, overflow: TextOverflow.ellipsis, style: new TextStyle(color:, ), ), ), ], ), ), ), new Container( height: 230.0, child: new Stack( children: <Widget>[ new Center( child: Container( child: new Text( "Google Map Box", textAlign:, ), padding: const EdgeInsets.all(20.0), ), ), new GestureDetector( onTap: () => mapUtil.showMap(), child: new Center( child: new, ), ), ], ), ), new Container( margin: new EdgeInsets.fromLTRB(0.0, 20.0, 0.0, 0.0), padding: new EdgeInsets.only(top: 10.0), child: new Text( myLocation, style: new TextStyle(fontWeight: FontWeight.bold), ), ), new GestureDetector( onTap: () => getMapRoute(), child: new Container( margin: EdgeInsets.fromLTRB(30.0, 30.0, 30.0, 0.0), padding: EdgeInsets.all(15.0), alignment:, decoration: new BoxDecoration( color: const Color(0xFFFFD900), borderRadius: new BorderRadius.all(const Radius.circular(6.0)), ), child: Text( "Draw Route", style: new TextStyle( color: const Color(0xFF28324E), fontSize: 20.0, fontWeight: FontWeight.bold), ), ), ), ], ); return new Scaffold( backgroundColor: const Color(0xFFA6AFAA), appBar: AppBar( title: new Text( "Google maps route", textAlign:, style: new TextStyle( fontWeight: FontWeight.bold, color: Colors.white, ), ), ), body: ProgressHUD( child: new SingleChildScrollView( child: screenWidget, ), inAsyncCall: _isLoading, opacity: 0.0, ), ); } Widget getTextField( String inputBoxName, TextEditingController inputBoxController) { var loginBtn = new Padding( padding: const EdgeInsets.all(5.0), child: new TextFormField( controller: inputBoxController, decoration: new InputDecoration( hintText: inputBoxName, ), ), ); return loginBtn; } Widget getButton(String buttonLabel, EdgeInsets margin) { var staticMapBtn = new Container( margin: margin, padding: EdgeInsets.all(8.0), alignment:, decoration: new BoxDecoration( color: const Color(0xFF167F67), border: Border.all(color: const Color(0xFF28324E)), borderRadius: new BorderRadius.all(const Radius.circular(6.0)), ), child: new Text( buttonLabel, style: new TextStyle( color: const Color(0xFFFFFFFF), fontSize: 20.0, fontWeight: FontWeight.w300, letterSpacing: 0.3, ), ), ); return staticMapBtn; } updateStaticMap() { setState(() {}); } @override updateScreen(Location location) { myLocation = "You are at: " + location.latitude.toString() + ", " + location.longitude.toString(); googlePlaces.updateLocation(location.latitude, location.longitude); setState(() {}); } @override selectedLocation(double lat, double lng, String address) { setState(() { _destinationLat = lat; _destinationLng = lng; locationAddress = address; }); } getMapRoute() { setState(() { _isLoading = true; }); mapUtil.getDirectionSteps(_destinationLat, _destinationLng); } @override dismissLoader() { setState(() { _isLoading = false; }); } } abstract class ScreenListener { updateScreen(Location location); dismissLoader(); }
in the above class, we have created instances of the util class for manage map and GPS in the init method. We have a ScreenListener abstract class that will update map_screen.dart widget.

5. Now create flutter_google_places_autocomplete.dart file. It will manage destination widget dialog on the map_screen.dart.
library flutter_google_places_autocomplete.src; import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_webservice/places.dart'; class GooglePlacesAutocompleteWidget extends StatefulWidget { final String apiKey; final String hint; final Location location; final num offset; final num radius; final String language; final List<String> types; final List<Component> components; final bool strictbounds; final ValueChanged<PlacesAutocompleteResponse> onError; GooglePlacesAutocompleteWidget( {@required this.apiKey, this.hint = "Search", this.offset, this.location, this.radius, this.language, this.types, this.components, this.strictbounds, this.onError, Key key}) : super(key: key); @override State<GooglePlacesAutocompleteWidget> createState() { return new _GooglePlacesAutocompleteOverlayState(); } static GooglePlacesAutocompleteState of(BuildContext context) => context .ancestorStateOfType(const TypeMatcher<GooglePlacesAutocompleteState>()); } class _GooglePlacesAutocompleteOverlayState extends GooglePlacesAutocompleteState { @override Widget build(BuildContext context) { final header = new Column(children: <Widget>[ new Material( child: new Row( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ new IconButton( color: Colors.black45, icon: new Icon(Icons.arrow_back), onPressed: () { Navigator.pop(context); }, ), new Expanded( child: new Padding( child: _textField(), padding: const EdgeInsets.only(right: 8.0), )), ], )), new Divider( //height: 1.0, ) ]); var body; if (query.text.isEmpty || response == null || response.predictions.isEmpty) { body = new Material( color: Colors.white, borderRadius: new BorderRadius.only( bottomLeft: new Radius.circular(2.0), bottomRight: new Radius.circular(2.0)), ); } else { body = new SingleChildScrollView( child: new Material( borderRadius: new BorderRadius.only( bottomLeft: new Radius.circular(2.0), bottomRight: new Radius.circular(2.0)), color: Colors.white, child: new ListBody( children: response.predictions .map((p) => new PredictionTile( prediction: p, onTap: Navigator.of(context).pop)) .toList()))); } final container = new Container( margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 30.0), child: new Stack(children: <Widget>[ header, new Padding(padding: new EdgeInsets.only(top: 48.0), child: body), ])); if (Platform.isIOS) { return new Padding( padding: new EdgeInsets.only(top: 8.0), child: container); } return container; } Widget _textField() => new TextField( controller: query, autofocus: true, decoration: new InputDecoration( hintText: widget.hint, hintStyle: new TextStyle(color: Colors.black54, fontSize: 16.0), border: null), onChanged: search, ); } class GooglePlacesAutocompleteResult extends StatefulWidget { final ValueChanged<Prediction> onTap; GooglePlacesAutocompleteResult({this.onTap}); @override _GooglePlacesAutocompleteResult createState() => new _GooglePlacesAutocompleteResult(); } class _GooglePlacesAutocompleteResult extends State<GooglePlacesAutocompleteResult> { @override Widget build(BuildContext context) { final state = GooglePlacesAutocompleteWidget.of(context); assert(state != null); if (state.query.text.isEmpty || state.response == null || state.response.predictions.isEmpty) { final children = <Widget>[]; return new Stack(children: children); } return new PredictionsListView( predictions: state.response.predictions, onTap: widget.onTap); } } class PredictionsListView extends StatelessWidget { final List<Prediction> predictions; final ValueChanged<Prediction> onTap; PredictionsListView({@required this.predictions, this.onTap}); @override Widget build(BuildContext context) { return new ListView( children: predictions .map((Prediction p) => new PredictionTile(prediction: p, onTap: onTap)) .toList()); } } class PredictionTile extends StatelessWidget { final Prediction prediction; final ValueChanged<Prediction> onTap; PredictionTile({@required this.prediction, this.onTap}); @override Widget build(BuildContext context) { return new ListTile( leading: new Icon(Icons.location_on), title: new Text(prediction.description), onTap: () { if (onTap != null) { onTap(prediction); } }, ); } } Future<Prediction> showGooglePlacesAutocomplete( {@required BuildContext context, @required String apiKey, String hint = "Search", num offset, Location location, num radius, String language, List<String> types, List<Component> components, bool strictbounds, ValueChanged<PlacesAutocompleteResponse> onError}) { final builder = (BuildContext ctx) => new GooglePlacesAutocompleteWidget( apiKey: apiKey, language: language, components: components, types: types, location: location, radius: radius, strictbounds: strictbounds, offset: offset, hint: hint, onError: onError, ); return showDialog(context: context, builder: builder); } abstract class GooglePlacesAutocompleteState extends State<GooglePlacesAutocompleteWidget> { TextEditingController query; PlacesAutocompleteResponse response; GoogleMapsPlaces _places; bool searching; @override void initState() { super.initState(); query = new TextEditingController(text: ""); _places = new GoogleMapsPlaces(widget.apiKey); searching = false; } Future<Null> doSearch(String value) async { if (mounted && value.isNotEmpty) { setState(() { searching = true; }); final res = await _places.autocomplete(value, offset: widget.offset, location: widget.location, radius: widget.radius, language: widget.language, types: widget.types, components: widget.components, strictbounds: widget.strictbounds); if (res.errorMessage?.isNotEmpty == true || res.status == "REQUEST_DENIED") { onResponseError(res); } else { onResponse(res); } } else { onResponse(null); } } Timer _timer; Future<Null> search(String value) async { _timer?.cancel(); _timer = new Timer(const Duration(milliseconds: 300), () { _timer.cancel(); doSearch(value); }); } @override void dispose() { _timer?.cancel(); _places.dispose(); super.dispose(); } @mustCallSuper void onResponseError(PlacesAutocompleteResponse res) { if (mounted) { if (widget.onError != null) { widget.onError(res); } setState(() { response = null; searching = false; }); } } @mustCallSuper void onResponse(PlacesAutocompleteResponse res) { if (mounted) { setState(() { response = res; searching = false; }); } } }

6. After that create some util classes gps_util.dart, map_util.dart and google_place_util.dart.
  •  gps_util.dart  will get your current position and update to another aspect of the app.
import 'dart:async'; import 'package:flutter/services.dart'; import 'package:location/location.dart'; class GpgUtils { GpsUtilListener listener; Map<String, double> _startLocation; Map<String, double> _currentLocation; StreamSubscription<Map<String, double>> _locationSubscription; Location _location = new Location(); bool _permission = false; String error; bool currentWidget = true; GpgUtils(this.listener); void init() { initPlatformState(); _locationSubscription = _location.onLocationChanged().listen((Map<String,double> result) { _currentLocation = result; listener.onLocationChange(_currentLocation); }); } // Platform messages are asynchronous, so we initialize in an async method. initPlatformState() async { Map<String, double> location; // Platform messages may fail, so we use a try/catch PlatformException. try { _permission = await _location.hasPermission(); location = await _location.getLocation(); error = null; } on PlatformException catch (e) { if (e.code == 'PERMISSION_DENIED') { error = 'Permission denied'; } else if (e.code == 'PERMISSION_DENIED_NEVER_ASK') { error = 'Permission denied - please ask the user to enable it from the app settings'; } location = null; } _startLocation = location; listener.onLocationChange(_startLocation); } } abstract class GpsUtilListener { onLocationChange(Map<String, double> location); }
  • map_util.dart will manage google map methods. Here, we have created a method getDirectionSteps. It will take a source and destination param and get google route map steps by using google map API. Before using it, make sure you have enabled it from google console as discussed above.
import 'package:flutter/material.dart'; import 'package:flutter_google_map_route/utils/gps_util.dart'; import 'package:flutter_google_map_route/map_screen.dart'; import 'package:flutter_google_map_route/model/route.dart'; import 'package:flutter_google_map_route/network/networ_util.dart'; import 'package:map_view/map_view.dart'; import 'package:map_view/polyline.dart'; class MapUtil implements GpsUtilListener { var staticMapProvider; CameraPosition cameraPosition; var location = new Location(0.0, 0.0); var zoomLevel = 18.0; MapView mapView; NetworkUtil network = new NetworkUtil(); GpgUtils gpgUtils; ScreenListener _screenListener; List<Location> ccc; MapUtil(this._screenListener); init() { mapView = new MapView(); gpgUtils = new GpgUtils(this); gpgUtils.init(); staticMapProvider = new StaticMapProvider("google_api_key"); } getDirectionSteps(double destinationLat, double destinationLng) { network .get("origin=" + location.latitude.toString() + "," + location.longitude.toString() + "&destination=" + destinationLat.toString() + "," + destinationLng.toString() + "&key=google_api_key") .then((dynamic res) { List<Steps> rr = res; print(res.toString()); ccc = new List(); for (final i in rr) { ccc.add(i.startLocation); ccc.add(i.endLocation); } mapView.onMapReady.listen((_) { mapView.setMarkers(getMarker(location.latitude,location.longitude,destinationLat,destinationLng)); mapView.addPolyline(new Polyline("12", ccc, width: 15.0)); }); _screenListener.dismissLoader(); showMap(); }).catchError((Exception error) => _screenListener.dismissLoader()); } List<Marker> getMarker(double scrLat,double scrLng,double desLat,double desLng) { List<Marker> markers = <Marker>[ new Marker("1", "My Location", scrLat, scrLng, color: Colors.amber), new Marker("2", "Destination", desLat, desLng, color:, ]; return markers; } Uri getStaticMap() { return staticMapProvider.getStaticUri(getMyLocation(), zoomLevel.toInt(), height: 400, width: 900); } Location getMyLocation() { return location; } CameraPosition getCamera() { cameraPosition = new CameraPosition(getMyLocation(), zoomLevel); return cameraPosition; } showMap() { new MapOptions( mapViewType: MapViewType.normal, initialCameraPosition: getCamera(), showUserLocation: true, title: "Draw route"), toolbarActions: [new ToolbarAction("Close", 1)]); mapView.onToolbarAction.listen((id) { if (id == 1) { mapView.dismiss(); } }); } updateLocation(Location location) { this.location = location; } updateZoomLevel(double zoomLevel) { this.zoomLevel = zoomLevel; } @override onLocationChange(Map<String, double> currentLocation) { location = new Location(currentLocation["latitude"], currentLocation["longitude"]); _screenListener.updateScreen(location); } cameraUpdate(CameraPosition cameraPosition) { print("campera position changed $location"); } void manageMapProperties() { mapView.zoomToFit(padding: 100); mapView.onLocationUpdated.listen((location) => updateLocation(location)); mapView.onTouchAnnotation.listen((marker) => print("marker tapped")); mapView.onMapTapped.listen((location) => updateLocation(location)); mapView.onCameraChanged .listen((cameraPosition) => cameraUpdate(cameraPosition)); } }

  • google_place_util.dart  will get google place list from the server.
import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_google_map_route/flutter_google_places_autocomplete.dart'; import 'package:google_maps_webservice/places.dart'; class GooglePlaces { final homeScaffoldKey = new GlobalKey<ScaffoldState>(); final searchScaffoldKey = new GlobalKey<ScaffoldState>(); GoogleMapsPlaces _places = new GoogleMapsPlaces("google_api_key"); Location location; GooglePlacesListener _mapScreenState; GooglePlaces(this._mapScreenState); Future findPlace(BuildContext context) async { Prediction p = await showGooglePlacesAutocomplete( context: context, location: location, apiKey: "google_api_key", onError: (res) { homeScaffoldKey.currentState .showSnackBar(new SnackBar(content: new Text(res.errorMessage))); }, ); displayPrediction(p, homeScaffoldKey.currentState); } Future<Null> displayPrediction(Prediction p, ScaffoldState scaffold) async { if (p != null) { // get detail (lat/lng) PlacesDetailsResponse detail = await _places.getDetailsByPlaceId(p.placeId); final lat =; final lng = detail.result.geometry.location.lng; _mapScreenState.selectedLocation( lat, lng, detail.result.formattedAddress); } } void updateLocation(double lat, double long) { location = new Location(lat, long); } } abstract class GooglePlacesListener { selectedLocation(double lat, double long, String address); }

7. To get route steps, we using Google API in this project. For fetch steps, we have created another util class network_util.dart.
import 'dart:async'; import 'dart:convert'; import 'package:flutter_google_map_route/model/route.dart'; import 'package:http/http.dart' as http; class NetworkUtil { static final BASE_URL = ""; static NetworkUtil _instance = new NetworkUtil.internal(); NetworkUtil.internal(); factory NetworkUtil() => _instance; final JsonDecoder _decoder = new JsonDecoder(); Future<dynamic> get(String url) { return http.get(BASE_URL + url).then((http.Response response) { String res = response.body; int statusCode = response.statusCode; print("API Response: " + res); if (statusCode < 200 || statusCode > 400 || json == null) { res = "{\"status\":" + statusCode.toString() + ",\"message\":\"error\",\"response\":" + res + "}"; throw new Exception(res); } List<Steps> steps; try { steps = parseSteps(_decoder.convert(res)["routes"][0]["legs"][0]["steps"]); } catch (e) { throw new Exception(res); } return steps; }); } List<Steps> parseSteps(final responseBody) { var list =<Steps>((json) => new Steps.fromJson(json)).toList(); return list; } }

8. To parse the response of google API,  we have created a POJO model class.

import 'package:map_view/location.dart'; class Steps { Location startLocation; Location endLocation; Steps({this.startLocation, this.endLocation}); factory Steps.fromJson(Map<String, dynamic> json) { return new Steps( startLocation: new Location( json["start_location"]["lat"], json["start_location"]["lng"]), endLocation: new Location( json["end_location"]["lat"], json["end_location"]["lng"])); } }

Now merge all the code and put google API key.  If you have followed the article carefully, you can see the app running very smoothly as shown in the above video demo. But if you are facing any problem. You can get the working project from Github. 

Source Code               Flutter firebse storage apk

Before run downloaded project code, make sure. You have replaced google_api_key with your key in Android manifest, map_util.dart, and google_place_util.dart. If still, you have any quires, please feel free to ask it from below comment section.



  1. FAILURE: Build failed with an exception.

    * What went wrong:
    Execution failed for task ':app:preDebugBuild'.
    > Android dependency '' has different version for the compile (26.1.0) and runtime (27.1.1) classpath. You should manually set the same version via DependencyResolution

    * Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

    * Get more help at

    BUILD FAILED in 4s
    Finished with error: Gradle task assembleDebug failed with exit code 1

    1. We have updated the code. Get it from

  2. Hii I am new in flutter is there proper document and answer so I can implement in my project
    I want to make root between two marker and and show multiple marker on map

    1. Hey, Flutter team working for it.

    2. Is there any information, about when it can be expected to be ready?

    3. There is no update regarding this. Tell us your requirements. We'll guide you.

  3. Hi! I've cloned your github repository for this article, added all the required updates. Then I'm trying to run the application, but in console see the following error:

    ...flutter/.pub-cache/hosted/ (178, 66): Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Map?

    FAILURE: Build failed with an exception.

    * What went wrong:
    Execution failed for task ':map_view:compileDebugKotlin'.
    > Compilation error. See log for more details

    Can you please suggest the solution for this error.

    1. For people having this issue.

      1. Open the Android project and go to the MapView module, then select MapViewPlugin.kt inside the java folder (This should be the file path: .pub-cache\hosted\\map_view-0.0.14\android\src\main\kotlin\com\apptreesoftware\mapview)

      2. If you are using Android Studio you will already see red warnings. Go to line 168 where you'll find val cameraDict = mapOptions["cameraPosition"] as Map

      3. Change it to this val cameraDict = mapOptions!!["cameraPosition"] as Map

      Now it should work.

  4. Hey bro.. how to get direction step multiple time and draw route multiple marker point like this
    for native i solved but flutter @#$%&*?? please help me!



Facebook Gruop


  • Cover art Blend

    Blend app is very advanced app there are lots of features like shayari, status, chat, feed....