Search code examples
androidandroid-workmanager

Upgrading to WorkManager 2.7.0: How to implement getForegroundInfoAsync for RxWorker?


My app is targeting API 31/Android 12 and version 2.7.0 of WorkManager is required for that according to Google, so in order to do that, I have added setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) to my OneTimeWorkRequestBuilder and added the necessary changes in the AndroidManifest as well (see this link for details). However, when I ran my app I encountered this error:

java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Expedited WorkRequests require a ListenableWorker to provide an implementation for `getForegroundInfoAsync()`

There are no examples or documentation provided by Google on how to do this for RxWorker, but I found this answer here in Stackoverflow, but it is for Coroutines.

My question is how do you implement the said getForegroundInfoAsync for RxWorker when getForegroundInfoAsync has to return ListenableFuture<ForegroundInfo>--reading the docs it seems I have to add Guava to my app to do this? Since the docs for ListenableFuture says to Avoid implementing ListenableFuture from scratch. If you can't get by with the standard implementations, prefer to derive a new Future instance with the methods in Futures or, if necessary, to extend AbstractFuture.


Solution

  • Edit: Currently, there are two ways to approach this:

    1. Using restricted API

    Looking at the source code for ListenableWorker, we find this for getForegroundInfoAsync:

        @NonNull
        public ListenableFuture<ForegroundInfo> getForegroundInfoAsync() {
            SettableFuture<ForegroundInfo> future = SettableFuture.create();
            String message =
                    "Expedited WorkRequests require a ListenableWorker to provide an implementation for"
                            + " `getForegroundInfoAsync()`";
            future.setException(new IllegalStateException(message));
            return future;
        }
    

    So in my own RxWorker's implementation of getForegroundInfoAsync, I tried creating a SettableFuture but I saw a lint warning telling me that usage of SettableFuture is restricted within its library. But this can be bypassed by just annotating the implementation getForegroundInfoAsync with @SuppressLint("RestrictedApi"). Here is how my code roughly looks like:

        @SuppressLint("RestrictedApi")
        override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> {
            val future = SettableFuture.create<ForegroundInfo>()
    
            val notificationId = id.hashCode()
            val fileName = inputData.getString(KEY_OUTPUT_FILE_NAME)
    
            if (fileName == null) {
                future.setException(IllegalStateException("Filename is null"))
                return future
            }
    
            val notificationBuilder = getNotificationBuilder(fileName)
    
            future.set(ForegroundInfo(notificationId, notificationBuilder.build()))
            return future
        }
    

    2. Using CallbackToFutureAdapter

    Instead of relying on restricted API, you can use CallbackToFutureAdapter, but this solution requires the Futures AndroidX library, see link here. After adding the said library to your project, you can use CallbackToFutureAdapter to return a ListenableFuture in this way:

    override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> {
        val fileName = inputData.getString(KEY_OUTPUT_FILE_NAME)
    
        return CallbackToFutureAdapter.getFuture {
            if (fileName == null) {
                it.setException(IllegalStateException("Filename is null"))
            } else {
                notificationBuilder = getNotificationBuilder(fileName)
    
                it.set(ForegroundInfo(notificationId, notificationBuilder.build()))
            }
        }
    }
    

    One warning though, when creating a PendingIntent for your Notification, do not forget to have PendingIntent.FLAG_MUTABLE flag set (see this answer for details)