Learn how to display the current device location on a map or scene.
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 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.
Steps
Open an Android Studio project
-
To start this tutorial, complete the Display a map tutorial. Or download and unzip the Display a map solution in a new folder.
-
Modify the old project for use in this new tutorial. Expand More info for instructions.
-
On your file system, delete the .idea folder, if present, at the top level of your project.
-
In the Android tool window, open app > res > values > strings.xml.
In the
<string name="app
element, change the text content to Display device location._name" > strings.xmlUse dark colors for code blocks <resources> <string name="app_name">Display device location</string> </resources>
-
In the Android tool window, open Gradle Scripts > settings.gradle.kts.
Change the value of
root
to "Display device location".Project.name settings.gradle.ktsUse dark colors for code blocks dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven { url = uri("https://esri.jfrog.io/artifactory/arcgis") } } } rootProject.name = "Display device location" include(":app")
-
The UI theme composable in Display a map tutorial was
Display
. Rename the theme composable throughout the tutorial by refactoringA Map Theme Display
.A Map Theme In the Android tool window, open app > kotlin+java > com.exmple.app > ui.theme > Theme.kt.
Right-click the function name
Display
and select Refactor -> Rename. Replace the name withA Map Theme Display
.Device Location Theme Theme.ktUse dark colors for code blocks Copy @Composable fun DisplayAMapTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ dynamicColor: Boolean = true, content: @Composable () -> Unit ) { val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } darkTheme -> DarkColorScheme else -> LightColorScheme }
-
Click File > Sync Project with Gradle files. Android Studio will recognize your changes and create a new .idea folder.
-
-
If you downloaded the solution, get an access token and set the API key in MainActivity.kt.
An API Key gives your app access to secure resources used in this tutorial.
-
Go to the Create an API key tutorial to obtain a new API key access token. Ensure that the following privilege is enabled: Location services > Basemaps > Basemap styles service. Copy the access token as it will be used in the next step.
-
In Android Studio: in the Android tool window, open app > java > com.example.app > MainActivity.
-
In the
set
function, find theApi Key() ApiKey.create()
call and paste your access token inside the double quotes, replacing YOUR_ACCESS_TOKEN.MainActivity.ktUse dark colors for code blocks Copy private fun setApiKey() { ArcGISEnvironment.apiKey = ApiKey.create("YOUR_ACCESS_TOKEN") }
-
Add import statements
-
In the Android tool window, open app > kotlin+java > com.example.app > MainScreen.kt. Replace the import statements with the imports needed for this tutorial.
MainScreen.ktUse dark colors for code blocks @file:OptIn(ExperimentalMaterial3Api::class) package com.example.app.screens import android.Manifest import android.content.Context import android.content.pm.PackageManager import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts 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.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.core.content.ContextCompat import com.arcgismaps.ArcGISEnvironment import com.arcgismaps.location.LocationDisplayAutoPanMode import com.arcgismaps.mapping.ArcGISMap import com.arcgismaps.mapping.BasemapStyle import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.toolkit.geoviewcompose.MapView import com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplay import com.example.app.R import kotlinx.coroutines.launch
Check location permissions
-
In the Android tool window, open app > manifests > AndroidManifest.xml.
In AndroidManifest.xml, add permissions for coarse and fine location.
AndroidManifest.xmlUse dark colors for code blocks <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" />
-
In MainScreen.kt, create a top-level function named
check
to check whether both coarse and fine location permissions are granted. This function takes the current localPermissions() Context
as a parameter, and returnstrue
if both permissions are granted andfalse
otherwise.MainScreen.ktUse dark colors for code blocks 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_GRANTED return permissionCheckCoarseLocation && permissionCheckFineLocation }
Show device location if permissions were granted
-
In the
Main
composable, you will add code that creates a location display and sets the auto pan mode.Screen Get the local
Context
and use it to set theArcGISEnvironment.applicationContext
property.MainScreen.ktUse dark colors for code blocks @Composable fun MainScreen() { val context = LocalContext.current val coroutineScope = rememberCoroutineScope() ArcGISEnvironment.applicationContext = context.applicationContext val map = remember { createMap() } Scaffold( topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) } ) { MapView( modifier = Modifier.fillMaxSize().padding(it), arcGISMap = map, ) } }
-
Create a
LocationDisplay
by calling the composable functionrememberLocationDisplay()
defined in the ArcGIS Maps SDK for Kotlin Toolkit. Then set aLocationDisplayAutoPanMode
that centers the map at the device location.MainScreen.ktUse dark colors for code blocks @Composable fun MainScreen() { val context = LocalContext.current val 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, ) } }
-
Set the
location
property ofDisplay MapView
usinglocation
, which we just created.Display MainScreen.ktUse dark colors for code blocks @Composable fun MainScreen() { val context = LocalContext.current val 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 ) } }
-
You will start location display, provided both permissions were granted.
To do so, call
check
, which returns true if both coarse and fine location permissions are granted. If the return value is true, call thePermissions() Launched
composable. In theEffect Launched
block, callEffect start()
on the data source for the location display.MainScreen.ktUse dark colors for code blocks @Composable fun MainScreen() { val context = LocalContext.current val 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 ) } }
Request location permissions if they were not granted
-
Create a composable function named
Request
that takes two parameters: one namedPermissions context
and the other namedon
, which is a function to be executed after requested permissions have been granted.Permissions Granted MainScreen.ktUse dark colors for code blocks @Composable fun RequestPermissions(context: Context, onPermissionsGranted: () -> Unit) { }
-
In
Request
, create anPermissions Activity
by calling the composable functionResult Launcher remember
.Launcher For Activity Result() Check that the value for each map entry in
permissions
is true. If both values are true, call theon
callback function. If values are not both true, show an error message.Permissions Granted You can show an error message in various ways. The code in this tutorial calls a custom function named
show
, which is defined like this:Error() MainScreen.ktUse dark colors for code blocks fun showError(context: Context, message: String) { Toast.makeText(context, message, Toast.LENGTH_LONG).show() }
MainScreen.ktUse dark colors for code blocks @Composable fun 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") } } }
-
Call the
Launched
composable. In theEffect Launched
block, execute the activity result launcher by callingEffect activity
and passing the permissions you are requesting: fine and coarse location permissions.Result Launcher.launch() MainScreen.ktUse dark colors for code blocks @Composable fun 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 ) ) } }
-
Last, in the
Main
composable, find the code that checks if coarse and fine location permissions were both granted.Screen MainScreen.ktUse dark colors for code blocks Copy @Composable fun MainScreen() { val context = LocalContext.current val 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 ) } }
-
In the
else
block (permissions were not granted), callRequest
and pass a trailing lambda as thePermissions on
parameter. The lambda will be called after the permissions are granted. In the lambda, start the data source of the location display.Permission Granted Since
Location
is aData Source.start() suspend
function, we call it from aCoroutine
block.Scope.launch MainScreen.ktUse dark colors for code blocks @Composable fun MainScreen() { val context = LocalContext.current val 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 ) } }
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) orApproximate
(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. SeeLocationDisplayAutoPanMode
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: