7

Due to the way our app works, I need to synchronously get the users current location. Our current implementation uses the com.google.android.gms.location.LocationListener to receive location updates. Problem is, I need to update the location server-side before attempting my first call, otherwise the user gets faulty data.

LocationServices.FusedLocationApi.getLastLocation() isn't suited for this, because a) it always seems to return null (for whatever reason) and b) we already save the users last known location server-side, so retrieving the last known location and sending it to the servers is redundant.

pseudocode

//this is the first call I need to make
webservice.appStart(AppStartBody body);
//then I want to retrieve the current location and send it to the server
webservice.setGeoLocation(getCurrentLocation());
//finally, I retrieve the items on the server based on the location
webservice.getListItems();

Waiting for the first location update event is a possibility that I want to avoid, because I don't know, how soon it will fire and I might have to keep my users in a loading screen for ages and in turn lose them, because nobody likes waiting.

  • If getting the device location takes e.g. 5 seconds, it takes 5 seconds. There's just no way around that. If you receive it synchronously then your application (or that specific thread) is stuck for 5 seconds. If you receive it asynchronously, you can use that 5 seconds to do something else. Making the operation synchronous doesn't solve your problem. ´getLastLocation()´ may easily return ´null´ on a developer's test phone with no other apps requesting the location. Things may be better on an actual user phone with Facebook, Google Now and whatnot running, but of course we can't rely on that. – Markus Kauppinen Oct 24 '16 at 14:02
  • _"...we already save the users last known location server-side, so retrieving the last known location and sending it to the servers is redundant."_ ...Unless the location has actually changed since the app was run the last time. Then it's not redundant. – Markus Kauppinen Oct 24 '16 at 14:04
  • as far as I've understood it, the location service will only return a last known location, as long as there are apps using it. Once all apps disconnect from the service, it will return null again, which is not an unlikely scenario. I don't want to rely on something as unreliable as that – TormundThunderfist Oct 25 '16 at 13:08

1 Answers1

2

I was able to get around this by placing the process of retrieving the location inside an Rx observable, specifically Single. The Single object's blockingGet method is called to retrieve the location synchronously. It is then placed inside a try catch block to perform retries since the location is not always available during the first try.

(I know this is an old question, but I'm gonna post an answer anyway so that I can share the way how I did it. Sorry for using Kotlin and RxJava! But I think everyone would get the gist of my idea and be able to implement it the way they like. I am also using the latest Google Location API.)

// Use an executor to prevent blocking the thread where 
// this method will be called. Also, DO NOT call this on 
// the main thread!!!
fun tryRetrieveLocationSync(flc: FusedLocationProviderClient,
          executor: Executor, numOfRetries: Int = 3, 
          retryWaitTime: Long = 500): Location {

  var location: Location
  var i = 1

  while (true) {
    try {
      // Place the method call inside a try catch block
      // because `Single#blockingGet` will throw any exception
      // that was provided to the `Emitter#onError` as an argument.

      location = retrieveLocationSync(flc, executor)
    } catch (e: NoLocationDataException) {
      if (i <= numOfRetries) {
        // The value from the `FusedLocationProviderClient#lastLocation` 
        // task usually becomes available after less than second (tried
        // and tested!!), but it's really up to you.

        SystemClock.sleep(retryWaitTime * i)
        i++
      } else {
        throw e // Give up once all the retries have been used.
      }
    } catch (e: Exception) {
      // Rethrow anything else that was thrown from 
      // the `Single#blockingGet`.

      throw e
    }
  }

  return location
}

private fun retrieveLocationSync(flc: FusedLocationProviderClient, 
          executor: Executor): Location {

  return Single.create<Location> { emitter ->
    val task = flc.lastLocation
    task.addOnCompleteListener(executor, OnCompleteListener { // it ->
      if (it.isSuccessful) {
        if (it.result != null) {
          emitter.onSuccess(it.result)
        } else {
          // There is no location data available probably because
          // the location services has just been enabled, the device
          // has just been turned on, or no other applications has 
          // requested the device's location.
          emitter.onError(NoLocationDataException())
        }
      } else {
        // I haven't encountered any exception here but this is
        // just to make sure everything's catchable.
        emitter.onError(it.exception ?: RuntimeException())
      }
    })
  }.blockingGet()
}
dragoon
  • 5,601
  • 5
  • 37
  • 55
bmdelacruz
  • 2,366
  • 1
  • 19
  • 26