Display device location

Learn how to display the current device location on a map or scene.

display device location

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

The following are required for this tutorial:

  1. An ArcGIS account to access API keys. If you don't have an account, sign up for free.
  2. A development and deployment environment that meets the system requirements.
  3. An IDE for Android development in Kotlin.

Steps

Open an Android Studio project

  1. To start this tutorial, complete the Display a map tutorial. Or download and unzip the Display a map solution in a new folder.

  2. Modify the old project for use in this new tutorial. Expand More info for instructions.

  3. Set your API key in MainActivity.kt.

Add import statements

  1. 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.kt
    Use dark colors for code blocks
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    @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

  1. In the Android tool window, open app > manifests > AndroidManifest.xml.

    In AndroidManifest.xml, add permissions for coarse and fine location.

    AndroidManifest.xml
    Expand
    Use dark colors for code blocks
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    <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" />
    
    Expand
  2. 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 local Context as a parameter, and returns true if both permissions are granted and false otherwise.

    MainScreen.kt
    Expand
    Use dark colors for code blocks
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    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
    }
    
    Expand

Show device location if permissions were granted

  1. In the MainScreen composable, you will add code that creates a location display and sets the auto pan mode.

    Get the local Context and use it to set the ArcGISEnvironment.applicationContext property.

    MainScreen.kt
    Expand
    Use dark colors for code blocks
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    @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,
    
            )
        }
    
    }
    
    Expand
  2. Create a LocationDisplay by calling the composable function rememberLocationDisplay() defined in the ArcGIS Maps SDK for Kotlin Toolkit. Then set a LocationDisplayAutoPanMode that centers the map at the device location.

    MainScreen.kt
    Expand
    Use dark colors for code blocks
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    @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,
    
            )
        }
    
    }
    
    Expand
  3. Set the locationDisplay property of MapView using locationDisplay, which we just created.

    MainScreen.kt
    Expand
    Use dark colors for code blocks
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    @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
    
            )
        }
    
    }
    
    Expand
  4. 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 the LaunchedEffect composable. In the LaunchedEffect block, call start() on the data source for the location display.

    MainScreen.kt
    Expand
    Use dark colors for code blocks
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    @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
    
            )
        }
    
    }
    
    Expand

Request location permissions if they were not granted

  1. Create a composable function named RequestPermissions that takes two parameters: one named context and the other named onPermissionsGranted, which is a function to be executed after requested permissions have been granted.

    MainScreen.kt
    Expand
    Use dark colors for code blocks
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    @Composable
    fun RequestPermissions(context: Context, onPermissionsGranted: () -> Unit) {
    
    }
    
    Expand
  2. In RequestPermissions, create an ActivityResultLauncher by calling the composable function rememberLauncherForActivityResult().

    Check that the value for each map entry in permissions is true. If both values are true, call the onPermissionsGranted callback function. If values are not both true, show an error message.

    MainScreen.kt
    Expand
    Use dark colors for code blocks
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    @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")
                }
            }
    
    }
    
    Expand
  3. Call the LaunchedEffect composable. In the LaunchedEffect block, execute the activity result launcher by calling activityResultLauncher.launch() and passing the permissions you are requesting: fine and coarse location permissions.

    MainScreen.kt
    Expand
    Use dark colors for code blocks
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    @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
                )
            )
        }
    
    }
    
    Expand
  4. Last, in the MainScreen composable, find the code that checks if coarse and fine location permissions were both granted.

    MainScreen.kt
    Expand
    Use dark colors for code blocksCopy
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    @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
    
            )
        }
    
    }
    
    Expand
  5. In the else block (permissions were not granted), call RequestPermissions and pass a trailing lambda as the onPermissionGranted parameter. The lambda will be called after the permissions are granted. In the lambda, start the data source of the location display.

    MainScreen.kt
    Expand
    Use dark colors for code blocks
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    @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
    
            )
        }
    
    }
    
    Expand

Run your app

  1. Click Run > Run > app to run the app.

    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.

What's next?

Learn how to use additional API features, ArcGIS location services, and ArcGIS tools in these tutorials:

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.