Learn how to display the current device location on a map

You can display the device location on a map or scene. This is important for workflows that require the user’s current location, such as finding nearby businesses, navigating from the current location, or identifying and collecting geospatial information.
By default, location display uses the device’s location provider. Your app can also process input from other location providers, such as an external GPS receiver or a provider that returns a simulated location. For more information, see the Show device location topic.
Prerequisites
Before starting this tutorial, you need the following:
-
An ArcGIS Location Platform or ArcGIS Online account.
-
A development and deployment environment that meets the system requirements.
-
An IDE for Android development in Kotlin.
Develop or download
You have two options for completing this tutorial:
Option 1: Develop the code
Open an Android Studio project
-
Open the project you created by completing the Display a map tutorial.
-
Continue with the following instructions to display the current device location on a map or scene.
-
Modify the old project for use in this new tutorial.
-
On your file system, delete the .idea folder, if present, at the top level of your project.
-
In the Android view, open app > res > values > strings.xml.
In the
<string name="app_name">element, change the text content to Display device location.strings.xml<resources><string name="app_name">Display device location</string></resources> -
In the Android view, open Gradle Scripts > settings.gradle.kts.
Change the value of
rootProject.nameto “Display device location”.settings.gradle.kts14 collapsed linespluginManagement {repositories {google {content {includeGroupByRegex("com\\.android.*")includeGroupByRegex("com\\.google.*")includeGroupByRegex("androidx.*")}}mavenCentral()gradlePluginPortal()}}dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {google()mavenCentral()maven { url = uri("https://esri.jfrog.io/artifactory/arcgis") }}}rootProject.name = "Display device location"include(":app") -
Click File > Sync Project with Gradle files. Android Studio will recognize your changes and create a new .idea folder.
-
Add import statements
-
In the Android view, open app > kotlin+java > com.example.app > MainScreen.kt. Replace the import statements with the imports needed for this tutorial.
MainScreen.kt@file:OptIn(ExperimentalMaterial3Api::class)package com.example.app.screensimport android.Manifestimport android.content.Contextimport android.content.pm.PackageManagerimport android.widget.Toastimport androidx.activity.compose.rememberLauncherForActivityResultimport androidx.activity.result.contract.ActivityResultContractsimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.rememberimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.ui.Modifierimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.stringResourceimport androidx.core.content.ContextCompatimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.location.LocationDisplayAutoPanModeimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplayimport com.example.app.Rimport kotlinx.coroutines.launch
Check location permissions
-
In the Android view, open app > manifests > AndroidManifest.xml.
In AndroidManifest.xml, add permissions for coarse and fine location.
AndroidManifest.xml2 collapsed lines<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />41 collapsed lines<applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.Tutorial"tools:targetApi="31"><activityandroid:name=".MainActivity"android:exported="true"android:label="@string/app_name"android:theme="@style/Theme.Tutorial"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activityandroid:name="com.arcgismaps.toolkit.authentication.AuthenticationActivity"android:configChanges="keyboard|keyboardHidden|orientation|screenSize"android:exported="true"android:launchMode="singleTop" ><intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><dataandroid:scheme="your_redirect_url_scheme"android:host="your_redirect_url_host" /></intent-filter></activity></application></manifest> -
In MainScreen.kt, create a top-level function named
checkPermissions()to check whether both coarse and fine location permissions are granted. This function takes the current localContextas a parameter, and returnstrueif both permissions are granted andfalseotherwise.MainScreen.kt61 collapsed lines@file:OptIn(ExperimentalMaterial3Api::class)package com.example.app.screensimport android.Manifestimport android.content.Contextimport android.content.pm.PackageManagerimport android.widget.Toastimport androidx.activity.compose.rememberLauncherForActivityResultimport androidx.activity.result.contract.ActivityResultContractsimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.rememberimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.ui.Modifierimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.stringResourceimport androidx.core.content.ContextCompatimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.location.LocationDisplayAutoPanModeimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplayimport com.example.app.Rimport kotlinx.coroutines.launch@Composablefun 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)}}fun checkPermissions(context: Context): Boolean {// Check permissions to see if both permissions are granted.// Coarse location permission.val permissionCheckCoarseLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED// Fine location permission.val permissionCheckFineLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTEDreturn permissionCheckCoarseLocation && permissionCheckFineLocation}4 collapsed linesfun showError(context: Context, message: String) {Toast.makeText(context, message, Toast.LENGTH_LONG).show()}
Show device location if permissions were granted
-
In the
MainScreencomposable, you will add code that creates a location display and sets the auto pan mode.Get the local
Contextand use it to set theArcGISEnvironment.applicationContextproperty.MainScreen.kt34 collapsed lines@file:OptIn(ExperimentalMaterial3Api::class)package com.example.app.screensimport android.Manifestimport android.content.Contextimport android.content.pm.PackageManagerimport android.widget.Toastimport androidx.activity.compose.rememberLauncherForActivityResultimport androidx.activity.result.contract.ActivityResultContractsimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.rememberimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.ui.Modifierimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.stringResourceimport androidx.core.content.ContextCompatimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.location.LocationDisplayAutoPanModeimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplayimport com.example.app.Rimport kotlinx.coroutines.launch@Composablefun MainScreen() {val context = LocalContext.currentval coroutineScope = rememberCoroutineScope()ArcGISEnvironment.applicationContext = context.applicationContextval map = remember { createMap() }Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) {MapView(modifier = Modifier.fillMaxSize().padding(it),arcGISMap = map,)}}30 collapsed linesfun createMap(): ArcGISMap {return ArcGISMap(BasemapStyle.ArcGISTopographic).apply {initialViewpoint = Viewpoint(latitude = 34.0270,longitude = -118.8050,scale = 72000.0)}}fun checkPermissions(context: Context): Boolean {// Check permissions to see if both permissions are granted.// Coarse location permission.val permissionCheckCoarseLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED// Fine location permission.val permissionCheckFineLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTEDreturn permissionCheckCoarseLocation && permissionCheckFineLocation}fun showError(context: Context, message: String) {Toast.makeText(context, message, Toast.LENGTH_LONG).show()} -
Create a
LocationDisplayby calling the composable functionrememberLocationDisplay()defined in the ArcGIS Maps SDK for Kotlin Toolkit. Then set aLocationDisplayAutoPanModethat centers the map at the device location.MainScreen.kt34 collapsed lines@file:OptIn(ExperimentalMaterial3Api::class)package com.example.app.screensimport android.Manifestimport android.content.Contextimport android.content.pm.PackageManagerimport android.widget.Toastimport androidx.activity.compose.rememberLauncherForActivityResultimport androidx.activity.result.contract.ActivityResultContractsimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.rememberimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.ui.Modifierimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.stringResourceimport androidx.core.content.ContextCompatimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.location.LocationDisplayAutoPanModeimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplayimport com.example.app.Rimport kotlinx.coroutines.launch@Composablefun MainScreen() {val context = LocalContext.currentval coroutineScope = rememberCoroutineScope()ArcGISEnvironment.applicationContext = context.applicationContext// Create and remember a location display with a recenter auto pan mode.val locationDisplay = rememberLocationDisplay().apply {setAutoPanMode(LocationDisplayAutoPanMode.Recenter)}val map = remember { createMap() }Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) {MapView(modifier = Modifier.fillMaxSize().padding(it),arcGISMap = map,)}}30 collapsed linesfun createMap(): ArcGISMap {return ArcGISMap(BasemapStyle.ArcGISTopographic).apply {initialViewpoint = Viewpoint(latitude = 34.0270,longitude = -118.8050,scale = 72000.0)}}fun checkPermissions(context: Context): Boolean {// Check permissions to see if both permissions are granted.// Coarse location permission.val permissionCheckCoarseLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED// Fine location permission.val permissionCheckFineLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTEDreturn permissionCheckCoarseLocation && permissionCheckFineLocation}fun showError(context: Context, message: String) {Toast.makeText(context, message, Toast.LENGTH_LONG).show()} -
Set the
locationDisplayproperty ofMapViewusinglocationDisplay, which we just created.MainScreen.kt34 collapsed lines@file:OptIn(ExperimentalMaterial3Api::class)package com.example.app.screensimport android.Manifestimport android.content.Contextimport android.content.pm.PackageManagerimport android.widget.Toastimport androidx.activity.compose.rememberLauncherForActivityResultimport androidx.activity.result.contract.ActivityResultContractsimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.rememberimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.ui.Modifierimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.stringResourceimport androidx.core.content.ContextCompatimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.location.LocationDisplayAutoPanModeimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplayimport com.example.app.Rimport kotlinx.coroutines.launch@Composablefun MainScreen() {val context = LocalContext.currentval coroutineScope = rememberCoroutineScope()ArcGISEnvironment.applicationContext = context.applicationContext// Create and remember a location display with a recenter auto pan mode.val locationDisplay = rememberLocationDisplay().apply {setAutoPanMode(LocationDisplayAutoPanMode.Recenter)}val map = remember { createMap() }Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) {MapView(modifier = Modifier.fillMaxSize().padding(it),arcGISMap = map,locationDisplay = locationDisplay)}}30 collapsed linesfun createMap(): ArcGISMap {return ArcGISMap(BasemapStyle.ArcGISTopographic).apply {initialViewpoint = Viewpoint(latitude = 34.0270,longitude = -118.8050,scale = 72000.0)}}fun checkPermissions(context: Context): Boolean {// Check permissions to see if both permissions are granted.// Coarse location permission.val permissionCheckCoarseLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED// Fine location permission.val permissionCheckFineLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTEDreturn permissionCheckCoarseLocation && permissionCheckFineLocation}fun showError(context: Context, message: String) {Toast.makeText(context, message, Toast.LENGTH_LONG).show()} -
You will start location display, provided both permissions were granted.
To do so, call
checkPermissions(), which returns true if both coarse and fine location permissions are granted. If the return value is true, call theLaunchedEffectcomposable. In theLaunchedEffectblock, callstart()on the data source for the location display.MainScreen.kt34 collapsed lines@file:OptIn(ExperimentalMaterial3Api::class)package com.example.app.screensimport android.Manifestimport android.content.Contextimport android.content.pm.PackageManagerimport android.widget.Toastimport androidx.activity.compose.rememberLauncherForActivityResultimport androidx.activity.result.contract.ActivityResultContractsimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.rememberimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.ui.Modifierimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.stringResourceimport androidx.core.content.ContextCompatimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.location.LocationDisplayAutoPanModeimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplayimport com.example.app.Rimport kotlinx.coroutines.launch@Composablefun MainScreen() {val context = LocalContext.currentval coroutineScope = rememberCoroutineScope()ArcGISEnvironment.applicationContext = context.applicationContext// Create and remember a location display with a recenter auto pan mode.val locationDisplay = rememberLocationDisplay().apply {setAutoPanMode(LocationDisplayAutoPanMode.Recenter)}if (checkPermissions(context)) {// Permissions are already granted.LaunchedEffect(Unit) {locationDisplay.dataSource.start()}} else {}val map = remember { createMap() }Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) {MapView(modifier = Modifier.fillMaxSize().padding(it),arcGISMap = map,locationDisplay = locationDisplay)}}30 collapsed linesfun createMap(): ArcGISMap {return ArcGISMap(BasemapStyle.ArcGISTopographic).apply {initialViewpoint = Viewpoint(latitude = 34.0270,longitude = -118.8050,scale = 72000.0)}}fun checkPermissions(context: Context): Boolean {// Check permissions to see if both permissions are granted.// Coarse location permission.val permissionCheckCoarseLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED// Fine location permission.val permissionCheckFineLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTEDreturn permissionCheckCoarseLocation && permissionCheckFineLocation}fun showError(context: Context, message: String) {Toast.makeText(context, message, Toast.LENGTH_LONG).show()}
Request location permissions if they were not granted
-
Create a composable function named
RequestPermissionsthat takes two parameters: one namedcontextand the other namedonPermissionsGranted, which is a function to be executed after requested permissions have been granted.MainScreen.kt72 collapsed lines@file:OptIn(ExperimentalMaterial3Api::class)package com.example.app.screensimport android.Manifestimport android.content.Contextimport android.content.pm.PackageManagerimport android.widget.Toastimport androidx.activity.compose.rememberLauncherForActivityResultimport androidx.activity.result.contract.ActivityResultContractsimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.rememberimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.ui.Modifierimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.stringResourceimport androidx.core.content.ContextCompatimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.location.LocationDisplayAutoPanModeimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplayimport com.example.app.Rimport kotlinx.coroutines.launch@Composablefun MainScreen() {val context = LocalContext.currentval coroutineScope = rememberCoroutineScope()ArcGISEnvironment.applicationContext = context.applicationContext// Create and remember a location display with a recenter auto pan mode.val locationDisplay = rememberLocationDisplay().apply {setAutoPanMode(LocationDisplayAutoPanMode.Recenter)}if (checkPermissions(context)) {// Permissions are already granted.LaunchedEffect(Unit) {locationDisplay.dataSource.start()}} else {}val map = remember { createMap() }Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) {MapView(modifier = Modifier.fillMaxSize().padding(it),arcGISMap = map,locationDisplay = locationDisplay)}}@Composablefun RequestPermissions(context: Context, onPermissionsGranted: () -> Unit) {}30 collapsed linesfun createMap(): ArcGISMap {return ArcGISMap(BasemapStyle.ArcGISTopographic).apply {initialViewpoint = Viewpoint(latitude = 34.0270,longitude = -118.8050,scale = 72000.0)}}fun checkPermissions(context: Context): Boolean {// Check permissions to see if both permissions are granted.// Coarse location permission.val permissionCheckCoarseLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED// Fine location permission.val permissionCheckFineLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTEDreturn permissionCheckCoarseLocation && permissionCheckFineLocation}fun showError(context: Context, message: String) {Toast.makeText(context, message, Toast.LENGTH_LONG).show()} -
In
RequestPermissions, create anActivityResultLauncherby calling the composable functionrememberLauncherForActivityResult().Check that the value for each map entry in
permissionsis true. If both values are true, call theonPermissionsGrantedcallback function. If values are not both true, show an error message.You can show an error message in various ways. The code in this tutorial calls a custom function named
showError(), which is defined like this:MainScreen.kt103 collapsed lines@file:OptIn(ExperimentalMaterial3Api::class)package com.example.app.screensimport android.Manifestimport android.content.Contextimport android.content.pm.PackageManagerimport android.widget.Toastimport androidx.activity.compose.rememberLauncherForActivityResultimport androidx.activity.result.contract.ActivityResultContractsimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.rememberimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.ui.Modifierimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.stringResourceimport androidx.core.content.ContextCompatimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.location.LocationDisplayAutoPanModeimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplayimport com.example.app.Rimport kotlinx.coroutines.launch@Composablefun MainScreen() {val context = LocalContext.currentval coroutineScope = rememberCoroutineScope()ArcGISEnvironment.applicationContext = context.applicationContext// Create and remember a location display with a recenter auto pan mode.val locationDisplay = rememberLocationDisplay().apply {setAutoPanMode(LocationDisplayAutoPanMode.Recenter)}if (checkPermissions(context)) {// Permissions are already granted.LaunchedEffect(Unit) {locationDisplay.dataSource.start()}} else {}val map = remember { createMap() }Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) {MapView(modifier = Modifier.fillMaxSize().padding(it),arcGISMap = map,locationDisplay = locationDisplay)}}@Composablefun RequestPermissions(context: Context, onPermissionsGranted: () -> Unit) {}fun createMap(): ArcGISMap {return ArcGISMap(BasemapStyle.ArcGISTopographic).apply {initialViewpoint = Viewpoint(latitude = 34.0270,longitude = -118.8050,scale = 72000.0)}}fun checkPermissions(context: Context): Boolean {// Check permissions to see if both permissions are granted.// Coarse location permission.val permissionCheckCoarseLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED// Fine location permission.val permissionCheckFineLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTEDreturn permissionCheckCoarseLocation && permissionCheckFineLocation}fun showError(context: Context, message: String) {Toast.makeText(context, message, Toast.LENGTH_LONG).show()}MainScreen.kt72 collapsed lines@file:OptIn(ExperimentalMaterial3Api::class)package com.example.app.screensimport android.Manifestimport android.content.Contextimport android.content.pm.PackageManagerimport android.widget.Toastimport androidx.activity.compose.rememberLauncherForActivityResultimport androidx.activity.result.contract.ActivityResultContractsimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.rememberimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.ui.Modifierimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.stringResourceimport androidx.core.content.ContextCompatimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.location.LocationDisplayAutoPanModeimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplayimport com.example.app.Rimport kotlinx.coroutines.launch@Composablefun MainScreen() {val context = LocalContext.currentval coroutineScope = rememberCoroutineScope()ArcGISEnvironment.applicationContext = context.applicationContext// Create and remember a location display with a recenter auto pan mode.val locationDisplay = rememberLocationDisplay().apply {setAutoPanMode(LocationDisplayAutoPanMode.Recenter)}if (checkPermissions(context)) {// Permissions are already granted.LaunchedEffect(Unit) {locationDisplay.dataSource.start()}} else {}val map = remember { createMap() }Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) {MapView(modifier = Modifier.fillMaxSize().padding(it),arcGISMap = map,locationDisplay = locationDisplay)}}@Composablefun RequestPermissions(context: Context, onPermissionsGranted: () -> Unit) {// Create an activity result launcher using permissions contract and handle the result.val activityResultLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->// Check if both fine & coarse location permissions are true.if (permissions.all { it.value }) {onPermissionsGranted()} else {showError(context, "Location permissions were denied")}}}30 collapsed linesfun createMap(): ArcGISMap {return ArcGISMap(BasemapStyle.ArcGISTopographic).apply {initialViewpoint = Viewpoint(latitude = 34.0270,longitude = -118.8050,scale = 72000.0)}}fun checkPermissions(context: Context): Boolean {// Check permissions to see if both permissions are granted.// Coarse location permission.val permissionCheckCoarseLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED// Fine location permission.val permissionCheckFineLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTEDreturn permissionCheckCoarseLocation && permissionCheckFineLocation}fun showError(context: Context, message: String) {Toast.makeText(context, message, Toast.LENGTH_LONG).show()} -
Call the
LaunchedEffectcomposable. In theLaunchedEffectblock, execute the activity result launcher by callingactivityResultLauncher.launch()and passing the permissions you are requesting: fine and coarse location permissions.MainScreen.kt72 collapsed lines@file:OptIn(ExperimentalMaterial3Api::class)package com.example.app.screensimport android.Manifestimport android.content.Contextimport android.content.pm.PackageManagerimport android.widget.Toastimport androidx.activity.compose.rememberLauncherForActivityResultimport androidx.activity.result.contract.ActivityResultContractsimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.rememberimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.ui.Modifierimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.stringResourceimport androidx.core.content.ContextCompatimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.location.LocationDisplayAutoPanModeimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplayimport com.example.app.Rimport kotlinx.coroutines.launch@Composablefun MainScreen() {val context = LocalContext.currentval coroutineScope = rememberCoroutineScope()ArcGISEnvironment.applicationContext = context.applicationContext// Create and remember a location display with a recenter auto pan mode.val locationDisplay = rememberLocationDisplay().apply {setAutoPanMode(LocationDisplayAutoPanMode.Recenter)}if (checkPermissions(context)) {// Permissions are already granted.LaunchedEffect(Unit) {locationDisplay.dataSource.start()}} else {}val map = remember { createMap() }Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) {MapView(modifier = Modifier.fillMaxSize().padding(it),arcGISMap = map,locationDisplay = locationDisplay)}}@Composablefun RequestPermissions(context: Context, onPermissionsGranted: () -> Unit) {// Create an activity result launcher using permissions contract and handle the result.val activityResultLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->// Check if both fine & coarse location permissions are true.if (permissions.all { it.value }) {onPermissionsGranted()} else {showError(context, "Location permissions were denied")}}LaunchedEffect(Unit) {activityResultLauncher.launch(// Request both fine and coarse location permissions.arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_FINE_LOCATION))}}30 collapsed linesfun createMap(): ArcGISMap {return ArcGISMap(BasemapStyle.ArcGISTopographic).apply {initialViewpoint = Viewpoint(latitude = 34.0270,longitude = -118.8050,scale = 72000.0)}}fun checkPermissions(context: Context): Boolean {// Check permissions to see if both permissions are granted.// Coarse location permission.val permissionCheckCoarseLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED// Fine location permission.val permissionCheckFineLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTEDreturn permissionCheckCoarseLocation && permissionCheckFineLocation}fun showError(context: Context, message: String) {Toast.makeText(context, message, Toast.LENGTH_LONG).show()} -
Last, in the
MainScreencomposable, find the code that checks if coarse and fine location permissions were both granted.MainScreen.kt34 collapsed lines@file:OptIn(ExperimentalMaterial3Api::class)package com.example.app.screensimport android.Manifestimport android.content.Contextimport android.content.pm.PackageManagerimport android.widget.Toastimport androidx.activity.compose.rememberLauncherForActivityResultimport androidx.activity.result.contract.ActivityResultContractsimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.rememberimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.ui.Modifierimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.stringResourceimport androidx.core.content.ContextCompatimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.location.LocationDisplayAutoPanModeimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplayimport com.example.app.Rimport kotlinx.coroutines.launch@Composablefun MainScreen() {val context = LocalContext.currentval coroutineScope = rememberCoroutineScope()ArcGISEnvironment.applicationContext = context.applicationContext// Create and remember a location display with a recenter auto pan mode.val locationDisplay = rememberLocationDisplay().apply {setAutoPanMode(LocationDisplayAutoPanMode.Recenter)}if (checkPermissions(context)) {// Permissions are already granted.LaunchedEffect(Unit) {locationDisplay.dataSource.start()}} else {}val map = remember { createMap() }Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) {MapView(modifier = Modifier.fillMaxSize().padding(it),arcGISMap = map,locationDisplay = locationDisplay)}}57 collapsed lines@Composablefun RequestPermissions(context: Context, onPermissionsGranted: () -> Unit) {// Create an activity result launcher using permissions contract and handle the result.val activityResultLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->// Check if both fine & coarse location permissions are true.if (permissions.all { it.value }) {onPermissionsGranted()} else {showError(context, "Location permissions were denied")}}LaunchedEffect(Unit) {activityResultLauncher.launch(// Request both fine and coarse location permissions.arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_FINE_LOCATION))}}fun createMap(): ArcGISMap {return ArcGISMap(BasemapStyle.ArcGISTopographic).apply {initialViewpoint = Viewpoint(latitude = 34.0270,longitude = -118.8050,scale = 72000.0)}}fun checkPermissions(context: Context): Boolean {// Check permissions to see if both permissions are granted.// Coarse location permission.val permissionCheckCoarseLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED// Fine location permission.val permissionCheckFineLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTEDreturn permissionCheckCoarseLocation && permissionCheckFineLocation}fun showError(context: Context, message: String) {Toast.makeText(context, message, Toast.LENGTH_LONG).show()} -
In the
elseblock (permissions were not granted), callRequestPermissionsand pass a trailing lambda as theonPermissionGrantedparameter. The lambda will be called after the permissions are granted. In the lambda, start the data source of the location display.Since
LocationDataSource.start()is asuspendfunction, we call it from aCoroutineScope.launchblock.MainScreen.kt34 collapsed lines@file:OptIn(ExperimentalMaterial3Api::class)package com.example.app.screensimport android.Manifestimport android.content.Contextimport android.content.pm.PackageManagerimport android.widget.Toastimport androidx.activity.compose.rememberLauncherForActivityResultimport androidx.activity.result.contract.ActivityResultContractsimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.rememberimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.ui.Modifierimport androidx.compose.ui.platform.LocalContextimport androidx.compose.ui.res.stringResourceimport androidx.core.content.ContextCompatimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.location.LocationDisplayAutoPanModeimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplayimport com.example.app.Rimport kotlinx.coroutines.launch@Composablefun MainScreen() {val context = LocalContext.currentval coroutineScope = rememberCoroutineScope()ArcGISEnvironment.applicationContext = context.applicationContext// Create and remember a location display with a recenter auto pan mode.val locationDisplay = rememberLocationDisplay().apply {setAutoPanMode(LocationDisplayAutoPanMode.Recenter)}if (checkPermissions(context)) {// Permissions are already granted.LaunchedEffect(Unit) {locationDisplay.dataSource.start()}} else {RequestPermissions(context = context,onPermissionsGranted = {coroutineScope.launch {locationDisplay.dataSource.start()}})}val map = remember { createMap() }Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) {MapView(modifier = Modifier.fillMaxSize().padding(it),arcGISMap = map,locationDisplay = locationDisplay)}}57 collapsed lines@Composablefun RequestPermissions(context: Context, onPermissionsGranted: () -> Unit) {// Create an activity result launcher using permissions contract and handle the result.val activityResultLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->// Check if both fine & coarse location permissions are true.if (permissions.all { it.value }) {onPermissionsGranted()} else {showError(context, "Location permissions were denied")}}LaunchedEffect(Unit) {activityResultLauncher.launch(// Request both fine and coarse location permissions.arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_FINE_LOCATION))}}fun createMap(): ArcGISMap {return ArcGISMap(BasemapStyle.ArcGISTopographic).apply {initialViewpoint = Viewpoint(latitude = 34.0270,longitude = -118.8050,scale = 72000.0)}}fun checkPermissions(context: Context): Boolean {// Check permissions to see if both permissions are granted.// Coarse location permission.val permissionCheckCoarseLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED// Fine location permission.val permissionCheckFineLocation = ContextCompat.checkSelfPermission(context,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTEDreturn permissionCheckCoarseLocation && permissionCheckFineLocation}fun showError(context: Context, message: String) {Toast.makeText(context, message, Toast.LENGTH_LONG).show()}
Run your app
Click Run > Run > app to run the app.
In Android Studio, you have two choices for running your app: an actual Android device or the Android Emulator.
Android device
Connect your computer to your Android device, using USB or Wi-Fi. For more details, see How to connect your Android device.
Android Emulator
Create an AVD (Android Virtual Device) to run in the Android Emulator. For details, see Run apps on the Android Emulator.
Selecting a device
When you build and run an app in Android Studio, you must first select a device. From the Android Studio toolbar, you can access the drop-down list of your currently available devices, both virtual and physical.
.
If you cannot access the list on the toolbar, click Tools > Device Manager.
The app should display a permissions popup, in which you must tap Precise (for fine permissions) or Approximate (for coarse permissions). After you do so, your current location should display on the map. Different location symbols are used depending on the auto pan mode and whether a location is acquired. See LocationDisplayAutoPanMode for details.
By default, a round blue symbol is used to display the device’s location. The location data source tries to get the most accurate location available but depending upon signal strength, satellite positions, and other factors, the location reported could be an approximation. A semi-transparent circle around the location symbol indicates the range of accuracy. As the device moves and location updates are received, the location symbol will be repositioned on the map.
Alternatively, you can download the tutorial solution, as follows.
Option 2: Download the solution
-
Click the Download solution link in the right-hand side of this page.
-
Unzip the file to a location on your machine.
-
Run Android Studio.
-
Go to File > Open…. Navigate to the solution folder and click Open.
On Windows: If you are in the Welcome to Android Studio dialog, click Open and navigate to the solution folder. Then click Open.
Since the downloaded solution does not contain authentication credentials, you must first set up authentication to create credentials, and then add the developer credentials to the solution.
Set up authentication
To access the secure ArcGIS location services
You can implement API key authentication or user authentication in this tutorial. Compare the differences below:
API key authentication
- Users are not required to sign in.
- Requires creating an API key credential
API key credentials are an item that contains the parameters used to create and manage long-lived access tokens for API key authentication. They are a type of developer credential. with the correct privileges. - API keys
An API key is a long-lived access token created using API key credentials. They are valid for up to one year and are typically embedded directly into client applications. are long-lived access tokens. - Service usage is billed to the API key owner/developer.
- Simplest authentication method to implement.
- Recommended approach for new ArcGIS developers.
Learn more in API key authentication.
User authentication
- Users are required to sign in with an ArcGIS account
An ArcGIS account is an identity with a user type and set of privileges that can access specific ArcGIS products, tools, APIs, services, and resources. The main account types that can be used for development are an ArcGIS Location Platform account, ArcGIS Online account, and ArcGIS Enterprise account. ArcGIS Location Platform and ArcGIS Online accounts are also associated with a subscription. . - User accounts must have privilege
Privileges are a set of permissions assigned to ArcGIS accounts, developer credentials, and applications that grant access to secure resources and functionality in ArcGIS. to access the ArcGIS servicesA service, also known as an ArcGIS service, is software that supports an ArcGIS REST API and provides geospatial functionality or data. A service can be hosted by Esri or in ArcGIS Enterprise. used in application. - Requires creating OAuth credentials
OAuth credentials are an item that contains parameters required to implement user authentication or app authentication, including a .client_id,client_secret, and redirect URIs. They are a type of developer credential. - Application uses a redirect URL and client ID.
- Service usage is billed to the organization of the user signed into the application.
Learn more in User authentication.
To complete this tutorial, click on the tab in the switcher below for your authentication type of choice, either API key authentication or User authentication.
Create a new API key access token
-
Complete the Create an API key tutorial and create an API key with the following privilege(s)
Privileges are a set of permissions assigned to ArcGIS accounts, developer credentials, and applications that grant access to secure resources and functionality in ArcGIS. :- Privileges
- Location services > Basemaps
- Privileges
-
Copy and paste the API key access token into a safe location. It will be used in a later step.
Create new OAuth credentials to access the secure resources used in this tutorial.
-
Complete the Create OAuth credentials for user authentication tutorial to obtain a Client ID and Redirect URL.
A
Client IDuniquely identifies your app on the authenticating server. If the server cannot find an app with the provided Client ID, it will not proceed with authentication.The
Redirect URL(also referred to as a callback url) is used to identify a response from the authenticating server when the system returns control back to your app after an OAuth login. Since it does not necessarily represent a valid endpoint that a user could navigate to, the redirect URL can use a custom scheme, such asmy-app://auth. It is important to make sure the redirect URL used in your app’s code matches a redirect URL configured on the authenticating server. -
Copy and paste the Client ID and Redirect URL into a safe location. They will be used in a later step.
All users that access this application need account privileges
Set developer credentials in the solution
To allow your app users to access ArcGIS location services
-
In the Android view of Android Studio, open app > kotlin+java > com.example.app > MainActivity. Set the
AuthenticationModeto.API_KEY.MainActivity.kt14 collapsed linespackage com.example.appimport android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport com.arcgismaps.ApiKeyimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.httpcore.authentication.OAuthUserConfigurationimport com.arcgismaps.toolkit.authentication.AuthenticatorStateimport com.arcgismaps.toolkit.authentication.DialogAuthenticatorimport com.example.app.screens.MainScreenimport com.example.app.ui.theme.TutorialThemeclass MainActivity : ComponentActivity() {private enum class AuthenticationMode { API_KEY, USER_AUTH }private val authenticationMode = AuthenticationMode.API_KEY42 collapsed linesprivate 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)}}}}} -
Set the
apiKeyproperty with your API key access token.MainActivity.kt22 collapsed linespackage com.example.appimport android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport com.arcgismaps.ApiKeyimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.httpcore.authentication.OAuthUserConfigurationimport com.arcgismaps.toolkit.authentication.AuthenticatorStateimport com.arcgismaps.toolkit.authentication.DialogAuthenticatorimport com.example.app.screens.MainScreenimport com.example.app.ui.theme.TutorialThemeclass MainActivity : ComponentActivity() {private enum class AuthenticationMode { API_KEY, USER_AUTH }private val authenticationMode = AuthenticationMode.API_KEYprivate 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 linesAuthenticationMode.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.
-
In the Android view of Android Studio, open app > kotlin+java > com.example.app > MainActivity. Set the
AuthenticationModeto.USER_AUTH.MainActivity.ktclass MainActivity : ComponentActivity() {private enum class AuthenticationMode { API_KEY, USER_AUTH }private val authenticationMode = AuthenticationMode.USER_AUTH -
Set your
clientIDandredirectURLvalues. You must use the RedirectURL that you supplied for your app in theuser authenticationpart of the Set up authentication step.MainActivity.ktAuthenticationMode.USER_AUTH -> {authenticatorState.oAuthUserConfigurations = listOf(OAuthUserConfiguration(portalUrl = "https://www.arcgis.com",clientId = "YOUR_CLIENT_ID",redirectUrl = "YOUR_REDIRECT_URL")) -
Open app > manifests > AndroidManifest.xml.
-
Set the
android:schemeandandroid:hostusing the scheme and host from your RedirectURL.A redirectURL is composed of a scheme and a host component. The format for the redirect url is
scheme://host. For example, if the redirect url ismyscheme://myhostthen the scheme ismyschemeand the host ismyhost.AndroidManifest.xml41 collapsed lines<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><uses-permission android:name="android.permission.INTERNET" /><applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.Tutorial"tools:targetApi="31"><activityandroid:name=".MainActivity"android:exported="true"android:label="@string/app_name"android:theme="@style/Theme.Tutorial"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activityandroid:name="com.arcgismaps.toolkit.authentication.AuthenticationActivity"android:configChanges="keyboard|keyboardHidden|orientation|screenSize"android:exported="true"android:launchMode="singleTop" ><intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><dataandroid:scheme="your_redirect_url_scheme"android:host="your_redirect_url_host" />6 collapsed lines</intent-filter></activity></application></manifest>
Best Practice: The OAuth credentials are 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.
In Android Studio, you have two choices for running your app: an actual Android device or the Android Emulator.
Android device
Connect your computer to your Android device, using USB or Wi-Fi. For more details, see How to connect your Android device.
Android Emulator
Create an AVD (Android Virtual Device) to run in the Android Emulator. For details, see Run apps on the Android Emulator.
Selecting a device
When you build and run an app in Android Studio, you must first select a device. From the Android Studio toolbar, you can access the drop-down list of your currently available devices, both virtual and physical.
.
If you cannot access the list on the toolbar, click Tools > Device Manager.
The app should display a permissions popup, in which you must tap Precise (for fine permissions) or Approximate (for coarse permissions). After you do so, your current location should display on the map. Different location symbols are used depending on the auto pan mode and whether a location is acquired. See LocationDisplayAutoPanMode for details.
By default, a round blue symbol is used to display the device’s location. The location data source tries to get the most accurate location available but depending upon signal strength, satellite positions, and other factors, the location reported could be an approximation. A semi-transparent circle around the location symbol indicates the range of accuracy. As the device moves and location updates are received, the location symbol will be repositioned on the map.
What’s next?
Learn how to use additional API features, ArcGIS location services, and ArcGIS tools in these tutorials: