Skip to content

Learn how to find a route and directions with the route service.

find a route and directions

Routing is the process of finding the path from an origin to a destination in a street network. You can use the Routing service to find routes, get driving directions, calculate drive times, and solve complicated, multiple vehicle routing problems. To create a route, you typically define a set of stops (origin and one or more destinations) and use the service to find a route with directions. You can also use a number of additional parameters such as barriers and mode of travel to refine the results.

In this tutorial, you define an origin and destination by clicking on the map. These values are used to get a route and directions from the route service. The directions are also displayed on the map.

Prerequisites

Before starting this tutorial, you need the following:

  1. An ArcGIS Location Platform or ArcGIS Online account.

  2. A development and deployment environment that meets the system requirements.

  3. An IDE for Android development in Kotlin.

Set up authentication

To access the secure ArcGIS location services used in this tutorial, you must implement API key authentication or user authentication using an ArcGIS Location Platform or an ArcGIS Online account.

Create a new API key access token with privileges to access the secure resources used in this tutorial.

  1. Complete the Create an API key tutorial and create an API key with the following privilege(s):

    • Privileges
      • Location services > Basemaps
      • Location services > Routing
  2. Copy and paste the API key access token into a safe location. It will be used in a later step.

Develop or download

You have two options for completing this tutorial:

  1. Option 1: Develop the code or
  2. Option 2: Download the completed solution

Option 1: Develop the code

Open an Android Studio project

  1. Open the project you created by completing the Display a map tutorial.

  2. Continue with the following instructions to find a route and directions with the ArcGIS Routing service.

  3. Modify the old project for use in this new tutorial.

Set developer credentials

If you implemented API key authentication in the Display a map tutorial, the API key access token will only have the Basemaps privilege. The Find a route and directions tutorial requires the Routing privilege to find a route using the RouteTask. To create an API Key access token that has the Basemaps and Routing privileges, see the Set up authentication step and then follow the instructions below.

  1. In the Android view of Android Studio, open app > kotlin+java > com.example.app > MainActivity.

  2. In the onCreate() lifecycle method of the MainActivity class, set the ArcGISEnvironment.apiKey​ property by calling ApiKey.create(). Pass in your API key access token as a string and don't forget the double quotes. Do this before the setContent block.

    MainActivity.kt
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30  override fun onCreate(savedInstanceState: Bundle?) {  super.onCreate(savedInstanceState)   ArcGISEnvironment.apiKey = ApiKey.create("YOUR_ACCESS_TOKEN") 

Best Practice: The access token is stored directly in the code as a convenience for this tutorial. Do not store credentials directly in source code in a production environment.

Add import statements and some Compose variables

  1. In the Android view, open app > kotlin+java > com.example.app > screens > MainScreen.kt. Replace the import statements with the imports needed for this tutorial.

    MainScreen.kt
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 @file:OptIn(ExperimentalMaterial3Api::class)  package com.example.app.screens  import android.content.Context import android.widget.Toast import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.arcgismaps.Color import com.arcgismaps.geometry.Point import com.arcgismaps.mapping.ArcGISMap import com.arcgismaps.mapping.BasemapStyle import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.mapping.symbology.SimpleLineSymbol import com.arcgismaps.mapping.symbology.SimpleLineSymbolStyle import com.arcgismaps.mapping.symbology.SimpleMarkerSymbol import com.arcgismaps.mapping.symbology.SimpleMarkerSymbolStyle import com.arcgismaps.mapping.view.Graphic import com.arcgismaps.mapping.view.GraphicsOverlay import com.arcgismaps.mapping.view.SingleTapConfirmedEvent import com.arcgismaps.tasks.networkanalysis.DirectionManeuver import com.arcgismaps.tasks.networkanalysis.RouteParameters import com.arcgismaps.tasks.networkanalysis.RouteResult import com.arcgismaps.tasks.networkanalysis.RouteTask import com.arcgismaps.tasks.networkanalysis.Stop import com.arcgismaps.toolkit.geoviewcompose.MapView import com.example.app.R import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch 
  2. In the MainScreen composable, create variables that will be passed to various functions in the MainScreen.kt file.

    MainScreen.kt
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 @Composable fun MainScreen() {   val context = LocalContext.current  val coroutineScope = rememberCoroutineScope()  val directionList = remember { mutableStateListOf("Tap to add two points to the map to find a route between them.") }  val routeStops = remember { mutableListOf<Stop>() }  val currentJob = remember { mutableStateOf<Job?>(null) }  // Create a graphics overlay to display the selected stops and route.  val graphicsOverlay = remember { GraphicsOverlay() }  val graphicsOverlays = remember { listOf(graphicsOverlay) }   val map = remember {  createMap()  }   Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) {   MapView(  modifier = Modifier.fillMaxWidth().fillMaxHeight(0.7f),  arcGISMap = map,   )   }  } 

Update the map

A streets basemap layer is typically used in routing applications. In createMap(), update the basemap to use the ArcGISStreets BasemapStyle, and change the position of the map to center on Los Angeles.

MainScreen.kt
Use dark colors for code blocks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 fun createMap(): ArcGISMap {   return ArcGISMap(BasemapStyle.ArcGISStreets).apply {  initialViewpoint = Viewpoint(  latitude = 34.0539,  longitude = -118.2453,  scale = 144447.638572  )  }  } 

Define a RoutesList to display driving directions

To display the turn-by-turn directions from the route, define a composable function named RoutesList that takes a MutableList<String> parameter named directionsList.

Inside the RouteList block, add a LazyColumn to hold UI items that are based on the directions list. Inside the LazyColumn, call the items() function, passing the size of the directions list.

The trailing lambda for items() is automatically invoked once for each string in the list, adding a UI item to the LazyColumn for each string. In the lambda, call Text to display the string, and then call Divider to create a light-gray horizontal line after the text. The result of calling RoutesList will be a scrollable list of directions, with items separated by lines.

MainScreen.kt
Use dark colors for code blocks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 @Composable fun RoutesList(directionList: MutableList<String>) {  LazyColumn {  items(directionList.size) { index ->  Text(text = directionList[index] + ".")  HorizontalDivider(  modifier = Modifier.fillMaxWidth(),  thickness = 1.dp,  color = androidx.compose.ui.graphics.Color.LightGray  )  }  } } 

Create an addStop() function

A RouteTask requires at least an origin and a destination stop to find a route. Create a function that adds a Stop object to the routeStops list and creates a graphic to display the stop.

MainScreen.kt
Use dark colors for code blocks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 fun addStop(  routeStops: MutableList<Stop>,  stop: Stop,  graphicsOverlay: GraphicsOverlay ) {  routeStops.add(stop)  // Create a green circle symbol for the stop.  val stopMarker = SimpleMarkerSymbol(  style = SimpleMarkerSymbolStyle.Circle, color = Color.green, size = 20f  )  // Get the stop's geometry.  val routeStopGeometry = stop.geometry  // Add graphic to graphics overlay.  graphicsOverlay.graphics.add(  Graphic(  geometry = routeStopGeometry,  symbol = stopMarker  )  ) } 

Create a findRoute() function

A task can make asynchronous requests to an online service or offline data and then return the results. In this tutorial, we will solve the route between the origin and destination stops using a RouteTask that accesses a routing service.

  1. Create a suspend function named findRoute() that takes the parameters shown below. Then create a RouteTask from a routing service.

    MainScreen.kt
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 suspend fun findRoute(  context: Context,  routeStops: MutableList<Stop>,  graphicsOverlay: GraphicsOverlay,  directionsList: MutableList<String> ) {   val routeTask = RouteTask(  url = "https://route-api.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World"  )  } 
  2. Using routeTask, create the default RouteParameters needed to solve a route. Then use routeParameters to set the route stops and specify that step-by-step directions should be returned. Wrap the code in try-catch blocks.

    MainScreen.kt
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 suspend fun findRoute(  context: Context,  routeStops: MutableList<Stop>,  graphicsOverlay: GraphicsOverlay,  directionsList: MutableList<String> ) {   val routeTask = RouteTask(  url = "https://route-api.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World"  )   // Create a job to find the route.  try {  val routeParameters: RouteParameters = routeTask.createDefaultParameters().getOrThrow()  routeParameters.setStops(routeStops)  routeParameters.returnDirections = true   } catch (e: Exception) {  showMessage(context, "Failed to find route: ${e.message}")  }  } 
  3. Using routeTask, solve the route and get the list of routes from the route result.

    MainScreen.kt
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 suspend fun findRoute(  context: Context,  routeStops: MutableList<Stop>,  graphicsOverlay: GraphicsOverlay,  directionsList: MutableList<String> ) {   val routeTask = RouteTask(  url = "https://route-api.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World"  )   // Create a job to find the route.  try {  val routeParameters: RouteParameters = routeTask.createDefaultParameters().getOrThrow()  routeParameters.setStops(routeStops)  routeParameters.returnDirections = true   // Solve a route using the route parameters created.  val routeResult: RouteResult = routeTask.solveRoute(routeParameters).getOrThrow()  val routes = routeResult.routes   } catch (e: Exception) {  showMessage(context, "Failed to find route: ${e.message}")  }  } 
  4. Get the first route from the list. Using the route's geometry and a green line symbol, create a Graphic to display the route.

    Next, add the route graphic to the graphics overlay. Last, get the list of Route.directionManeuvers, and for each direction maneuver, add the directionText (such as "In 50 ft, turn left on 1st Avenue") to the directionsList.

    MainScreen.kt
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 suspend fun findRoute(  context: Context,  routeStops: MutableList<Stop>,  graphicsOverlay: GraphicsOverlay,  directionsList: MutableList<String> ) {   val routeTask = RouteTask(  url = "https://route-api.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World"  )   // Create a job to find the route.  try {  val routeParameters: RouteParameters = routeTask.createDefaultParameters().getOrThrow()  routeParameters.setStops(routeStops)  routeParameters.returnDirections = true   // Solve a route using the route parameters created.  val routeResult: RouteResult = routeTask.solveRoute(routeParameters).getOrThrow()  val routes = routeResult.routes   // If a route is found.  if (routes.isNotEmpty()) {  val route = routes[0]  val routeGraphic = Graphic(  geometry = route.routeGeometry,  symbol = SimpleLineSymbol(  style = SimpleLineSymbolStyle.Solid,  color = Color.green,  width = 2f  )  )  // Add the route graphic to the graphics overlay.  graphicsOverlay.graphics.add(routeGraphic)  // Get the direction text for each maneuver and display it as a list on the UI.  directionsList.clear()  route.directionManeuvers.forEach { directionManeuver: DirectionManeuver ->  directionsList.add(directionManeuver.directionText)  }  }   } catch (e: Exception) {  showMessage(context, "Failed to find route: ${e.message}")  }  } 

Create a clearStops() function

Create a function to clear the current route.

MainScreen.kt
Use dark colors for code blocks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 fun clearStops(  routeStops: MutableList<Stop>,  directionList: MutableList<String>,  graphicsOverlay: GraphicsOverlay ) {  graphicsOverlay.graphics.clear()  routeStops.clear()  directionList.clear()  directionList.add("Tap to add two points to the map to find a route between them.") } 

Handle user input

  1. Define a suspend function named onSingleTapConfirmed() that handles the user tapping on the screen to set an origin and tapping again to set a destination point. Declare the parameters shown below.

    MainScreen.kt
    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 fun onSingleTapConfirmed(  context: Context,  coroutineScope: CoroutineScope,  currentJob: MutableState<Job?>,  event: SingleTapConfirmedEvent,  routeStops: MutableList<Stop>,  graphicsOverlay: GraphicsOverlay,  directionList: MutableList<String> ) {  } 
    Expand
  2. Cancel any coroutine Job that is currently running.

    Then get the tapped Point using the mapPoint property of SingleTapConfirmedEvent, returning an error message if there is no point.

    Last, create a Stop using the point.

    MainScreen.kt
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 fun onSingleTapConfirmed(  context: Context,  coroutineScope: CoroutineScope,  currentJob: MutableState<Job?>,  event: SingleTapConfirmedEvent,  routeStops: MutableList<Stop>,  graphicsOverlay: GraphicsOverlay,  directionList: MutableList<String> ) {   currentJob.value?.cancel()  // Retrieve the tapped map point from the SingleTapConfirmedEvent  val point: Point = event.mapPoint ?: return showMessage(context, "No map point retrieved from tap.")  val stop = Stop(point)  } 
  3. Call addStop() to add the stop to routeStops and add its graphic to the graphics overlay. Use a when statement to handle whether the routeStops list currently contains zero, one, or two stops.

    • If zero stops, add the stop and do nothing else.
    • If one stop, add the stop. Then launch a new coroutine and solve the route by calling findRoute().
    • If two stops, empty the routeStops list by calling clearStops(). Then add the stop.
    MainScreen.kt
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 fun onSingleTapConfirmed(  context: Context,  coroutineScope: CoroutineScope,  currentJob: MutableState<Job?>,  event: SingleTapConfirmedEvent,  routeStops: MutableList<Stop>,  graphicsOverlay: GraphicsOverlay,  directionList: MutableList<String> ) {   currentJob.value?.cancel()  // Retrieve the tapped map point from the SingleTapConfirmedEvent  val point: Point = event.mapPoint ?: return showMessage(context, "No map point retrieved from tap.")  val stop = Stop(point)   when (routeStops.size) {  // On first tap, add a stop.  0 -> {  addStop(routeStops, stop, graphicsOverlay)  }  // On second tap, add a stop and find route between them.  1 -> {  addStop(routeStops, stop, graphicsOverlay)  currentJob.value = coroutineScope.launch {  findRoute(context, routeStops, graphicsOverlay, directionList)  }  showMessage(context, "Calculating route...")  }  // On a further tap, clear and add a new first stop.  else -> {  clearStops(routeStops, directionList, graphicsOverlay)  addStop(routeStops, stop, graphicsOverlay)  }  }  } 

Pass parameters to MapView and call RoutesList

  1. Inside the Scaffold block, find the MapView from the Display a map tutorial and replace it with a call of the Column composable. A Column allows you to display the map view at the top of screen and the routes list directly below.

    MainScreen.kt
    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241  Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) {   Column(  modifier = Modifier.fillMaxSize().padding(it)  ) {   }   } 
    Expand
  2. Inside the Column block, add back the MapView code. Then call RoutesList.

    MainScreen.kt
    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241  Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) {   Column(  modifier = Modifier.fillMaxSize().padding(it)  ) {   MapView(  modifier = Modifier.fillMaxWidth().fillMaxHeight(0.7f),  arcGISMap = map,   )   RoutesList(directionList)   }   } 
    Expand
  3. Pass two additional parameters to MapView:

    • graphicsOverlays
    • a lambda that calls onSingleTapConfirmed() and passes the parameters shown below.
    MainScreen.kt
    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241  Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) {   Column(  modifier = Modifier.fillMaxSize().padding(it)  ) {   MapView(  modifier = Modifier.fillMaxWidth().fillMaxHeight(0.7f),  arcGISMap = map,   graphicsOverlays = graphicsOverlays,   onSingleTapConfirmed = { event ->  onSingleTapConfirmed(  context, coroutineScope, currentJob, event, routeStops, graphicsOverlay, directionList  )  }   )   RoutesList(directionList)   }   } 
    Expand
  4. (Optional) The code in this tutorial calls a function to display messages to the user. One possible implementation of showMessage() is the following.

    MainScreen.kt
    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 fun showMessage(context: Context, message: String) {  Toast.makeText(context, message, Toast.LENGTH_LONG).show() }
  5. Click Run > Run > app to run the app.

The map should support two taps to create origin and destination points and then use the route service to display the resulting route and turn-by-turn directions.

Alternatively, you can download the tutorial solution, as follows.

Option 2: Download the solution

  1. Click the Download solution link in the right-hand side of this page.

  2. Unzip the file to a location on your machine.

  3. Run Android Studio.

  4. Go to File > Open.... Navigate to the solution folder and click Open.

    On Windows: If you are in the Welcome to Android Studio dialog, click Open and navigate to the solution folder. Then click Open.

Since the downloaded solution does not contain authentication credentials, you must add the developer credentials that you created in the Set up authentication section.

Set developer credentials in the solution

To allow your app users to access ArcGIS location services, use the developer credentials that you created in the Set up authentication step to authenticate requests for resources.

  1. In the Android view of Android Studio, open app > kotlin+java > com.example.app > MainActivity. Set the AuthenticationMode to .API_KEY.

    MainActivity.kt
    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 class MainActivity : ComponentActivity() {   private enum class AuthenticationMode { API_KEY, USER_AUTH }   private val authenticationMode = AuthenticationMode.API_KEY 
    Expand
  2. Set the apiKey property with your API key access token.

    MainActivity.kt
    Expand
    Use dark colors for code blocks
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62  override fun onCreate(savedInstanceState: Bundle?) {  super.onCreate(savedInstanceState)   when (authenticationMode) {  AuthenticationMode.API_KEY -> {   ArcGISEnvironment.apiKey = ApiKey.create("YOUR_ACCESS_TOKEN")   } 
    Expand

Best Practice: The access token is stored directly in the code as a convenience for this tutorial. Do not store credentials directly in source code in a production environment.

Run the app

Click Run > Run > app to run the app.

The map should support two taps to create origin and destination points and then use the route service to display the resulting route and turn-by-turn directions.

What's next?

To explore more API features and ArcGIS location services, try the following tutorial:

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.