Setup Google Play Services
Edit build.gradle
(Module)
dependencies {
// https://developers.google.com/android/guides/releases
implementation "com.google.android.gms:play-services-location:15.0.1"
}
NOTE: Set Up Google Play Services.
Specify app permission
Edit AndroidManifest.xml
.
<manifest ...>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
</manifest>
NOTE: ACCESS_COARSE_LOCATION only allow accuracy up to a city block. I need ACCESS_FINE_LOCATION, and I suspect it works better when both are specified.
Get last known location
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)fusedLocationClient.lastLocation .addOnSuccessListener { location: Location? -> // Got last known location. In some rare situations this can be null. if (location != null) { // do something, save it perhaps? } }
Request location update
Get last known location is nice, but I also want to be sure we have the latest and most accurate location.
// activity private var locationCallback: LocationCallback? = null
val locationRequest = LocationRequest().apply { interval = 10000 fastestInterval = 5000 priority = LocationRequest.PRIORITY_HIGH_ACCURACY}val builder = LocationSettingsRequest.Builder() .addLocationRequest(locationRequest)val client = LocationServices.getSettingsClient(activity)val task = client.checkLocationSettings(builder.build())var updateCount = 0task.addOnSuccessListener { locationCallback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult?) { updateCount++ locationResult ?: return for (location in locationResult.locations){ if (location != null) { // do something Log.d(TAG, "gps.accuracy=${location.accuracy}, speed=${location.speed}, time=${location.time}") if (isLocationAccurate(location, true)) { stopLocationUpdates() } } } // if location is still not accurate after 10 updates, stop the update if (updateCount > 10) { stopLocationUpdates() } } } fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null /* Looper */)
fun isLocationAccurate(location: Location, isStrict: Boolean): Boolean { val now = LocalDateTime.now(ZoneOffset.UTC) val locationTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(location.time), ZoneOffset.UTC) val seconds = Duration.between(locationTime, now).seconds if (seconds > 60) { return false } if (location.hasAccuracy()) { val accuracy = if (isStrict) 20 else 50 if (location.accuracy > accuracy) { return false } } /* if (location.hasSpeed()) { // 10 km/p = 2.77 m/s if (location.speed > 3) { } } */ return true}
private fun stopLocationUpdates() { if (locationCallback != null) { fusedLocationClient.removeLocationUpdates(locationCallback) }}
NOTE: You might want to pause location update on onPause
and resume it onResume
.
Permission Request
You also need to handle Permission Request
companion object { private const val PERMISSION_LOCATION_PHOTO = 1}override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == PERMISSION_LOCATION_PHOTO) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { startLocationUpdates() } else { // fragmentContainer val snackbar = Snackbar.make(container, getString(R.string.permission_denied_location), Snackbar.LENGTH_INDEFINITE) snackbar.setAction("Settings") { val intent = Intent() intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS val uri = Uri.fromParts("package", packageName, null) intent.data = uri startActivity(intent) } snackbar.show() } }}
if (PermissionHelper.requestLocationPermission(activity, fragment, PERMISSION_LOCATION_PHOTO, getString(R.string.permission_message_location_photo))) { startLocationUpdates()}
object PermissionHelper { fun requestLocationPermission(activity: Activity, fragment: Fragment, requestCode: Int, message: String): Boolean { return requestPermission(activity, fragment, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION), requestCode, message) } fun checkLocationPermission(activity: Context): Boolean { return checkPermission(activity, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)) } fun checkPermission(activity: Context?, permissions: Array<String>): Boolean { for (permission in permissions) { if (ActivityCompat.checkSelfPermission(activity!!, permission) != PackageManager.PERMISSION_GRANTED) return false } return true } fun requestPermission(activity: Activity, fragment: Fragment?, permissions: Array<String>, requestCode: Int, message: String): Boolean { var context: Context? = activity if (fragment != null) { context = fragment.context } val isAllow = checkPermission(context, permissions) if (!isAllow) { if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permissions[0])) { val dialog = AlertDialog.Builder(context) .setMessage(message) .setPositiveButton(android.R.string.ok) { dialog, which -> if (fragment == null) { ActivityCompat.requestPermissions(activity, permissions, requestCode) } else { fragment.requestPermissions(permissions, requestCode) } } .create() dialog.show() } else { if (fragment == null) { ActivityCompat.requestPermissions(activity, permissions, requestCode) } else { fragment.requestPermissions(permissions, requestCode) } } return false } else { return true } }}
References: