With the ArcGIS Maps SDK for Kotlin, you can perform advanced operations on the composable MapView and SceneView components using proxy classes like MapViewProxy and SceneViewProxy. The proxy classes provides access to methods and properties that aren't suitable for use in a composable function's initializer or a modifier. They enable operations such as:
- Identifying features on the map.
- Setting the viewpoint with animation.
- Converting coordinates between the map and screen.
- Exporting a snapshot of the view's image.
To use a proxy, you must first create a one-to-one relationship between it and the composable view. This is done by passing a proxy instance to the composable Map
or Scene
function.
MapView(
arcGISMap = map,
graphicsOverlays = listOf(mapViewModel.graphicsOverlay),
mapViewProxy = mapViewModel.mapViewProxy
)
Note: The example above shows a Map
that uses a Map
. Similarly, you would use a Scene
with a Scene
.
Identify features
The following example demonstrates how to use the Map
to identify features on a map. When a user taps the screen, the identify
method is called to retrieve features at that location and display their associated popups or attributes.
// In the ViewModel:
class MapViewModel(application: Application) : AndroidViewModel(application) {
// ....
// Proxy object used to help identify the feature at the tapped coordinate
val mapViewProxy = MapViewProxy()
fun identifyFeatureAt(
screenCoordinate: ScreenCoordinate,
onFeatureSelected: (ArcGISFeature) -> Unit
) {
viewModelScope.launch {
// Identify the feature tapped at the given screen coords
val identifyLayerResults = mapViewProxy.identifyLayers(
screenCoordinate = screenCoordinate,
tolerance = 5.dp,
returnPopupsOnly = false,
maximumResults = 1
).getOrThrow()
// Identify the layer
val identifyLayerResult = identifyLayerResults.firstOrNull()
// Identify the feature
val foundFeature = identifyLayerResult?.geoElements?.firstOrNull() as? ArcGISFeature
if (foundFeature != null) {
// Report the selected feature to UI
onFeatureSelected(foundFeature)
}
}
}
// In the Composable:
@Composable
fun MainScreen(modifier: Modifier, viewmodel: MapViewModel = viewModel()) {
var selectedFeature by remember { mutableStateOf<ArcGISFeature?>(null) }
MapView(
modifier = modifier.fillMaxSize(),
arcGISMap = viewmodel.map,
mapViewProxy = viewmodel.mapViewProxy,
onSingleTapConfirmed = { tapEvent ->
viewmodel.identifyFeatureAt(
screenCoordinate = tapEvent.screenCoordinate,
onFeatureSelected = { feature ->
selectedFeature = feature
}
)
}
) {
}
}
Set a Viewpoint
You can use the Map
to programmatically change the viewpoint of the map. This is useful for things like zooming to a specific feature or panning to a new location.
// Identify the feature
val foundFeature = identifyLayerResult?.geoElements?.firstOrNull() as? ArcGISFeature
if (foundFeature != null) {
// Animate the viewpoint to center on the feature.
foundFeature.geometry?.let { centerPoint ->
mapViewProxy.setViewpointAnimated(
viewpoint = Viewpoint(centerPoint),
duration = 300.milliseconds,
curve = AnimationCurve.EaseOutSine
)
}
// Report the selected feature to UI
onFeatureSelected(foundFeature)
}
Display a Callout on a GeoElement
When a user taps on a map and a feature is identified, you often want to display a Popup, or Callout, to show more information. You can use a mutable
property in your ViewModel to hold the identified Geo
. When this property is updated, Compose automatically recomposes the UI and displays the Callout
.
The following example combines the above patterns to create a full workflow.
- A user taps the map, triggering the
on
event handler.Single Tap Confirmed - The ViewModel's
identify
function is called, which uses theFeature At Map
to find a feature at the tapped location.View Proxy - The
set
function is used to gracefully move the map's viewpoint to the identified feature.Viewpoint Animated - The ViewModel updates its
selected
state.Feature - Compose detects the state change and displays a
Callout
at the feature's location, using its attributes to populate the content.
This seamless pattern demonstrates how Map
and composable state work together to build a responsive and interactive user experience.

// In the Composable:
@Composable
fun MainScreen(modifier: Modifier, viewmodel: MapViewModel = viewModel()) {
var selectedFeature by remember { mutableStateOf<ArcGISFeature?>(null) }
MapView(
modifier = modifier.fillMaxSize(),
arcGISMap = viewmodel.map,
mapViewProxy = viewmodel.mapViewProxy,
onSingleTapConfirmed = { tapEvent ->
viewmodel.identifyFeatureAt(
screenCoordinate = tapEvent.screenCoordinate,
onFeatureSelected = { feature ->
selectedFeature = feature
}
)
}
) {
selectedFeature?.let { selectedFeature ->
Callout(geoElement = selectedFeature) {
CalloutContent(
selectedElementAttributes = viewmodel.filterAttributes(
attributes = selectedFeature.attributes
)
)
}
}
}
}
@Composable
fun CalloutContent(selectedElementAttributes: Map<String, Any?>) {
LazyColumn(
modifier = Modifier.widthIn(max = 250.dp),
contentPadding = PaddingValues(8.dp)
) {
selectedElementAttributes.forEach { attribute ->
item {
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top
) {
Text(
text = "${attribute.key}:",
fontStyle = FontStyle.Italic,
style = MaterialTheme.typography.labelMedium
)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = "${attribute.value}",
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.End
)
}
}
}
}
}
// In the ViewModel:
class MapViewModel(application: Application) : AndroidViewModel(application) {
// ....
// Feature table of street trees in Portland.
private val featureTable = ServiceFeatureTable(
uri = "https://services2.arcgis.com/ZQgQTuoyBrtmoGdP/arcgis/rest/services/Trees_of_Portland/FeatureServer/0"
)
val map = ArcGISMap(BasemapStyle.ArcGISTopographic).apply {
// Set a viewpoint to downtown Portland, OR.
initialViewpoint = Viewpoint(latitude = 45.5266, longitude = -122.6219, scale = 6000.0)
// Create a feature layer from the feature table and add it to the map.
operationalLayers.add(element = FeatureLayer.createWithFeatureTable(featureTable))
}
// Proxy object used to help identify the feature at the tapped coordinate
val mapViewProxy = MapViewProxy()
fun identifyFeatureAt(
screenCoordinate: ScreenCoordinate,
onFeatureSelected: (ArcGISFeature) -> Unit
) {
viewModelScope.launch {
// Identify the feature tapped at the given screen coords
val identifyLayerResults = mapViewProxy.identifyLayers(
screenCoordinate = screenCoordinate,
tolerance = 5.dp,
returnPopupsOnly = false,
maximumResults = 1
).getOrThrow()
// Identify the layer
val identifyLayerResult = identifyLayerResults.firstOrNull()
// Identify the feature
val foundFeature = identifyLayerResult?.geoElements?.firstOrNull() as? ArcGISFeature
if (foundFeature != null) {
// Animate the viewpoint to center on the feature.
foundFeature.geometry?.let { centerPoint ->
mapViewProxy.setViewpointAnimated(
viewpoint = Viewpoint(centerPoint),
duration = 300.milliseconds,
curve = AnimationCurve.EaseOutSine
)
}
// Report the selected feature to UI
onFeatureSelected(foundFeature)
}
}
}
// Helper function for simple Callout content
fun filterAttributes(attributes: Map<String, Any?>): Map<String, Any?> {
// Filter undesired feature attributes like, empty or null values and GlobalIDs.
return attributes
.filter { attribute -> attribute.value != null }
.filter { attribute -> attribute.value.toString().trim().isNotEmpty() }
.filter { attribute -> !attribute.key.contains("GlobalID") }
}
}
Note:
- See the tutorial guide Display device location to learn how to display the current device location on a map or scene.
- See the sample app Identify layer features to learn how to identify features in all layers in a map.
- See the sample app Show popup to learn how to show a predefined popup from a web map.
- See the sample app Show callout to learn how to show a callout with the latitude and longitude of user-tapped points.