FusedLocationProviderClient with Kotlin coroutines
You have set up your coroutineContext
wrong. You should instead have
override val coroutineContext = Dispatchers.MAIN + job
If you ever need the IO
dispatcher, then require it explicitly:
withContext(Dispatchers.IO) { ... blocking IO code ... }
To suspend the coroutine, call suspendCancellableCoroutine
, otherwise you won't get any benefit from structured concurrency.
Another detail, don't write any code after it.resume
in the suspendCancellableCoroutine
block. If the dispatcher chooses to resume the coroutine immediately, within the resume
call, that code won't execute until all the code of the coroutine has run (or at least until the next suspension point).
override fun onLocationResult(result: LocationResult) {
fusedLocationProviderClient.removeLocationUpdates(this)
it.resume(result.lastLocation.run { latitude at longitude })
}
private val locationRequestGPS by lazy {
LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setNumUpdates(1)
.setExpirationDuration(1000)
}
private val locationRequestNETWORK by lazy {
LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_LOW_POWER)
.setNumUpdates(1)
.setExpirationDuration(1000)
}
suspend fun getLocation(context: Context, offsetMinutes: Int = 15): Location? = suspendCoroutine { task ->
val ctx = context.applicationContext
if (!ctx.isPermissionValid(Manifest.permission.ACCESS_COARSE_LOCATION)
&& !ctx.isPermissionValid(Manifest.permission.ACCESS_FINE_LOCATION)) {
task.resume(null)
} else {
val manager = ctx.getSystemService(Context.LOCATION_SERVICE) as LocationManager
if (!LocationManagerCompat.isLocationEnabled(manager)) {
task.resume(null)
} else {
val service = LocationServices.getFusedLocationProviderClient(ctx)
service.lastLocation
.addOnCompleteListener { locTask ->
if (locTask.result == null || System.currentTimeMillis() - locTask.result!!.time > offsetMinutes.minute) {
GlobalScope.launch(Dispatchers.Main) {
task.resume(locationRequest(manager, service))
}
} else {
task.resume(locTask.result)
}
}
}
}
}
suspend fun getLocationLast(context: Context): Location? = suspendCoroutine { task ->
val ctx = context.applicationContext
if (!ctx.isPermissionValid(Manifest.permission.ACCESS_COARSE_LOCATION)
&& !ctx.isPermissionValid(Manifest.permission.ACCESS_FINE_LOCATION)) {
task.resume(null)
} else {
if (!LocationManagerCompat.isLocationEnabled(ctx.getSystemService(Context.LOCATION_SERVICE) as LocationManager)) {
task.resume(null)
} else {
LocationServices.getFusedLocationProviderClient(ctx)
.lastLocation
.addOnCompleteListener { locTask ->
task.resume(locTask.result)
}
}
}
}
suspend fun locationRequest(locationManager: LocationManager, service: FusedLocationProviderClient): Location? = suspendCoroutine { task ->
val callback = object : LocationCallback() {
override fun onLocationResult(p0: LocationResult?) {
service.removeLocationUpdates(this)
task.resume(p0?.lastLocation)
}
}
when {
locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) -> {
service.requestLocationUpdates(locationRequestGPS, callback, Looper.getMainLooper())
}
locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) -> {
service.requestLocationUpdates(locationRequestNETWORK, callback, Looper.getMainLooper())
}
else -> {
task.resume(null)
}
}
}