Learn how to search for places of interest, such as hotels, cafes, and gas stations using the 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. Learn more .

find places

Geocoding Geocoding is the process of converting text for an address or place to a complete address with a location. Learn more is the process of transforming an address or place name to a location on the earth’s surface. A 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. Learn more allows you to quickly find places that meet specific criteria.

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:

  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 ArcGIS Location Services, also referred to as Location Services, are services hosted by Esri that provide geospatial functionality for developing mapping applications. They include the ArcGIS Basemap Styles service, ArcGIS Static Basemap Tiles service, ArcGIS Places service, ArcGIS Geocoding service, ArcGIS Routing service, ArcGIS GeoEnrichment service, and ArcGIS Elevation service. An ArcGIS Location Platform or ArcGIS Online account is required to use the services. Learn more used in this tutorial, you must implement API key authentication API key authentication is a type of authentication that uses an API key to authenticate requests to ArcGIS services and secure portal items. Learn more or user authentication User authentication is a type of authentication that allows users with an ArcGIS account to sign into an application and allow it to access ArcGIS content, services, and resources on their behalf. The typical authorization protocol used is OAuth2.0. Learn more using an ArcGIS Location Platform An ArcGIS Location Platform account, formerly known as an ArcGIS Developer account, is an identity associated with an ArcGIS Location Platform subscription. Learn more or an ArcGIS Online An ArcGIS Online account, also known as an ArcGIS Organization account, is an identity associated with an ArcGIS Online subscription. It can be used to access ArcGIS tools and develop applications with ArcGIS location services for an organization. Learn more account.

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 An access token is an authorization string that provides access to secure ArcGIS content, data, and services. Its capabilities are determined by the privileges it supports. It is obtained by implementing API key authentication, User authentication, or App authentication. Learn more with privileges Privileges are a set of permissions assigned to ArcGIS accounts, developer credentials, and applications that grant access to secure resources and functionality in ArcGIS. Learn more 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 are a set of permissions assigned to ArcGIS accounts, developer credentials, and applications that grant access to secure resources and functionality in ArcGIS. Learn more :

    • Privileges
      • Location services > Basemaps
      • Location services > Geocoding
  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 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. Learn more .

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

    1. In libs.versions.toml, add a [libraries] entry for the dependency. In the module-level build.gradle.kts (app), add the dependency for the lifecycle view model.

      20 collapsed lines
      [versions]
      arcgisMapsKotlin = "200.8.2"
      # Version numbers added by Android Studio New Project Wizard
      agp = "8.9.2"
      kotlin = "2.1.20"
      coreKtx = "1.16.0"
      junit = "4.13.2"
      junitVersion = "1.2.1"
      espressoCore = "3.6.1"
      lifecycleRuntimeKtx = "2.8.7"
      activityCompose = "1.10.1"
      composeBom = "2025.04.00"
      # Other version numbers
      compileSdk = "36"
      minSdk = "28"
      targetSdk = "36"
      media3CommonKtx = "1.5.1"
      [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" }
      15 collapsed lines
      androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
      androidx-ui = { group = "androidx.compose.ui", name = "ui" }
      androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
      androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
      androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
      androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
      androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
      androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
      androidx-media3-common-ktx = { group = "androidx.media3", name = "media3-common-ktx", version.ref = "media3CommonKtx" }
      [plugins]
      android-application = { id = "com.android.application", version.ref = "agp" }
      kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
      kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

Set developer credentials

If you implemented API key authentication API key authentication is a type of authentication that uses an API key to authenticate requests to ArcGIS services and secure portal items. Learn more in the Display a map tutorial, the API key access token will only have the Basemaps privilege. The Find places tutorial requires the Geocoding privilege to find places using the LocatorTask. 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.

  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
    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
    @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.

  1. In Android Studio: in the Android view, open app > kotlin+java > com.example.app > screens > MainScreen.kt, create a map view model that extends ViewModel.

    MainScreen.kt
    65 collapsed lines
    @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
    class MapViewModel() : ViewModel() {
    }
    5 collapsed lines
    @Composable
    fun MainScreen() {
    }
  2. In the MainScreen composable, 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.

    MainScreen.kt
    69 collapsed lines
    @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
    class MapViewModel() : ViewModel() {
    }
    @Composable
    fun MainScreen() {
    }
    MainScreen.kt
    40 collapsed lines
    @file:OptIn(ExperimentalMaterial3Api::class)
    package com.example.app.screens
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.foundation.layout.padding
    import androidx.compose.material3.ExperimentalMaterial3Api
    import androidx.compose.material3.Scaffold
    import androidx.compose.material3.Text
    import androidx.compose.material3.TopAppBar
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.remember
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.res.stringResource
    import com.arcgismaps.mapping.ArcGISMap
    import com.arcgismaps.mapping.BasemapStyle
    import com.arcgismaps.mapping.Viewpoint
    import com.arcgismaps.toolkit.geoviewcompose.MapView
    import com.example.app.R
    @Composable
    fun MainScreen() {
    val map = remember {
    createMap()
    }
    Scaffold(
    topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }
    ) {
    MapView(
    modifier = Modifier.fillMaxSize().padding(it),
    arcGISMap = map
    )
    }
    }
    fun createMap(): ArcGISMap {
    return ArcGISMap(BasemapStyle.ArcGISTopographic).apply {
    initialViewpoint = Viewpoint(
    latitude = 34.0270,
    longitude = -118.8050,
    scale = 72000.0
    )
    }
    }
  3. In the view model, create a map from a BasemapStyle and center the ArcGISMap.initialViewpoint on downtown Santa Monica, CA. Then create a MapViewProxy, which is defined in the ArcGIS Maps SDK for Kotlin Toolkit.

    MapViewModel class
    67 collapsed lines
    @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
    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()
    7 collapsed lines
    }
    @Composable
    fun MainScreen() {
    }
  4. Create a GraphicsOverlay to display graphics A graphic is a visual element composed of a geometry, symbol, and attributes that is displayed on a map or scene. Learn more that represent places of the same category, such as coffee shops.

    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. Learn more , which is the closest geoelement to the tap location. (A graphic is a type of GeoElement.)
    • The current coroutine Job, so the job can be cancelled before launching a new coroutine.
    MapViewModel class
    65 collapsed lines
    @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
    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
    }
    5 collapsed lines
    @Composable
    fun MainScreen() {
    }

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 Attributes are fields and values for a single feature or non-spatial record. They are typically stored in a database or service such as a feature service. Learn more ). Create the locator task along with any variables and methods needed to perform the search and display the results.

  1. In the view model, create a LocatorTask property named locator based on the Geocoding service.

    MapViewModel class
    94 collapsed lines
    @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
    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
    val locator = LocatorTask(uri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")
    7 collapsed lines
    }
    @Composable
    fun MainScreen() {
    }
  2. To support the geocode operation, create an enum named Category. It should have two properties: a String named “label” and a Color named “color”. Each category is searched using its label and is distinguished on the map using its associated color.

    MapViewModel class
    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)
    }
  3. Create a suspend function called findPlaces() to perform the geocode Geocoding is the process of converting text for an address or place to a complete address with a location. Learn more search operation. The method takes a parameter of type Category that you created in the previous step to indicate which category of places to search for.

    MapViewModel class
    108 collapsed lines
    @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
    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
    val locator = LocatorTask(uri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")
    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)
    }
    suspend fun findPlaces(category: Category) {
    }
    7 collapsed lines
    }
    @Composable
    fun MainScreen() {
    }
  4. Clear the previous results by removing all graphics from the graphics overlay. Create and configure new GeocodeParameters. Populate them with the searchArea parameter (the current map view extent) and the result attribute names.

    MapViewModel class
    108 collapsed lines
    @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
    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
    val locator = LocatorTask(uri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")
    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)
    }
    suspend fun findPlaces(category: Category) {
    graphicsOverlay.graphics.clear()
    val geocodeParameters = GeocodeParameters().apply {
    searchArea = geoViewExtent
    resultAttributeNames.addAll(listOf("Place_addr", "PlaceName"))
    }
    }
    7 collapsed lines
    }
    @Composable
    fun MainScreen() {
    }
  5. Perform the search query using geocode(). Pass in the category’s label and the geocode parameters.

    MapViewModel class
    108 collapsed lines
    @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
    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
    val locator = LocatorTask(uri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")
    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)
    }
    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)
    }
    }
    7 collapsed lines
    }
    @Composable
    fun MainScreen() {
    }
  6. Create graphics for each of the results and add them to the graphics overlay.

    MapViewModel class
    108 collapsed lines
    @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
    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
    val locator = LocatorTask(uri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")
    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)
    }
    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)
    }
    }
    7 collapsed lines
    }
    @Composable
    fun MainScreen() {
    }

Identify the closest geoelement to the user’s tap location

An identify Identify is a query based on a point on a map or scene that is used to find feature, graphic, or raster cell data. Learn more operation can be used to get information about a 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. Learn more (such as a graphic A graphic is a visual element composed of a geometry, symbol, and attributes that is displayed on a map or scene. Learn more ) at a location where the user has tapped on the map.

  1. Create a function called identify() that takes a SingleTapConfirmedEvent . In the function, first cancel the current identify job. Then launch a new coroutine and call MapViewProxy.identify() to identify the closest graphic(s) at the tap location.

    MapViewModel class
    /**
    * 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

  1. 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. Learn more . Then add a function to log errors.

    MapViewModel class
    170 collapsed lines
    @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
    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
    val locator = LocatorTask(uri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")
    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)
    }
    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)
    }
    }
    /**
    * 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)
    }
    }
    }
    fun clearSelectedGeoElement() {
    _selectedGeoElement.value = null
    }
    private fun logError(error: Throwable) {
    Log.e(this.javaClass.simpleName, error.message.toString(), error.cause)
    }
    7 collapsed lines
    }
    @Composable
    fun MainScreen() {
    }

Add a Scaffold with a top bar and a dropdown menu

The MainScreen composable needs some variables to hold state for a dropdown menu, a view model, and 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. Learn more . It also needs a Scaffold composable to display a dropdown menu, the map view, and a callout.

  1. In the MainScreen composable function add the following:

    • A mutable state boolean to hold whether a dropdown menu, which you’ll create in a later step, is expanded.
    • An instance of your MapViewModel class, using the Jetpack Compose viewModel() function.
    • A selectedGeoElement variable that collects the mapViewModel.selectedGeoElement, which was defined as a StateFlow<GeoElement>.
    MainScreen()
    180 collapsed lines
    @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
    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
    val locator = LocatorTask(uri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")
    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)
    }
    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)
    }
    }
    /**
    * 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)
    }
    }
    }
    fun clearSelectedGeoElement() {
    _selectedGeoElement.value = null
    }
    private fun logError(error: Throwable) {
    Log.e(this.javaClass.simpleName, error.message.toString(), error.cause)
    }
    }
    @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
    }
  2. Add a Scaffold that has a top bar. Add an empty content lambda to the Scaffold. That lambda is where you will add MapView in a later step.

    MainScreen()
    180 collapsed lines
    @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
    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
    val locator = LocatorTask(uri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")
    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)
    }
    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)
    }
    }
    /**
    * 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)
    }
    }
    }
    fun clearSelectedGeoElement() {
    _selectedGeoElement.value = null
    }
    private fun logError(error: Throwable) {
    Log.e(this.javaClass.simpleName, error.message.toString(), error.cause)
    }
    }
    @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)) },
    ) }
    ) {
    }
    }
  3. Add an actions lambda as a parameter to the TopAppBar. 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.

    MainScreen()
    180 collapsed lines
    @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
    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
    val locator = LocatorTask(uri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")
    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)
    }
    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)
    }
    }
    /**
    * 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)
    }
    }
    }
    fun clearSelectedGeoElement() {
    _selectedGeoElement.value = null
    }
    private fun logError(error: Throwable) {
    Log.e(this.javaClass.simpleName, error.message.toString(), error.cause)
    }
    }
    @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"
    )
    }
    }
    ) }
    ) {
    }
    }
  4. The actions lambda then defines a DropdownMenu that has a DropdownMenuItem for each of the categories you created in the map view model.

    MainScreen()
    180 collapsed lines
    @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
    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
    val locator = LocatorTask(uri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")
    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)
    }
    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)
    }
    }
    /**
    * 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)
    }
    }
    }
    fun clearSelectedGeoElement() {
    _selectedGeoElement.value = null
    }
    private fun logError(error: Throwable) {
    Log.e(this.javaClass.simpleName, error.message.toString(), error.cause)
    }
    }
    @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 MapView 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()
    264 collapsed lines
    @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
    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
    val locator = LocatorTask(uri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")
    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)
    }
    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)
    }
    }
    /**
    * 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)
    }
    }
    }
    fun clearSelectedGeoElement() {
    _selectedGeoElement.value = null
    }
    private fun logError(error: Throwable) {
    Log.e(this.javaClass.simpleName, error.message.toString(), error.cause)
    }
    }
    @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) }
    )
    }
    }
    ) }
    ) {
    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)
    },
    )
    4 collapsed lines
    }
    }

Define CalloutContent 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 A geoelement refers to any geographic element in a map or map view that can be identified by its location to return attribute information. Learn more closest to the tapped location on the map.

  1. Define a composable function named CalloutContent that displays the name of the place in larger font size and the address of the place in a smaller size.

    CalloutContent()
    284 collapsed lines
    @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
    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
    val locator = LocatorTask(uri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")
    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)
    }
    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)
    }
    }
    /**
    * 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)
    }
    }
    }
    fun clearSelectedGeoElement() {
    _selectedGeoElement.value = null
    }
    private fun logError(error: Throwable) {
    Log.e(this.javaClass.simpleName, error.message.toString(), error.cause)
    }
    }
    @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) }
    )
    }
    }
    ) }
    ) {
    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 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 MapView needs one more parameter so the callout content displays on top of the map view.

  1. Go back to MapView in your code and add the content parameter, which is a lambda that calls the Callout composable defined in the ArcGIS Maps SDK for Kotlin Toolkit. The Callout function has its own content lambda parameter, which in turn calls your CalloutContent function with the attributes (Place_addr, PlaceName) you specified in the map view model .

    MapView()
    264 collapsed lines
    @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
    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
    val locator = LocatorTask(uri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")
    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)
    }
    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)
    }
    }
    /**
    * 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)
    }
    }
    }
    fun clearSelectedGeoElement() {
    _selectedGeoElement.value = null
    }
    private fun logError(error: Throwable) {
    Log.e(this.javaClass.simpleName, error.message.toString(), error.cause)
    }
    }
    @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) }
    )
    }
    }
    ) }
    ) {
    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
    }
    )
    32 collapsed lines
    }
    }
    /**
    * 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
    )
    }
    }
    }
    }
  2. 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

  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 ArcGIS Location Services, also referred to as Location Services, are services hosted by Esri that provide geospatial functionality for developing mapping applications. They include the ArcGIS Basemap Styles service, ArcGIS Static Basemap Tiles service, ArcGIS Places service, ArcGIS Geocoding service, ArcGIS Routing service, ArcGIS GeoEnrichment service, and ArcGIS Elevation service. An ArcGIS Location Platform or ArcGIS Online account is required to use the services. Learn more , 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
    14 collapsed lines
    package com.example.app
    import android.os.Bundle
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.activity.enableEdgeToEdge
    import com.arcgismaps.ApiKey
    import com.arcgismaps.ArcGISEnvironment
    import com.arcgismaps.httpcore.authentication.OAuthUserConfiguration
    import com.arcgismaps.toolkit.authentication.AuthenticatorState
    import com.arcgismaps.toolkit.authentication.DialogAuthenticator
    import com.example.app.screens.MainScreen
    import com.example.app.ui.theme.TutorialTheme
    class MainActivity : ComponentActivity() {
    private enum class AuthenticationMode { API_KEY, USER_AUTH }
    private val authenticationMode = AuthenticationMode.API_KEY
    42 collapsed lines
    private val authenticatorState = AuthenticatorState()
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    when (authenticationMode) {
    AuthenticationMode.API_KEY -> {
    ArcGISEnvironment.apiKey = ApiKey.create("YOUR_ACCESS_TOKEN")
    }
    AuthenticationMode.USER_AUTH -> {
    authenticatorState.oAuthUserConfigurations = listOf(
    OAuthUserConfiguration(
    portalUrl = "https://www.arcgis.com",
    clientId = "YOUR_CLIENT_ID",
    redirectUrl = "YOUR_REDIRECT_URL"
    )
    )
    }
    }
    enableEdgeToEdge()
    setContent {
    TutorialTheme {
    MainScreen()
    if (authenticationMode == AuthenticationMode.USER_AUTH) {
    DialogAuthenticator(authenticatorState)
    }
    }
    }
    }
    }
  2. Set the apiKey property with your API key access token.

    MainActivity.kt
    22 collapsed lines
    package com.example.app
    import android.os.Bundle
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.activity.enableEdgeToEdge
    import com.arcgismaps.ApiKey
    import com.arcgismaps.ArcGISEnvironment
    import com.arcgismaps.httpcore.authentication.OAuthUserConfiguration
    import com.arcgismaps.toolkit.authentication.AuthenticatorState
    import com.arcgismaps.toolkit.authentication.DialogAuthenticator
    import com.example.app.screens.MainScreen
    import com.example.app.ui.theme.TutorialTheme
    class MainActivity : ComponentActivity() {
    private enum class AuthenticationMode { API_KEY, USER_AUTH }
    private val authenticationMode = AuthenticationMode.API_KEY
    private val authenticatorState = AuthenticatorState()
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    when (authenticationMode) {
    AuthenticationMode.API_KEY -> {
    ArcGISEnvironment.apiKey = ApiKey.create("YOUR_ACCESS_TOKEN")
    }
    30 collapsed lines
    AuthenticationMode.USER_AUTH -> {
    authenticatorState.oAuthUserConfigurations = listOf(
    OAuthUserConfiguration(
    portalUrl = "https://www.arcgis.com",
    clientId = "YOUR_CLIENT_ID",
    redirectUrl = "YOUR_REDIRECT_URL"
    )
    )
    }
    }
    enableEdgeToEdge()
    setContent {
    TutorialTheme {
    MainScreen()
    if (authenticationMode == AuthenticationMode.USER_AUTH) {
    DialogAuthenticator(authenticatorState)
    }
    }
    }
    }
    }

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: