androidfutureandroid-workmanagerfusedlocationproviderapi

Why is LocationCallback not firing within CallbackToFutureAdapter?


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


Solution

  • 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";
            });
        }