Learn how to search for places of interest, such as hotels, cafes, and gas stations using the geocoding service
Geocoding
In this tutorial, you use a picklist in the user interface to select a category of places, for example, coffee shops or gas stations. You locate all the places that match this category by accessing a geocoding service. The places are displayed on the map so that you can click on them to get further information.
Prerequisites
Before starting this tutorial, you need the following:
-
An ArcGIS Location Platform or ArcGIS Online account.
-
A development and deployment environment that meets the system requirements.
-
An IDE for Android development in Kotlin.
Set up authentication
To access the secure ArcGIS location services
You can implement API key authentication or user authentication in this tutorial. Compare the differences below:
API key authentication
- Users are not required to sign in.
- Requires creating an API key credential
API key credentials are an item that contains the parameters used to create and manage long-lived access tokens for API key authentication. They are a type of developer credential. with the correct privileges. - API keys
An API key is a long-lived access token created using API key credentials. They are valid for up to one year and are typically embedded directly into client applications. are long-lived access tokens. - Service usage is billed to the API key owner/developer.
- Simplest authentication method to implement.
- Recommended approach for new ArcGIS developers.
Learn more in API key authentication.
User authentication
- Users are required to sign in with an ArcGIS account
An ArcGIS account is an identity with a user type and set of privileges that can access specific ArcGIS products, tools, APIs, services, and resources. The main account types that can be used for development are an ArcGIS Location Platform account, ArcGIS Online account, and ArcGIS Enterprise account. ArcGIS Location Platform and ArcGIS Online accounts are also associated with a subscription. . - User accounts must have privilege
Privileges are a set of permissions assigned to ArcGIS accounts, developer credentials, and applications that grant access to secure resources and functionality in ArcGIS. to access the ArcGIS servicesA service, also known as an ArcGIS service, is software that supports an ArcGIS REST API and provides geospatial functionality or data. A service can be hosted by Esri or in ArcGIS Enterprise. used in application. - Requires creating OAuth credentials
OAuth credentials are an item that contains parameters required to implement user authentication or app authentication, including a .client_id,client_secret, and redirect URIs. They are a type of developer credential. - Application uses a redirect URL and client ID.
- Service usage is billed to the organization of the user signed into the application.
Learn more in User authentication.
To complete this tutorial, click on the tab in the switcher below for your authentication type of choice, either API key authentication or User authentication.
Create a new API key access token
-
Complete the Create an API key tutorial and create an API key with the following privilege(s)
Privileges are a set of permissions assigned to ArcGIS accounts, developer credentials, and applications that grant access to secure resources and functionality in ArcGIS. :- Privileges
- Location services > Basemaps
- Location services > Geocoding
- Privileges
-
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:
Option 1: Develop the code
Open an Android Studio project
-
Open the project you created by completing the Display a map tutorial.
-
Continue with the following instructions to search for places of interest, such as hotels, cafes, and gas stations using the ArcGIS Geocoding service
A geocoding service is a service that can search for addresses, place addresses, businesses, reverse geocode coordinates to addresses, provide suggestions for places, and perform bulk geocoding. It is hosted by Esri as the ArcGIS Geocoding service and can also be hosted in ArcGIS Enterprise. . -
Modify the old project for use in this new tutorial.
-
On your file system, delete the .idea folder, if present, at the top level of your project.
-
In the Android view, open app > res > values > strings.xml.
In the
<string name="appelement, change the text content to Find places._name" > strings.xmlUse dark colors for code blocks <resources> <string name="app_name">Find places</string> </resources> -
In the Android view, open Gradle Scripts > settings.gradle.kts.
Change the value of
rootto "Find places".Project.name settings.gradle.ktsUse dark colors for code blocks dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven { url = uri("https://esri.jfrog.io/artifactory/arcgis") } } } rootProject.name = "Find places" include(":app") -
Click File > Sync Project with Gradle files. Android Studio will recognize your changes and create a new .idea folder.
-
In
libs.versions.toml, add a [libraries] entry for the dependency. In the module-levelbuild.gradle.kts (app), add the dependency for the lifecycle view model.Use dark colors for code blocks [libraries] arcgis-maps-kotlin = { group = "com.esri", name = "arcgis-maps-kotlin", version.ref = "arcgisMapsKotlin" } arcgis-maps-kotlin-toolkit-bom = { group = "com.esri", name = "arcgis-maps-kotlin-toolkit-bom", version.ref = "arcgisMapsKotlin" } arcgis-maps-kotlin-toolkit-geoview-compose = { group = "com.esri", name = "arcgis-maps-kotlin-toolkit-geoview-compose" } arcgis-maps-kotlin-toolkit-authentication = { group = "com.esri", name = "arcgis-maps-kotlin-toolkit-authentication" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" }
-
Set developer credentials
If you implemented API key authenticationLocatorTask. To create an API Key access token that has the Basemaps and Geocoding privileges, see the Set up authentication step and then follow the instructions below.
-
In the Android view of Android Studio, open app > kotlin+java > com.example.app > MainActivity.
-
In the
onlifecycle method of theCreate() Mainclass, set theActivity ArcGISEnvironment.apiKeyproperty by callingApiKey.create(). Pass in your API key access token as a string and don't forget the double quotes. Do this before thesetblock.Content MainActivity.ktUse dark colors for code blocks 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
-
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.ktUse dark colors for code blocks @file:OptIn(ExperimentalMaterial3Api::class) package com.example.app.screens import android.app.Application import android.util.Log import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel import com.arcgismaps.Color import com.arcgismaps.geometry.Envelope import com.arcgismaps.geometry.Point import com.arcgismaps.geometry.SpatialReference import com.arcgismaps.mapping.ArcGISMap import com.arcgismaps.mapping.BasemapStyle import com.arcgismaps.mapping.GeoElement 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.geocode.GeocodeParameters import com.arcgismaps.tasks.geocode.LocatorTask import com.arcgismaps.toolkit.geoviewcompose.MapView import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy import com.example.app.R import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch
Create a view model
Modern app architecture uses a map view model to hold the business logic and the mutable state of your app.
-
In Android Studio: in the Android view, open app > kotlin+java > com.example.app > screens > MainScreen.kt, create a map view model that extends
View.Model MainScreen.ktUse dark colors for code blocks class MapViewModel() : ViewModel() { } -
In the
Maincomposable, delete the entire function body. Leave just the function declaration as shown below. Then delete the top-level code for creating a map that is part of the Display a map tutorial. In this tutorial, you will create the map within the view model.Screen MainScreen.ktUse dark colors for code blocks Copy @Composable fun MainScreen() { }MainScreen.ktUse dark colors for code blocks fun createMap(): ArcGISMap { return ArcGISMap(BasemapStyle.ArcGISTopographic).apply { initialViewpoint = Viewpoint( latitude = 34.0270, longitude = -118.8050, scale = 72000.0 ) } } -
In the view model, create a map from a
BasemapStyleand center theArcGISMap.initialViewpointon downtown Santa Monica, CA. Then create aMapViewProxy, which is defined in the ArcGIS Maps SDK for Kotlin Toolkit.MapViewModel classUse dark colors for code blocks // Create a map using the basemap style ArcGISTopographic. val map = ArcGISMap(BasemapStyle.ArcGISTopographic).apply { initialViewpoint = Viewpoint( center = Point( x = -118.477324, y = 33.999390, spatialReference = SpatialReference.wgs84() ), scale = 72_000.0 ) } val mapViewProxy = MapViewProxy() -
Create a
GraphicsOverlayto display graphicsA graphic is a visual element composed of a geometry, symbol, and attributes that is displayed on a map or scene. that represent places of the same category, such as coffee shops.A graphics overlay
A graphics overlay is a client-side, temporary container of graphics to display on a map view or scene view. is a container for graphicsA graphic is a visual element composed of a geometry, symbol, and attributes that is displayed on a map or scene. . It is used with a map viewA map view is a user interface that displays map layers and graphics in 2D. It controls the area (extent) of the map that is visible and supports user interactions such as pan and zoom. to display graphics on a mapA map is a collection of layers that are displayed in 2D. It is typically composed of a basemap layer and data layers. . You can add more than one graphics overlay to a map view. Graphics overlays are displayed on top of all the other layersA layer is a reference to a collection of geographic data that is used to access and display data. The data for layers are typically provided by the basemap layer service and data services. .Then create other map view model properties as follows:
- The current extent visible on the map view.
- The location on the map where the user taps to obtain further information about the place represented by a graphic.
- The selected geoelement
A geoelement refers to any geographic element in a map or map view that can be identified by its location to return attribute information. , which is the closest geoelement to the tap location. (A graphic is a type ofGeoElement.) - The current coroutine Job, so the job can be cancelled before launching a new coroutine.
MapViewModel classUse dark colors for code blocks class MapViewModel() : ViewModel() { // Create a map using the basemap style ArcGISTopographic. val map = ArcGISMap(BasemapStyle.ArcGISTopographic).apply { initialViewpoint = Viewpoint( center = Point( x = -118.477324, y = 33.999390, spatialReference = SpatialReference.wgs84() ), scale = 72_000.0 ) } val mapViewProxy = MapViewProxy() val graphicsOverlay = GraphicsOverlay() // The current extent visible in the map view. lateinit var geoViewExtent: Envelope // The location on the map that the user tapped to obtain further information. (A graphic is a type of GeoElement). private val _tapLocation = MutableStateFlow<Point?>(null) val tapLocation: StateFlow<Point?> = _tapLocation.asStateFlow() // The geoelement closest to the location that the user tapped. private val _selectedGeoElement = MutableStateFlow<GeoElement?>(null) val selectedGeoElement: StateFlow<GeoElement?> = _selectedGeoElement.asStateFlow() private var currentIdentifyJob: Job? = null }
Set up the LocatorTask
A locator task is used to search for places using a geocoding service. Results from this search contain the place location and additional information (attributes
-
In the view model, create a
LocatorTaskproperty namedlocatorbased on the Geocoding service.A locator task is used to convert an address to a point
A point is a type of geometry containing a single set of (geocode) or vice-versa (reverse geocodex,ycoordinates and a spatial reference.Reverse geocoding is the process of converting a point to its nearest address or place. ). An address includes any type of information that distinguishes a place. A locatorA locator is an ArcGIS dataset that stores address information and the rules for translating descriptions of places (such as street addresses or place names) into spatial data that can be displayed on a map. involves finding matching locations for a given address. Reverse-geocoding is the opposite and finds the closest address for a given point.MapViewModel classUse dark colors for code blocks val locator = LocatorTask(uri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer") -
To support the geocode operation, create an
enumnamedCategory. It should have two properties: aStringnamed "label" and aColornamed "color". Each category is searched using itslabeland is distinguished on the map using its associatedcolor.This tutorial uses category filtering to provide accurate search results based on pre-determined place categories. Feel free to modify this list to your specific requirements.
MapViewModel classUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. enum class Category(val label: String, val color: Color) { // CoffeeShop color is brown. CoffeeShop(label = "Coffee shop", color = Color.fromRgba(r =150, g = 75, b= 0, a = 255)), // GasStation color is orange. GasStation(label = "Gas station", color = Color.fromRgba(r = 255, g = 165, b = 0, a =255)), // Food color is purple. Food(label = "Food", color = Color.fromRgba(r = 160, g = 32, b = 240, a = 255)), // Hotel color is blue. Hotel(label = "Hotel", color = Color.fromRgba(r = 0, g = 0, b = 255, a = 255)), ParksOutdoors(label = "Parks and Outdoors", Color.green) } -
Create a suspend function called
findto perform the geocodePlaces() Geocoding is the process of converting text for an address or place to a complete address with a location. search operation. The method takes a parameter of typeCategorythat you created in the previous step to indicate which category of places to search for.MapViewModel classUse dark colors for code blocks suspend fun findPlaces(category: Category) { } -
Clear the previous results by removing all graphics from the graphics overlay. Create and configure new
Geocode. Populate them with theParameters searchparameter (the current map view extent) and the result attribute names.Area MapViewModel classUse dark colors for code blocks suspend fun findPlaces(category: Category) { graphicsOverlay.graphics.clear() val geocodeParameters = GeocodeParameters().apply { searchArea = geoViewExtent resultAttributeNames.addAll(listOf("Place_addr", "PlaceName")) } } -
Perform the search query using
geocode(). Pass in the category'slabeland the geocode parameters.MapViewModel classUse dark colors for code blocks suspend fun findPlaces(category: Category) { graphicsOverlay.graphics.clear() val geocodeParameters = GeocodeParameters().apply { searchArea = geoViewExtent resultAttributeNames.addAll(listOf("Place_addr", "PlaceName")) } val geocodeResultsList = locator.geocode( searchText = category.label, parameters = geocodeParameters ).getOrElse { error -> return logError(error) } } -
Create graphics for each of the results and add them to the graphics overlay.
Populate the
graphicswithOverlay SimpleMarkerSymbols representing each place returned in the search results. This is very similar to the Add a point, line, and polygon tutorial.MapViewModel classUse dark colors for code blocks suspend fun findPlaces(category: Category) { graphicsOverlay.graphics.clear() val geocodeParameters = GeocodeParameters().apply { searchArea = geoViewExtent resultAttributeNames.addAll(listOf("Place_addr", "PlaceName")) } val geocodeResultsList = locator.geocode( searchText = category.label, parameters = geocodeParameters ).getOrElse { error -> return logError(error) } if (geocodeResultsList.isNotEmpty()) { val placeSymbol = SimpleMarkerSymbol( style = SimpleMarkerSymbolStyle.Circle, color = category.color, size = 10f ).apply { outline = SimpleLineSymbol( style = SimpleLineSymbolStyle.Solid, color = Color.white, width = 2f ) } val graphics = geocodeResultsList.map { geocodeResult -> Graphic( geometry = geocodeResult.displayLocation, attributes = geocodeResult.attributes, symbol = placeSymbol ) } graphicsOverlay.graphics.addAll(graphics) } }
Identify the closest geoelement to the user's tap location
An identify
-
Create a function called
identify()that takes aSingleTapConfirmedEvent. In the function, first cancel the current identify job. Then launch a new coroutine and callMapViewProxy.identify()to identify the closest graphic(s) at the tap location.MapViewModel classUse dark colors for code blocks /** * Identifies the tapped screen coordinate in the provided [singleTapConfirmedEvent]. The * identified geoelement is set to [_selectedGeoElement]. * * @since 200.5.0 */ fun identify(singleTapConfirmedEvent: SingleTapConfirmedEvent) { currentIdentifyJob?.cancel() currentIdentifyJob = viewModelScope.launch { val result = mapViewProxy.identify( graphicsOverlay = graphicsOverlay, screenCoordinate = singleTapConfirmedEvent.screenCoordinate, tolerance = 2.dp ) result.onSuccess { identifyGraphicsOverlayResult -> _selectedGeoElement.value = identifyGraphicsOverlayResult.geoElements.firstOrNull() }.onFailure { error -> logError(error) } } }
Add functions to clear the selected geoelement and log errors
-
Add a function that clears the currently selected geoelement
A geoelement refers to any geographic element in a map or map view that can be identified by its location to return attribute information. . Then add a function to log errors.MapViewModel classUse dark colors for code blocks fun clearSelectedGeoElement() { _selectedGeoElement.value = null } private fun logError(error: Throwable) { Log.e(this.javaClass.simpleName, error.message.toString(), error.cause) }
Add a Scaffold with a top bar and a dropdown menu
The Main composable needs some variables to hold state for a dropdown menu, a view model, and the currently selected geoelementScaffold composable to display a dropdown menu, the map view, and a callout.
-
In the
Maincomposable function add the following:Screen - A mutable state boolean to hold whether a dropdown menu, which you'll create in a later step, is expanded.
- An instance of your
Mapclass, using the Jetpack ComposeView Model viewfunction.Model() - A
selectedvariable that collects theGeo Element map, which was defined as aView Model.selected Geo Element State.Flow <Geo Element >
MainScreen()Use dark colors for code blocks @Composable fun MainScreen() { var isDropdownExpanded by remember { mutableStateOf(false) } // Create a ViewModel to handle MapView interactions. val mapViewModel: MapViewModel = viewModel() val selectedGeoElement = mapViewModel.selectedGeoElement.collectAsState().value } -
Add a
Scaffoldthat has a top bar. Add an empty content lambda to theScaffold. That lambda is where you will addMapViewin a later step.MainScreen()Use dark colors for code blocks @Composable fun MainScreen() { var isDropdownExpanded by remember { mutableStateOf(false) } // Create a ViewModel to handle MapView interactions. val mapViewModel: MapViewModel = viewModel() val selectedGeoElement = mapViewModel.selectedGeoElement.collectAsState().value Scaffold( topBar = { TopAppBar( title = { Text(text = stringResource(id = R.string.app_name)) }, ) } ) { } } -
Add an
actionslambda as a parameter to theTop. The lambda will first display a More options icon (the three vertical dots), which the user taps to display a dropdown menu of place categories.App Bar MainScreen()Use dark colors for code blocks @Composable fun MainScreen() { var isDropdownExpanded by remember { mutableStateOf(false) } // Create a ViewModel to handle MapView interactions. val mapViewModel: MapViewModel = viewModel() val selectedGeoElement = mapViewModel.selectedGeoElement.collectAsState().value Scaffold( topBar = { TopAppBar( title = { Text(text = stringResource(id = R.string.app_name)) }, actions = { IconButton(onClick = { isDropdownExpanded = !isDropdownExpanded }) { Icon( imageVector = Icons.Default.MoreVert, contentDescription = "More options" ) } } ) } ) { } } -
The
actionslambda then defines aDropdownthat has aMenu Dropdownfor each of the categories you created in the map view model.Menu Item MainScreen()Use dark colors for code blocks @Composable fun MainScreen() { var isDropdownExpanded by remember { mutableStateOf(false) } // Create a ViewModel to handle MapView interactions. val mapViewModel: MapViewModel = viewModel() val selectedGeoElement = mapViewModel.selectedGeoElement.collectAsState().value Scaffold( topBar = { TopAppBar( title = { Text(text = stringResource(id = R.string.app_name)) }, actions = { IconButton(onClick = { isDropdownExpanded = !isDropdownExpanded }) { Icon( imageVector = Icons.Default.MoreVert, contentDescription = "More options" ) } DropdownMenu( expanded = isDropdownExpanded, onDismissRequest = { isDropdownExpanded = false } ) { DropdownMenuItem( onClick = { mapViewModel.clearSelectedGeoElement() mapViewModel.viewModelScope.launch { mapViewModel.findPlaces(category = MapViewModel.Category.CoffeeShop) isDropdownExpanded = false } }, text = { Text(MapViewModel.Category.CoffeeShop.label) } ) DropdownMenuItem( onClick = { mapViewModel.clearSelectedGeoElement() mapViewModel.viewModelScope.launch { mapViewModel.findPlaces(category = MapViewModel.Category.GasStation) isDropdownExpanded = false } }, text = { Text(MapViewModel.Category.GasStation.label) } ) DropdownMenuItem( onClick = { mapViewModel.clearSelectedGeoElement() mapViewModel.viewModelScope.launch { mapViewModel.findPlaces(category = MapViewModel.Category.Food) isDropdownExpanded = false } }, text = { Text(MapViewModel.Category.Food.label) } ) DropdownMenuItem( onClick = { mapViewModel.clearSelectedGeoElement() mapViewModel.viewModelScope.launch { mapViewModel.findPlaces(category = MapViewModel.Category.Hotel) isDropdownExpanded = false } }, text = { Text(MapViewModel.Category.Hotel.label) } ) DropdownMenuItem( onClick = { mapViewModel.clearSelectedGeoElement() mapViewModel.viewModelScope.launch { mapViewModel.findPlaces(category = MapViewModel.Category.ParksOutdoors) isDropdownExpanded = false } }, text = { Text(MapViewModel.Category.ParksOutdoors.label) } ) } } ) } ) { } }
Add Map View to display the map and interact with the map view model
Add the MapView composable and pass the parameters that do the following:
-
Display the current extent of the map when the extent (visible area) changes.
-
Define a
MapViewProxy -
Attach the graphics overlay to display the graphics that represent places matching the category selected in the dropdown menu.
-
Call the
identify()function when the user taps on the map. You created this function in the map view model.MapView()Use dark colors for code blocks MapView(modifier = Modifier .fillMaxSize() .padding(it), arcGISMap = mapViewModel.map, onVisibleAreaChanged = { newVisibleArea -> mapViewModel.geoViewExtent = newVisibleArea.extent }, mapViewProxy = mapViewModel.mapViewProxy, graphicsOverlays = listOf(mapViewModel.graphicsOverlay), onSingleTapConfirmed = { singleTapConfirmedEvent -> mapViewModel.identify(singleTapConfirmedEvent) }, )
Define Callout Content to display name and address for the found place
When a user taps on a graphic, a callout displays showing the name and address of the geoelement
-
Define a composable function named
Calloutthat displays the name of the place in larger font size and the address of the place in a smaller size.Content CalloutContent()Use dark colors for code blocks /** * Content for the Callout to display information on the tapped graphics overlay and its [selectedElementAttributes] */ @Composable fun CalloutContent( selectedElementAttributes: Map<String, Any?> ) { LazyColumn(contentPadding = PaddingValues(8.dp)) { selectedElementAttributes.forEach { attribute -> item { val style = if (attribute.key == "PlaceName") { MaterialTheme.typography.titleLarge } else { MaterialTheme.typography.bodyMedium } Text( text = "${attribute.value}", fontStyle = FontStyle.Normal, style = style, textAlign = TextAlign.Start ) } } } }
Display information for the found place
The Map needs one more parameter so the callout content displays on top of the map view.
-
Go back to
Mapin your code and add theView contentparameter, which is a lambda that calls theCalloutcomposable defined in the ArcGIS Maps SDK for Kotlin Toolkit. TheCalloutfunction has its owncontentlambda parameter, which in turn calls yourCalloutfunction with the attributes (Content Place,_addr Place) you specified in the map view model .Name The
Modifierpassed toCalloutcalls size functions that restrict the display size of the callout:wrap,Content Size() height(),width.In() MapView()Use dark colors for code blocks MapView(modifier = Modifier .fillMaxSize() .padding(it), arcGISMap = mapViewModel.map, onVisibleAreaChanged = { newVisibleArea -> mapViewModel.geoViewExtent = newVisibleArea.extent }, mapViewProxy = mapViewModel.mapViewProxy, graphicsOverlays = listOf(mapViewModel.graphicsOverlay), onSingleTapConfirmed = { singleTapConfirmedEvent -> mapViewModel.identify(singleTapConfirmedEvent) }, content = if (selectedGeoElement != null) { { Callout( modifier = Modifier .wrapContentSize() .height(120.dp) .widthIn(max = 300.dp), geoElement = selectedGeoElement, tapLocation = mapViewModel.tapLocation.value ) { CalloutContent( selectedElementAttributes = selectedGeoElement.attributes ) } } } else { null } ) -
Click Run > Run > app to run the app.
When the app opens, tap the More options icon in the upper right and select a place category. Then tap one of the places and see its name and address. If you zoom or pan to another area, tap the More options icon again and select a category to display places in the new extent.
Alternatively, you can download the tutorial solution, as follows.
Option 2: Download the solution
-
Click the Download solution link in the right-hand side of this page.
-
Unzip the file to a location on your machine.
-
Run Android Studio.
-
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
-
In the Android view of Android Studio, open app > kotlin+java > com.example.app > MainActivity. Set the
AuthenticationtoMode ..API _KEY MainActivity.ktUse dark colors for code blocks class MainActivity : ComponentActivity() { private enum class AuthenticationMode { API_KEY, USER_AUTH } private val authenticationMode = AuthenticationMode.API_KEY -
Set the
apiproperty with your API key access token.Key MainActivity.ktUse dark colors for code blocks override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) when (authenticationMode) { AuthenticationMode.API_KEY -> { 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.
Run the app
Click Run > Run > app to run the app.
When the app opens, tap the More options icon in the upper right and select a place category. Then tap one of the places and see its name and address. If you zoom or pan to another area, tap the More options icon again and select a category to display places in the new extent.
What's next?
Learn how to use additional API features, ArcGIS location services, and ArcGIS tools in these tutorials: