What I need to accomplish: My application should request the current location of the user, repeating every 15 minutes, also in the background and even when the application is terminated.
What I did: I am using the FusedLocationProviderAPI, called within a periodical WorkManager, extending the ListenableWorker, as the API is asynchronous. Each time a new Location Update is provided by the system (max. every fifteen minutes), a LocationCallback is called.
As ResolvableFuture seems to be deprecated to use within the ListenableWorker's startWork-Method, I use the CallbackToFutureAdapter. Within this adapter, I initialize the LocationCallback and call the doWork-Method, which includes all the logic for requesting information.
My problem: When the screen is active, everything works fine. But as soon as the application runs in the background or is terminated, the LocationCallback won't be evoked. (last log: "Waiting for callback"). Please note that I removed big chunks of code below (like exception handling and details).
Here's my code within the MyWorker extends ListenableWorker startWork-Method:
public ListenableFuture<Result> startWork() {
return CallbackToFutureAdapter.getFuture(completer -> {
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
// Storing data to different repositories.
completer.set(Result.success());
}
};
doWork();
return "startSomeAsyncStuff";
});
}
doWork():
public Result doWork() {
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(mContext);
mSettingsClient = LocationServices.getSettingsClient(mContext);
createLocationCallback();
createLocationRequest();
buildLocationSettingsRequest();
startLocationUpdates();
return Result.success();
}
startLocationUpdates():
private void startLocationUpdates() {
// Begin by checking if the device has the necessary location settings.
mSettingsClient.checkLocationSettings(mLocationSettingsRequest)
.addOnSuccessListener(new OnSuccessListener<LocationSettingsResponse>() {
@Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
Log.i(TAG, "All location settings are satisfied.");
mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.getMainLooper());
Log.i(TAG, "Location updated requested - Waiting for Callback");
}
})
Related posts: How do I return a ListenableFuture<Result> with work manager 2.0? and WorkManager: ResolvableFuture can only be called from within the same library group prefix
So, after hours of trial and error here's the solution that finally works for me:
The trick is to add a Foreground-Service, that ensures the worker not to be cancelled, until a new Location-Update arrives from the system. However, you can not avoid showing a Notification during that timeframe. I also moved the LocationCallback into the doWork-Method, just before requesting Location Updates (as you need to pass the Callback to that method.)
This solution is based on https://developer.android.com/topic/libraries/architecture/workmanager/advanced/long-running , which I should have taken a look at in the beginning to save me some frustration ;)
Here's the startWork()-Method:
public ListenableFuture<Result> startWork() {
return CallbackToFutureAdapter.getFuture(completer -> {
setForegroundAsync(createForegroundInfo());
doWork(completer);
// This value is used only for debug purposes: it will be used
// in toString() of returned future or error cases.
return "Location Worker";
});
}