W1 D2: Fetching & Saving User's Current Location 👤📍
Tags: 100daysofcode, android, kotlin, maps
Published: 2021-09-15 14:42:38

So in the last article, we added a Google Map to an Android app. This is a continuation of that article. Now let's fetch the user's current location & save the map state along with the camera position.

### Week 1 🗺️
This week we'll be doing all things maps in Android. Being able to pull off stunts like adding markers to the map, style the map, add geofences, get the user's last known location, etc. are indispensable to your knowledge in this area (pun intended). These things are extremely useful & are super important if you want to know how to add functionalities like sharing your location, sending notifications according to the user's location, etc.

You can check out the other posts from this week 📍 👍.

1.  [Adding a Google Map](https://dev.to/apigeoneer/w1-d1-adding-a-google-map-1gi8) 
2. [Extracting & Saving the User's Current Location](https://dev.to/apigeoneer/w1-d2-fetching-saving-user-s-current-location-4p57)
3. Incremental Location Permissions
4. Incremental Location Permissions with EasyPermissions Library
5. Sharing Location Data using Firebase
6. Live Location Tracking/Sharing
7. Adding a Marker to the Map on Long-click & Storing the Marked Location/POI

### Day 2: Fetching & Saving User's Current Location

This stunt is divided into 3 parts -
1. Preparing to get the user's device location (getting the permissions, setting up the UI, etc.)
2. Getting the user's current device location
3. Saving the map state & camera position

Okay so shut all the distractions, get hydrated, we'll now get these parts done.
![lets-go-dwight-schrute.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1631374088693/Ib7CPSG8c.gif)

--------------------------------------------------------------------------------------------------------

#### Prepare to get the user's device location

1. Enable the ***Places SDK*** in the Google Cloud project you just created.
    - Google Cloud console -> select your project -> APIs -> Places SDK -> Enable
2. Add Places dependency (app: build.gradle)
    - implementation 'com.google.android.libraries.places:places:2.4.0'
3. Instantiate the Places API client
    - Create a `PlacesClient` object & a `FusedLocationProviderClient` object.

        ```kotlin
            // MapsActivity.kt

            private lateinit var placesClient: PlacesClient
            private lateinit var fusedLocationProviderClient: FusedLocationProviderClient

            override fun onCreate(savedInstanceState: Bundle?) {
            		...

            		// Create PlacesClient
                Places.initialize(applicationContext, getString(R.string.maps_api_key))
                placesClient = Places.createClient(this)

                // Create FusedLocationProviderClient
                fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
            }
        ```
4. Request location permission
    - Add fine location permission in your app for FINE_LOCATION

        ```kotlin
            // AndroidManifest.xml

            <!--
                 The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
                 Google Maps Android API v2, but you must specify either coarse or fine
                 location permissions for the "MyLocation" functionality.
            -->
            <uses-permissionandroid:name="android.permission.ACCESS_FINE_LOCATION" />
        ```
5. Request run-time permission for FINE_LOCATION
    ```kotlin
                // MapsActivity.kt

            		private var locationPermissionGranted = false

            		private fun getLocationPermission() {
                    /*
                     * Request location permission, so that we can get the location of the
                     * device. The result of the permission request is handled by a callback,
                     * onRequestPermissionsResult.
                     */
                    if (ContextCompat.checkSelfPermission(this.applicationContext, 
                            android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
                        locationPermissionGranted = true
                    } else {
                        ActivityCompat.requestPermissions(this, 
                            arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), 
                            PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION
                        )
                    }
                }

            		companion object {
                        private const val PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 111
                    }
    ```
6. Handle the result of the request permission
    ```kotlin
            		// MapsActivity.kt		

            		override fun onRequestPermissionsResult(requestCode: Int,
                                                        permissions: Array<String>,
                                                        grantResults: IntArray) {
                    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
                    locationPermissionGranted = false
                    when (requestCode) {
                        PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION -> {

                            // If request is cancelled, the result arrays are empty. But if the user has now
                            // granted the permission (after we requested), the result array contains that permission.
                            if (grantResults.isNotEmpty() &&
                                grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                                locationPermissionGranted = true
                            }
                        }
                    }
                }
    ```
7. Update the UI
    - Update the UI if the user has granted permission
        ```kotlin
            // mAPSaCTIVITY.KT
            private var lastKnownLocation = null
            
            override fun onMapReady(googleMap: GoogleMap) {
        		...
                // Turn on the My Location layer and the related control on the map.
                updateLocationUI()
            }

            override fun onRequestPermissionsResult(...) {
        		...
        		updateLocationUI()
            }		

            private fun updateLocationUI() {
                /**
                 * If the user has granted the FINE_LOCATION permission, update the UI else don't
                 */
                if (mMap == null) {
                    return
                }
                try {
                    if (locationPermissionGranted) {
                        mMap.isMyLocationEnabled = true
                        mMap.uiSettings.isMyLocationButtonEnabled = true
                    } else {
                        mMap.isMyLocationEnabled = false
                        mMap.uiSettings.isMyLocationButtonEnabled = false
                        lastKnownLocation = null
                        getLocationPermission()
                    }
                } catch (e: SecurityException) {
                    Log.e("Exception: %s", e.message, e)
                }
            }
        ```
#### Get the location of the Android device and position the map
  - get the device location
      ```kotlin
            // MapsActivity.kt

        		private var lastKnownLocation: Location? = null
                private val defaultLocation = LatLng(-34.0, 151.0)      // Sydney		

        		override fun onMapReady(googleMap: GoogleMap) {
        				...
                    // Get the current location of the device and set the position of the map.
                    getDeviceLocation()
                }

        		private fun getDeviceLocation() {
                /*
                 * Get the best and most recent location of the device, which may be null in rare
                 * cases when a location is not available.
                 */
                try {
                    if (locationPermissionGranted) {
                        val locationResult = fusedLocationProviderClient.lastLocation
                        locationResult.addOnCompleteListener(this) { task ->
                            if (task.isSuccessful) {
                                // Set the map's camera position to the current location of the device.
                                lastKnownLocation = task.result
                                if (lastKnownLocation != null) {
                                    mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                        LatLng(lastKnownLocation!!.latitude,
                                            lastKnownLocation!!.longitude), DEFAULT_ZOOM))
                                }
                            } else {
                                Log.d(TAG, "Current location is null. Using defaults.")
                                Log.e(TAG, "Exception: %s", task.exception)
                                mMap.moveCamera(CameraUpdateFactory
                                    .newLatLngZoom(defaultLocation, DEFAULT_ZOOM))
                                mMap.uiSettings.isMyLocationButtonEnabled = false
                            }
                        }
                    }
                } catch (e: SecurityException) {
                    Log.e("Exception: %s", e.message, e)
                }
            }
       
            companion object {
                 private const val DEFAULT_ZOOM = 50F
             }

        ```

    Now run the app. After allowing the app to get your location, you'll see that the map zooms in to your current location, displaying a blue circle at your precise latitude & longitude. Great! 🍻
![55.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1631373417221/GE0fvfGUg.png)
![44.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1631373423561/r2MR9A-Ry.png)

#### Save the map's state
  - Save map's state & camera position
      - Set up the values that you want to save
      - Save the state
      - Retrieve the saved state on onCreate()

        ```kotlin
        // MapsActivity.kt		

        private var cameraPosition: CameraPosition? = CameraPosition(defaultLocation, DEFAULT_ZOOM, 0.0F, 0.0F)

            override fun onCreate(savedInstanceState: Bundle?) {
                ...
                // Retrieve the user's saved map state & camera position
                if (savedInstanceState != null) {
                    lastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION)
                    cameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION)
                }
            }

            override fun onSaveInstanceState(outState: Bundle) {
                /**
                 * Save the map's state & camera position when the activity is destroyed
                 * (like in a configuration change)
                 */
                mMap.let { map ->
                    outState.putParcelable(KEY_CAMERA_POSITION, map.cameraPosition)
                    outState.putParcelable(KEY_LOCATION, lastKnownLocation)
                }
                super.onSaveInstanceState(outState)
            }

            companion object {
                ...
                private const val KEY_CAMERA_POSITION = "camera_position"
                private const val KEY_LOCATION = "location"
            }
        ```

This is it. Now run the app & rotate it. You'll see the marker persists. You've now successfully fetched & saved your app's user's current location! 🎊