Skip to content

Instantly share code, notes, and snippets.

@graffiti75
Last active April 21, 2023 04:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save graffiti75/1cbbc7194656fad46c4d27f2aed22d73 to your computer and use it in GitHub Desktop.
Save graffiti75/1cbbc7194656fad46c4d27f2aed22d73 to your computer and use it in GitHub Desktop.
How to track the user location in Android, using Kotlin, and dealing with ACCESS_FINE_LOCATION and ACCESS_BACKGROUND_LOCATION permissions
Changing name of this Gist.

Be sure to include the latest location services in the app level gradle (18.0.0 at the time of writing):

implementation "com.google.android.gms:play-services-location:18.0.0"

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".SandboxActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
fun Context.toast(textId: Int) {
Toast.makeText(this, textId, Toast.LENGTH_SHORT).show()
}
fun Context.toast(text: String) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
}
fun Context.permissionGranted(permission: String): Boolean {
return ContextCompat.checkSelfPermission(
this, permission
) == PackageManager.PERMISSION_GRANTED
}
fun FragmentActivity.requestTrackingPermissions(array: Array<String>, requestCode: Int) {
ActivityCompat.requestPermissions(
this,
array,
requestCode
)
}
fun FragmentActivity.showRequestPermissionRationale(permission: String): Boolean =
ActivityCompat.shouldShowRequestPermissionRationale(
this,
permission
)
import android.Manifest
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Looper
import android.provider.Settings
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.Granularity
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority
import com.udacity.project4.utils.permissionGranted
import com.udacity.project4.utils.requestTrackingPermissions
import com.udacity.project4.utils.showRequestPermissionRationale
import com.udacity.project4.utils.toast
/**
* Sources:
* https://stackoverflow.com/questions/60384554/access-background-location-not-working-on-lower-than-q-29-android-versions
* https://stackoverflow.com/questions/40142331/how-to-request-location-permission-at-runtime/40142454#40142454
* https://github.com/zoontek/react-native-permissions/issues/620
*/
class TrackingActivity : AppCompatActivity() {
companion object {
private const val MY_PERMISSIONS_REQUEST_LOCATION = 99
private const val MY_PERMISSIONS_REQUEST_BACKGROUND_LOCATION = 66
private const val UPDATE_INTERVAL = (10 * 1000).toLong() // 10 secs
private const val FASTEST_INTERVAL: Long = 2000 // 2 secs
}
private var fusedLocationProvider: FusedLocationProviderClient? = null
private val locationRequest: LocationRequest = LocationRequest.Builder(
Priority.PRIORITY_BALANCED_POWER_ACCURACY, UPDATE_INTERVAL
).apply {
setMinUpdateDistanceMeters(5F)
setGranularity(Granularity.GRANULARITY_PERMISSION_LEVEL)
setWaitForAccurateLocation(true)
setMinUpdateIntervalMillis(FASTEST_INTERVAL)
}.build()
/*
private val locationRequest: LocationRequest = LocationRequest.create().apply {
interval = 30
fastestInterval = 10
priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY
maxWaitTime = 60
}
*/
private var locationCallback: LocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
val locationList = locationResult.locations
if (locationList.isNotEmpty()) {
// The last location in the list is the newest.
val location = locationList.last()
toast("Got Location: $location")
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tracking)
fusedLocationProvider = LocationServices.getFusedLocationProviderClient(this)
checkLocationPermission()
}
@SuppressLint("MissingPermission")
override fun onResume() {
super.onResume()
if (permissionGranted(Manifest.permission.ACCESS_FINE_LOCATION)) {
fusedLocationProvider?.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
}
}
override fun onPause() {
super.onPause()
if (permissionGranted(Manifest.permission.ACCESS_FINE_LOCATION)) {
fusedLocationProvider?.removeLocationUpdates(locationCallback)
}
}
private fun checkLocationPermission() {
if (!permissionGranted(Manifest.permission.ACCESS_FINE_LOCATION)) {
// Should we show an explanation?
if (showRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
AlertDialog.Builder(this)
.setTitle("Location Permission Needed")
.setMessage("This app needs the Location permission, please accept to use location functionality")
.setPositiveButton(
"OK"
) { _, _ ->
// Prompt the user once explanation has been shown.
requestLocationPermission()
}
.create()
.show()
} else {
// No explanation needed, we can request the permission.
requestLocationPermission()
}
} else {
checkBackgroundLocation()
}
}
private fun checkBackgroundLocation() {
if (!permissionGranted(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
requestBackgroundLocationPermission()
}
}
private fun requestLocationPermission() {
requestTrackingPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
MY_PERMISSIONS_REQUEST_LOCATION
)
}
private fun requestBackgroundLocationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
requestTrackingPermissions(
arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
MY_PERMISSIONS_REQUEST_BACKGROUND_LOCATION
)
} else {
requestTrackingPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
MY_PERMISSIONS_REQUEST_LOCATION
)
}
}
@SuppressLint("MissingSuperCall", "MissingPermission")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
when (requestCode) {
MY_PERMISSIONS_REQUEST_LOCATION -> {
// If request is cancelled, the result arrays are empty.
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission was granted, yay! Do the location-related task you need to do.
if (permissionGranted(Manifest.permission.ACCESS_FINE_LOCATION)) {
fusedLocationProvider?.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
// Now check background location
checkBackgroundLocation()
}
} else {
// Permission denied, boo! Disable the functionality that depends on this permission.
toast("Permission denied")
// Check if we are in a state where the user has denied the permission and
// selected Don't ask again.
if (!showRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
startActivity(
Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", this.packageName, null),
),
)
}
}
return
}
MY_PERMISSIONS_REQUEST_BACKGROUND_LOCATION -> {
// If request is cancelled, the result arrays are empty.
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission was granted, yay! Do the location-related task you need to do.
if (permissionGranted(Manifest.permission.ACCESS_FINE_LOCATION)) {
fusedLocationProvider?.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
toast("Granted Background Location Permission")
}
} else {
// Permission denied, boo! Disable the functionality that depends on this permission.
toast("Permission denied")
}
return
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment