Draw route on google maps using poly-lines in Compose | Android Kotlin

Sahil Khan
4 min readMar 19, 2023

--

Before following this article, you need to ensure that you have added the necessary dependencies for Google Maps in your Android project. Additionally, you must have generated an API Key for Google Maps from the Google Cloud Console and added it to your project.

Enable Directions api for google maps for your project in google cloud console : ( Directions Api Page Url : https://console.cloud.google.com/apis/library/directions-backend.googleapis.com?project=<your project name here> )

To draw a path from the origin to destination on Google Maps using polylines, you first need to obtain the coordinates of the route using the Google Maps API.

Here are the steps you can follow:

Create a retrofit interface for maps directions api

interface MapDirectionsService {

@GET("directions/json")
suspend fun getDirections(
@Query("origin") originLatLng: String,
@Query("destination") destinationLatLang: String,
@Query("key") apiKey: String = BuildConfig.MAPS_API_KEY
): Response<DirectionsDto>

}

“DirectionsDto” is model class to represent the json format we received from directions api in response

package me.imsahil.mapsroutedrawexample.data.remote.dto

import com.google.android.gms.maps.model.LatLng

data class DirectionsDto(
val geocoded_waypoints: List<GeocodedWaypoint>,
val routes: List<Route>,
val status: String
){
data class GeocodedWaypoint(
val geocoder_status: String,
val place_id: String,
val types: List<String>
)
data class Route(
val bounds: Bounds,
val copyrights: String,
val legs: List<Leg>,
val overview_polyline: OverviewPolyline,
val summary: String,
val warnings: List<Any>,
val waypoint_order: List<Any>
){
data class Bounds(
val northeast: Northeast,
val southwest: Southwest
){

data class Northeast(
val lat: Double,
val lng: Double
)

data class Southwest(
val lat: Double,
val lng: Double
)
}

data class Leg(
val distance: Distance,
val duration: Duration,
val end_address: String,
val end_location: EndLocation,
val start_address: String,
val start_location: StartLocation,
val steps: List<Step>,
val traffic_speed_entry: List<Any>,
val via_waypoint: List<Any>
){
data class Distance(
val text: String,
val value: Int
)

data class Duration(
val text: String,
val value: Int
)

data class EndLocation(
val lat: Double,
val lng: Double
)

data class StartLocation(
val lat: Double,
val lng: Double
)

data class Step(
val distance: Distance,
val duration: Duration,
val end_location: EndLocation,
val html_instructions: String,
val maneuver: String,
val polyline: Polyline,
val start_location: StartLocation,
val travel_mode: String
){
data class Polyline(
val points: String
){

/* to decode polyline String to list of latLng
* so we can draw route using the coordinates */

fun decodePolyline(encoded: String): List<LatLng> {
val poly = ArrayList<LatLng>()
var index = 0
val len = encoded.length
var lat = 0
var lng = 0
while (index < len) {
var b: Int
var shift = 0
var result = 0
do {
b = encoded[index++].code - 63
result = result or (b and 0x1f shl shift)
shift += 5
} while (b >= 0x20)
val dlat = if (result and 1 != 0) (result shr 1).inv() else result shr 1
lat += dlat
shift = 0
result = 0
do {
b = encoded[index++].code - 63
result = result or (b and 0x1f shl shift)
shift += 5
} while (b >= 0x20)
val dlng = if (result and 1 != 0) (result shr 1).inv() else result shr 1
lng += dlng
val latLng = LatLng((lat.toDouble() / 1E5),(lng.toDouble() / 1E5))
poly.add(latLng)
}
return poly
}

}
}

}
data class OverviewPolyline(
val points: String
)
}
}

“decodePolyline” method is used to decode the string of encoded polyline into list of latLng that are further required to draw route on maps

    @Provides
@Singleton
fun provideRetrofit(
okHttpClient: OkHttpClient
): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.MAPS_API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
}
    @Provides
@Singleton
fun providesMapDirectionsService(
retrofit: Retrofit
): MapDirectionsService = retrofit.create(MapDirectionsService::class.java)

Initialise Retrofit and Create provide mapDirectionsService Object as follows

data class Route(
val routePoints: List<List<LatLng>>
)

Actual model class that is used to hold our route coordinates we are going to use

class MapsRepositoryImpl(
private val directionsService: MapDirectionsService
) : MapsRepository {
override suspend fun getDirections(origin: LatLng, destination: LatLng): Resource<Route> {
return try {

val response = directionsService.getDirections(
originLatLng = "${origin.latitude},${origin.longitude}",
destinationLatLang = "${destination.latitude},${destination.longitude}",
)


if (response.isSuccessful && response.body() != null){

val polyLinePoints = try {
response.body()!!.routes[0].legs[0].steps.map { step ->
step.polyline.decodePolyline(step.polyline.points)
}
}catch (e: Exception){ emptyList() }
Resource.Success(data = Route(routePoints = polyLinePoints))
}else{
Resource.Error(response.message())
}

}catch (e:Exception){
Resource.Error("Something went wrong")
}
}
}

In the maps repository we have to call getDirections fun of directions service with origin and destination latlng and map the response data to our route data class field

 viewModelScope.launch {

val pathResult = mapsRepository.getDirections(
origin = mainUiState.value.originLatLng!!,
destination = mainUiState.value.destinationLatLng!!
)

when (pathResult) {
is Resource.Error -> {
_mainUiState.update { it.copy(uiMessage = pathResult.message) }
}
is Resource.Success -> {
pathResult.data?.let { route ->
_mainUiState.update {
it.copy(
routeCoordinates = route.routePoints,
currentStep = null
)
}
}

}
}

}

In our viewModel we have to get the routeCoordinates from our repository

  if (uiState.routeCoordinates.isNotEmpty()) {
uiState.routeCoordinates.forEach {
Polyline(points = it, color = Color.Red)
}
}

Inside our GoogleMaps Composable content lambda we have to draw Polyline using the list of route coordinates

Github Demo Project Link : https://github.com/sahilsk3333/MapsRouteDrawExample-Compose-Demo

--

--