Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asynchronous Worker in Android WorkManager

Google recently announced new WorkManager architecture component. It makes it easy to schedule synchronous work by implementing doWork() in Worker class, but what if I want to do some asynchronous work in the background? For example, I want to make a network service call using Retrofit. I know I can make a synchronous network request, but it would block the thread and just feels wrong. Is there any solution for this or it is just not supported at the moment?

like image 595
Anton Tananaev Avatar asked May 18 '18 02:05

Anton Tananaev


People also ask

What is the use of WorkManager in Android?

WorkManager is intended for work that is required to run reliably even if the user navigates off a screen, the app exits, or the device restarts. For example: Sending logs or analytics to backend services. Periodically syncing application data with a server.

What is difference between WorkManager and coroutines?

The main difference between a Worker class and a CoroutineWorker is that the doWork() method in a CoroutineWorker is a suspend function and can run asynchronous tasks, while Worker 's doWork() can only execute synchronous talks.

Does WorkManager run on background thread?

Worker is the simplest implementation, and the one you have seen in previous sections. WorkManager automatically runs it on a background thread (that you can override).

Does WorkManager work in doze mode?

Another nice feature of WorkManager is that it respects power-management features so that if a job is scheduled to run at a defined time and the device is in Doze at that time, WorkManager will try to run the task during a maintenance window if the constraints are met or after Doze is lifted.


2 Answers

I used a countdownlatch and waited for this to reach 0, which will only happen once the asynchronous callback has updated it. See this code:

public WorkerResult doWork() {          final WorkerResult[] result = {WorkerResult.RETRY};         CountDownLatch countDownLatch = new CountDownLatch(1);         FirebaseFirestore db = FirebaseFirestore.getInstance();          db.collection("collection").whereEqualTo("this","that").get().addOnCompleteListener(task -> {             if(task.isSuccessful()) {                 task.getResult().getDocuments().get(0).getReference().update("field", "value")                         .addOnCompleteListener(task2 -> {                             if (task2.isSuccessful()) {                                 result[0] = WorkerResult.SUCCESS;                             } else {                                 result[0] = WorkerResult.RETRY;                             }                             countDownLatch.countDown();                         });             } else {                 result[0] = WorkerResult.RETRY;                 countDownLatch.countDown();             }         });          try {             countDownLatch.await();         } catch (InterruptedException e) {             e.printStackTrace();         }          return result[0];      } 
like image 139
TomH Avatar answered Sep 29 '22 19:09

TomH


FYI there is now ListenableWorker, which is designed to be asynchronous.

Edit: Here are some snippets of example usage. I cut out big chunks of code that I think aren't illustrative, so there's a good chance there's a minor error or two here.

This is for a task that takes a String photoKey, retrieves metadata from a server, does some compression work, and then uploads the compressed photo. This happens off the main thread. Here's how we send the work request:

private void compressAndUploadFile(final String photoKey) {     Data inputData = new Data.Builder()             .putString(UploadWorker.ARG_PHOTO_KEY, photoKey)             .build();     Constraints constraints = new Constraints.Builder()             .setRequiredNetworkType(NetworkType.CONNECTED)             .build();     OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(UploadWorker.class)             .setInputData(inputData)             .setConstraints(constraints)             .build();     WorkManager.getInstance().enqueue(request); } 

And in UploadWorker:

public class UploadWorker extends ListenableWorker {     private static final String TAG = "UploadWorker";     public static final String ARG_PHOTO_KEY = "photo-key";      private String mPhotoKey;      /**      * @param appContext   The application {@link Context}      * @param workerParams Parameters to setup the internal state of this worker      */     public UploadWorker(@NonNull Context appContext, @NonNull WorkerParameters workerParams) {         super(appContext, workerParams);         mPhotoKey = workerParams.getInputData().getString(ARG_PHOTO_KEY);     }      @NonNull     @Override     public ListenableFuture<Payload> onStartWork() {         SettableFuture<Payload> future = SettableFuture.create();         Photo photo = getPhotoMetadataFromServer(mPhotoKey).addOnCompleteListener(task -> {             if (!task.isSuccessful()) {                 Log.e(TAG, "Failed to retrieve photo metadata", task.getException());                 future.setException(task.getException());                 return;             }             MyPhotoType photo = task.getResult();             File file = photo.getFile();             Log.d(TAG, "Compressing " + photo);             MyImageUtil.compressImage(file, MyConstants.photoUploadConfig).addOnCompleteListener(compressionTask -> {                 if (!compressionTask.isSuccessful()) {                     Log.e(TAG, "Could not parse " + photo + " as an image.", compressionTask.getException());                     future.set(new Payload(Result.FAILURE));                     return;                 }                 byte[] imageData = compressionTask.getResult();                 Log.d(TAG, "Done compressing " + photo);                 UploadUtil.uploadToServer(photo, imageData);                 future.set(new Payload(Result.SUCCESS));             });         });         return future;     } } 

EDIT

Depending on the things you are using in your application, you can also extends RxWorker (if you are using RxJava) or CoroutineWorker (if you're using Coroutines). They both extend from ListenableWorker.

like image 30
Bartholomew Furrow Avatar answered Sep 29 '22 20:09

Bartholomew Furrow