androidkotlinfusedlocationproviderapikotlin-coroutines

FusedLocationProviderClient with Kotlin coroutines


I am trying to request a new location with FusedLocationProviderClient and Kotlin Coroutines. This is my current setup:

class LocationProviderImpl(context: Context) : LocationProvider, CoroutineScope {

    private val TAG = this::class.java.simpleName

    private val job = Job()
    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.IO

    private val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
    private val locationRequest = LocationRequest().apply {
        numUpdates = 1
        priority = LocationRequest.PRIORITY_HIGH_ACCURACY
    }

    override suspend fun getLocation(): LatLng = suspendCoroutine {
        val locationCallback = object : LocationCallback() {
            override fun onLocationResult(result: LocationResult) {
                result.lastLocation.run {
                    val latLng = latitude at longitude
                    it.resume(latLng)
                }

                fusedLocationProviderClient.removeLocationUpdates(this)
            }
        }

        try {
            fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper())
        } catch (e: SecurityException) {
            throw NoLocationPermissionException()
        }
    }
}

But when trying to request a new location, I get the following exception:

java.lang.IllegalStateException: Can't create handler inside thread that has not called Looper.prepare()

However, if I would call Looper.prepare() (and Looper.quit() eventually) wouldn't it mean that I can call the function only once?

Any help is appreciated.


Solution

  • The problem is in the way you set up your coroutineContext. Use this instead:

    override val coroutineContext = Dispatchers.Main + job
    

    If you ever need the IO dispatcher, you can 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 to longitude })
    }