How to get current location (latitude, longitude) in Android using Kotlin

How to get current location (latitude, longitude) with GPS in Android

Handheld devices may track their geographic position, and they may interact with map services to graphically interfere with a user’s location needs.

The Android OS itself contains a location framework with classes in the package android.location. However, the official position of Google is to favor the Google Play Services Location API because it is more elaborate and simpler to use. We follow this suggestion and talk about the services API in the following paragraphs. Location is about finding out the geographical position of a device as a latitude-longitude pair.

You can find article for Java language here.

To make the Services Location API available to your app, add the following as dependencies in your app module’s build.gradle file

implementation
    'com.google.android.gms:play-services-location:11.8.0'
implementation
    'com.google.android.gms:play-services-maps:11.8.0'

The easiest way to get the device’s position is to get the last known location. To do so, in your app request permissions, add the following:

<uses-permission android:name=
    "android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name=
    "android.permission.ACCESS_FINE_LOCATION"/>

GPS resolution with fewer than ten yards needs FINE location permission, while the coarser network-based resolution with approximately 100 yards needs the COARSE location permission. Adding both to the manifest file gives us the most options, but depending on your needs, you could continue with just the coarse one.

Then, inside you component (for example, inside onCreate()), you construct a FusedLocationProviderClient as follows:

var fusedLocationClient: FusedLocationProviderClient? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
    fusedLocationClient = LocationServices.
        getFusedLocationProviderClient(this)
}    

Wherever needed in your app, you can use it to get the last known location.

if (checkPermission(
    Manifest.permission.ACCESS_COARSE_LOCATION,
    Manifest.permission.ACCESS_FINE_LOCATION)) {
    fusedLocationClient?.lastLocation?.
        addOnSuccessListener(this,
            {location : Location? ->
                // Got last known location. In some rare
                // situations this can be null.
                if(location == null) {
                    // TODO, handle it
                } else location.apply {
                    // Handle location object
                    Log.e("LOG", location.toString())
                }
            })
}    

Here, checkPermission() checks and possibly acquires the needed permissions as described here. This could be, for example, the following:

val PERMISSION_ID = 42
private fun checkPermission(vararg perm:String) : Boolean {
    val havePermissions = perm.toList().all {
        ContextCompat.checkSelfPermission(this,it) ==
        PackageManager.PERMISSION_GRANTED
    }
    if (!havePermissions) {
        if(perm.toList().any {
            ActivityCompat.
                shouldShowRequestPermissionRationale(this, it)}
        ) {
        val dialog = AlertDialog.Builder(this)
            .setTitle("Permission")
            .setMessage("Permission needed!")
            .setPositiveButton("OK", {id, v ->
                    ActivityCompat.requestPermissions(
                        this, perm, PERMISSION_ID)
            })
            .setNegativeButton("No", {id, v -> })
            .create()
            dialog.show()
        } else {
            ActivityCompat.requestPermissions(this, perm, PERMISSION_ID)
        }
        return false
    }
    return true
}

The function checkPermission(), if necessary, tries to acquire permission from a system activity. Whether the user grants permissions, upon return from this activity, your app may accordingly react to the result.

override fun onRequestPermissionsResult(requestCode: Int,
    permissions: Array<String>, grantResults: IntArray) {
        when (requestCode) {
            PERMISSION_ID -> {
            ...
        }
   }
}

If your app needs to track updates on changing locations, you follow a different approach. First, the permissions needed are the same as stated earlier for the last known location, so there’s no change there. The difference is in requesting periodic updates from the fused location provider. For that we need to define a location settings object. To create one, write the following:

val reqSetting = LocationRequest.create().apply {
    fastestInterval = 10000
    interval = 10000
    priority = LocationRequest.PRIORITY_HIGH_ACCURACY
    smallestDisplacement = 1.0f
}

The .apply construct lets us configure the object faster. For example, fastestInterval = 10000 internally gets translated to reqSetting.setFastestInterval(10000). The meanings of the individual settings are as follows:

  • fastestInterval. The fastest possible total update interval in milliseconds for the location provider.
  • interval. The requested interval in milliseconds. This setting is only approximate.
  • priority. The requested accuracy. This setting influences the battery usage. This value will be internally adapted according to available permissions. The following are possible values (constants from LocationRequest):

    • PRIORITY_NO_POWER. Fetches passive updates only if other requestors actively request updates
    • PRIORITY_LOW_POWER. Updates only on "city" levels
    • PRIORITY_BALANCED_POWER_ACCURACY. Updates only on "city street block" levels
    • PRIORITY_HIGH_ACCURACY. Uses the highest possible accuracy available
  • smallestDisplacement. This is the smallest displacement in meters necessary for an update message to be fired.

With that location request setting, we check whether the system is able to fulfill our request. This happens in the following code snippet:

val REQUEST_CHECK_STATE = 12300 // any suitable ID
val builder = LocationSettingsRequest.Builder()
    .addLocationRequest(reqSetting)

val client = LocationServices.getSettingsClient(this) 
client.checkLocationSettings(builder.build()).addOnCompleteListener { task ->
    try {
        val state: LocationSettingsStates = task.result.locationSettingsStates
        Log.e("LOG", "LocationSettings: \n" +
        " GPS present: ${state.isGpsPresent} \n" +
        " GPS usable: ${state.isGpsUsable} \n" +
        " Location present: " +
        "${state.isLocationPresent} \n" +
        " Location usable: " +
        "${state.isLocationUsable} \n" +
        " Network Location present: " +
        "${state.isNetworkLocationPresent} \n" +
        " Network Location usable: " +
        "${state.isNetworkLocationUsable} \n"
        )
    } catch (e: RuntimeExecutionException) {
        if (e.cause is ResolvableApiException)
            (e.cause as ResolvableApiException).startResolutionForResult(    
                this@MainActivity,
                REQUEST_CHECK_STATE)
    }
}

This asynchronously performs a check. If high accuracy is requested and the device’s setting won’t allow updates based on GPS data, the corresponding system settings dialog gets called. The latter happens somewhat awkwardly inside the exception catch. The result of the corresponding system intent call ends up in the following:

override fun onActivityResult(requestCode: Int, 
    resultCode: Int, data: Intent) {
    if (requestCode and 0xFFFF == REQUEST_CHECK_STATE) {
        Log.e("LOG", "Back from REQUEST_CHECK_STATE")
        ...
    }
}

With all set up correctly and enough permissions, we now can register for location updates via the following:

val locationUpdates = object : LocationCallback() {
    override fun onLocationResult(lr: LocationResult) {
        Log.e("LOG", lr.toString())
        Log.e("LOG", "Newest Location: " + lr.locations.last())
        // do something with the new location...
    }
}

fusedLocationClient?.requestLocationUpdates(reqSetting,
    locationUpdates,
    null /* Looper */)

To stop location updates, you move locationUpdates to a class field and react to stop requests via the following:

fun stopPeriodic(view:View) {
    fusedLocationClient?.
        removeLocationUpdates(locationUpdates)
}