Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate sample: Find route #271

Open
wants to merge 6 commits into
base: v.next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion samples/find-route/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ For simplicity, the sample comes loaded with a start and end stop. You can tap o
* RouteTask
* Stop

## Additional information

This sample uses the GeoView-Compose Toolkit module to be able to implement a composable MapView.

## Tags

directions, driving, navigation, network, network analysis, route, routing, shortest path, turn-by-turn
directions, driving, geoview-compose, navigation, network, network analysis, route, routing, shortest path, toolkit, turn-by-turn
10 changes: 6 additions & 4 deletions samples/find-route/README.metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
"keywords": [
"directions",
"driving",
"geoview-compose",
"navigation",
"network",
"network analysis",
"route",
"routing",
"shortest path",
"toolkit",
"turn-by-turn",
"DirectionManeuver",
"Route",
Expand All @@ -24,9 +26,7 @@
"Stop"
],
"language": "kotlin",
"redirect_from": [
"/android/latest/sample-code/find-route.htm"
],
"redirect_from": "",
"relevant_apis": [
"DirectionManeuver",
"Route",
Expand All @@ -36,7 +36,9 @@
"Stop"
],
"snippets": [
"src/main/java/com/esri/arcgismaps/sample/findroute/MainActivity.kt"
"src/main/java/com/esri/arcgismaps/sample/findroute/components/FindRouteViewModel.kt",
"src/main/java/com/esri/arcgismaps/sample/findroute/MainActivity.kt",
"src/main/java/com/esri/arcgismaps/sample/findroute/screens/FindRouteScreen.kt"
],
"title": "Find route"
}
3 changes: 1 addition & 2 deletions samples/find-route/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.arcgismaps.android.library)
alias(libs.plugins.arcgismaps.android.library.compose)
alias(libs.plugins.arcgismaps.kotlin.sample)
alias(libs.plugins.gradle.secrets)
}
Expand All @@ -11,9 +12,7 @@ secrets {

android {
namespace = "com.esri.arcgismaps.sample.findroute"
// For view based samples
buildFeatures {
dataBinding = true
buildConfig = true
}
}
Expand Down
Binary file modified samples/find-route/find-route.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions samples/find-route/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@

<application><activity
android:exported="true"
android:name=".MainActivity"
android:label="@string/find_route_app_name">
android:name=".MainActivity">

</activity>
</application>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright 2022 Esri
/* Copyright 2025 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,281 +16,38 @@

package com.esri.arcgismaps.sample.findroute

import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ListView
import android.widget.ProgressBar
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.lifecycleScope
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import com.arcgismaps.ApiKey
import com.arcgismaps.ArcGISEnvironment
import com.arcgismaps.Color
import com.arcgismaps.geometry.Point
import com.arcgismaps.geometry.SpatialReference
import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.BasemapStyle
import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.mapping.symbology.PictureMarkerSymbol
import com.arcgismaps.mapping.symbology.SimpleLineSymbol
import com.arcgismaps.mapping.symbology.SimpleLineSymbolStyle
import com.arcgismaps.mapping.view.Graphic
import com.arcgismaps.mapping.view.GraphicsOverlay
import com.arcgismaps.mapping.view.MapView
import com.arcgismaps.tasks.networkanalysis.DirectionManeuver
import com.arcgismaps.tasks.networkanalysis.RouteResult
import com.arcgismaps.tasks.networkanalysis.RouteTask
import com.arcgismaps.tasks.networkanalysis.Stop
import com.esri.arcgismaps.sample.findroute.databinding.FindRouteActivityMainBinding
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.launch
import com.esri.arcgismaps.sample.findroute.screens.FindRouteScreen
import com.esri.arcgismaps.sample.sampleslib.theme.SampleAppTheme

class MainActivity : AppCompatActivity() {

// set up data binding for the activity
private val activityMainBinding: FindRouteActivityMainBinding by lazy {
DataBindingUtil.setContentView(this, R.layout.find_route_activity_main)
}

private val graphicsOverlay: GraphicsOverlay by lazy { GraphicsOverlay() }

private val mapView: MapView by lazy {
activityMainBinding.mapView
}

private val mainContainer: ConstraintLayout by lazy {
activityMainBinding.mainContainer
}

private val mainProgressBar: ProgressBar by lazy {
activityMainBinding.mainProgressBar
}

private val directionFab: FloatingActionButton by lazy {
activityMainBinding.directionFab
}

private val bottomSheet: LinearLayout by lazy {
activityMainBinding.bottomSheet.bottomSheetLayout
}

private val header: ConstraintLayout by lazy {
activityMainBinding.bottomSheet.header
}

private val imageView: ImageView by lazy {
activityMainBinding.bottomSheet.imageView
}

private val directionsListView: ListView by lazy {
activityMainBinding.bottomSheet.directionsListView
}
class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// authentication with an API key or named user is
// required to access basemaps and other location services
ArcGISEnvironment.apiKey = ApiKey.create(BuildConfig.ACCESS_TOKEN)
lifecycle.addObserver(activityMainBinding.mapView)

mapView.apply {
// set the map to a new map with the navigation base map
map = ArcGISMap(BasemapStyle.ArcGISNavigation)
// set initial viewpoint to San Diego
setViewpoint(Viewpoint(32.7157, -117.1611, 200000.0))
mapView.graphicsOverlays.add(graphicsOverlay)
}

// create the symbols for the route
setupSymbols()

// hide the bottom sheet and make the map view span the whole screen
bottomSheet.visibility = View.INVISIBLE
(mainContainer.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin = 0

// solve the route and display the bottom sheet when the FAB is clicked
directionFab.setOnClickListener { lifecycleScope.launch { solveRoute() } }
}

/**
* Solves the route using a Route Task, populates the navigation drawer with the directions,
* and displays a graphic of the route on the map.
*/
private suspend fun solveRoute() {
// set the applicationContext as it is required with RouteTask
ArcGISEnvironment.applicationContext = applicationContext
// create a route task instance
val routeTask =
RouteTask(
"https://route-api.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World"
)

// show the progress bar
mainProgressBar.visibility = View.VISIBLE
routeTask.createDefaultParameters().onSuccess { routeParams ->
// create stops
val stops = listOf(
Stop(Point(-117.1508, 32.7411, SpatialReference.wgs84())),
Stop(Point(-117.1555, 32.7033, SpatialReference.wgs84()))
)

routeParams.apply {
setStops(stops)
// set return directions as true to return turn-by-turn directions in the route's directionManeuvers
returnDirections = true
setContent {
SampleAppTheme {
FindRouteApp()
}

// solve the route
val routeResult = routeTask.solveRoute(routeParams).getOrElse {
showError(it.message.toString())
} as RouteResult
val route = routeResult.routes[0]
// create a simple line symbol for the route
val routeSymbol = SimpleLineSymbol(SimpleLineSymbolStyle.Solid, Color.blue, 5f)

// create a graphic for the route and add it to the graphics overlay
graphicsOverlay.graphics.add(Graphic(route.routeGeometry, routeSymbol))
// get the list of direction maneuvers and display it
// NOTE: to get turn-by-turn directions the route parameters
// must have the isReturnDirections parameter set to true.
val directions = route.directionManeuvers
setupBottomSheet(directions)

// when the route is solved, hide the FAB and the progress bar
directionFab.visibility = View.GONE
mainProgressBar.visibility = View.GONE
}.onFailure {
showError(it.message.toString())
mainProgressBar.visibility = View.GONE
}
}

/** Creates a bottom sheet to display a list of direction maneuvers.
* [directions] a list of DirectionManeuver which represents the route
*/
private fun setupBottomSheet(directions: List<DirectionManeuver>) {
// create a bottom sheet behavior from the bottom sheet view in the main layout
val bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet).apply {
// expand the bottom sheet, and ensure it is displayed on the screen when collapsed
state = BottomSheetBehavior.STATE_EXPANDED
peekHeight = header.height
// animate the arrow when the bottom sheet slides
addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
imageView.rotation = slideOffset * 180f
}

override fun onStateChanged(bottomSheet: View, newState: Int) {
imageView.rotation = when (newState) {
BottomSheetBehavior.STATE_EXPANDED -> 180f
else -> imageView.rotation
}
}
})
}

bottomSheet.apply {
visibility = View.VISIBLE
// expand or collapse the bottom sheet when the header is clicked
header.setOnClickListener {
bottomSheetBehavior.state = when (bottomSheetBehavior.state) {
BottomSheetBehavior.STATE_COLLAPSED -> BottomSheetBehavior.STATE_EXPANDED
else -> BottomSheetBehavior.STATE_COLLAPSED
}
}
// rotate the arrow so it starts off in the correct rotation
imageView.rotation = 180f
}

directionsListView.apply {
// Set the adapter for the list view
adapter = ArrayAdapter(
this@MainActivity,
android.R.layout.simple_list_item_1,
directions.map { it.directionText }
@Composable
private fun FindRouteApp() {
Surface(color = MaterialTheme.colorScheme.background) {
FindRouteScreen(
sampleName = getString(R.string.find_route_app_name)
)
// when the user taps a maneuver, set the viewpoint to that portion of the route
onItemClickListener =
AdapterView.OnItemClickListener { _, _, position, _ ->
directions[position].geometry?.let { geometry ->
// set the viewpoint to the selected maneuver
mapView.setViewpoint(
Viewpoint(geometry.extent, 20.0)
)
// create a graphic with a symbol for the maneuver and add it to the graphics overlay
val selectedRouteSymbol = SimpleLineSymbol(
SimpleLineSymbolStyle.Solid,
Color.green, 5f
)
graphicsOverlay.graphics.add(Graphic(geometry, selectedRouteSymbol))
// collapse the bottom sheet
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
}
}

// shrink the map view so it is not hidden under the bottom sheet header
(mainContainer.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin =
header.height
}

/**
* Set up the source, destination and route symbols.
*/
private fun setupSymbols() {
val startDrawable =
ContextCompat.getDrawable(this, R.drawable.ic_source) as BitmapDrawable
val pinSourceSymbol = PictureMarkerSymbol.createWithImage(startDrawable).apply {
// make the graphic smaller
width = 30f
height = 30f
offsetY = 20f
}
// create a point for the new graphic
val sourcePoint = Point(
-117.1508, 32.7411, SpatialReference.wgs84()
)
// create a graphic and it to the graphics overlay
graphicsOverlay.graphics.add(Graphic(sourcePoint, pinSourceSymbol))

val endDrawable =
ContextCompat.getDrawable(this, R.drawable.ic_destination) as BitmapDrawable

endDrawable.let {
val pinDestinationSymbol =
PictureMarkerSymbol.createWithImage(endDrawable).apply {
// make the graphic smaller
width = 30f
height = 30f
offsetY = 20f
}
// create a point for the new graphic
val destinationPoint = Point(-117.1555, 32.7033, SpatialReference.wgs84())
// create a graphic and add it to the graphics overlay
graphicsOverlay.graphics.add(Graphic(destinationPoint, pinDestinationSymbol))
}
}

private fun showError(message: String) {
Log.e(localClassName, message)
Snackbar.make(mapView, message, Snackbar.LENGTH_SHORT).show()
}

private val Color.Companion.blue: Color
get() {
return fromRgba(0, 0, 255, 255)
}

}
Loading
Loading