Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LocationAvailability No Location Available

Google's FusedLocationProviderApi for Android was recently deprecated within the past few months, with FusedLocationProviderClient being its successor so I recently updated the location APIs used in my client's app to use the new ones.

Every time onLocationAvailability is fired in LocationCallback I notify the user when locationAvailability.isLocationAvailable() returns false, but it appears that this condition occurs more often than I expected on some devices. I run these location updates inside a foreground Service and it is crucial that these location updates remain consistent. Is there a way to determine the cause of this failure so

  1. We don't indicate any false positives to the end-user
  2. We can try to fix the issue or at least report to the end-user what they should do?

It appears to me that either the deprecated APIs provide more insight into these issues since it was used in conjunction with GoogleApiClient or perhaps I'm missing some smaller details.

like image 549
lorenzo Avatar asked Nov 27 '17 22:11

lorenzo


4 Answers

I went through the same issue. And after three days of trying things out, I got to work on it out.

I also had to collect a location in a foreground state like you, and if the foreground service was destroyed, I had to unregister.

The first mistake I made was not to guarantee that removeLocationUpdates would be run on the same thread as the requestLocationUpdates. Actually, it doesn't have to be the same thread, but after a requestLocationUpdates, you must call removeLocationUpdates to make the next requestLocationUpdates valid. To ensure this, it is much easier to work on the same thread.

For example:

  private fun FusedLocationProviderClient.requestLocation(
      request: LocationRequest
  ): Single<LocationResult> {
    return Single.create<LocationResult> { emitter ->
      requestLocationUpdates(request, object : LocationCallback() {
        override fun onLocationResult(result: LocationResult?) {
          removeLocationUpdates(object : LocationCallback() {})
              .addOnCompleteListener {
                if (emitter.isDisposed) {
                  info("onLocationResult called after disposing.")
                  return@addOnCompleteListener
                }

                if (result != null && result.locations.isNotEmpty()) {
                  onSuccess(result)
                } else {
                  onError(RuntimeException("Invalid location result"))
                }
              }
        }

        private fun onError(error: Exception) {
          if (!emitter.isDisposed) {
            emitter.onError(error)
          }
        }

        private fun onSuccess(item: LocationResult) {
          if (!emitter.isDisposed) {
            emitter.onSuccess(item)
          }
        }

      }, Looper.getMainLooper())
    }
  }

As the code suggests, I have attracted Single's emitter to the addOnCompleteListener in removeLocationUpdates to ensure the call of removeLocationUpdates behind the requestLocationUpdates. Without RxJava, of course, it would be easier to implement.

The second mistake I made was the wrong interval setting in LocationRequest. According to the doc:

This method sets the rate in milliseconds at which your app prefers to receive location updates. Note that the location updates may be somewhat faster or slower than this rate to optimize for battery usage, or there may be no updates at all (if the device has no connectivity, for example).

The explanation is unkind but ultimately, if you call requestLocationUpdates once, you must have a Location update event triggered by interval before the next requestLocationUpdates. Finding this bug was the hardest.

The third mistake I made was to set the wrong priority in LocationRequest. In API 10 and below, it was not PRIORITY_BALANCED_POWER_ACCURACY but it was resolved by using PRIORITY_HIGH_ACCURACY. In this case, I have only tested on the emulator, so the actual device may have different results. I guess PRIORITY_BALANCED_POWER_ACCURACY doesn't seem to work properly because the emulator doesn't provide Bluetooth hardware.

So my LocationRequest looks like:

LocationRequest.apply {
  priority = PRIORITY_HIGH_ACCURACY
  interval = 10000L
}

I hope the three mistakes and solutions I made is helpful to you!

like image 103
poqw Avatar answered Nov 15 '22 18:11

poqw


I filter false positives of locationAvailability.isLocationAvailable() by calling this piece of code, which checks if location service is enabled.

fun isLocationEnabled(): Boolean {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
            lm.isLocationEnabled
        } else {
            val mode = Settings.Secure.getInt(
                context.contentResolver, Settings.Secure.LOCATION_MODE,
                Settings.Secure.LOCATION_MODE_OFF
            )
            mode != Settings.Secure.LOCATION_MODE_OFF
        }
    }
override fun onLocationAvailability(p0: LocationAvailability?) {
            if (isLocationEnabled().not()) {
                locationUpdateSubject.failed(LocationAvailabilityError)
            }
        }
like image 25
Roman Droppa Avatar answered Nov 15 '22 17:11

Roman Droppa


As official documentation says:

onLocationAvailability Called when there is a change in the availability of location data. When isLocationAvailable() returns false you can assume that location will not be returned in onLocationResult(LocationResult) until something changes in the device's settings or environment. Even when isLocationAvailable() returns true the onLocationResult(LocationResult) may not always be called regularly, however the device location is known and both the most recently delivered location and getLastLocation(GoogleApiClient) will be reasonably up to date given the hints specified by the active LocationRequests.

So this method does not provide information about reason.

  1. We don't indicate any false positives to the end-user

    • Currently I just ignore result of this method because it returns false too often, and then again true, and so on.
  2. We can try to fix the issue or at least report to the end-user what they should do?

    • Check if Location Services are enabled (using LocationManager.isProviderEnabled())

    • Check if you have permissions, request them if needed (docs)

like image 41
B-GangsteR Avatar answered Nov 15 '22 18:11

B-GangsteR


as Google docs says:

When isLocationAvailable() returns false you can assume that location will not be returned in onLocationResult(LocationResult) until something changes in the device's settings or environment.

So you can assume that location may be not available not only because of disabled location settings, but because of signal strength, or maybe satellites are not visible or something else, it just indicates that you will not receive location updates until something changes. You can show user notification about it with something like "We can't get your location, try enable location settings"

like image 34
Wackaloon Avatar answered Nov 15 '22 19:11

Wackaloon