user2923322
user2923322

Reputation: 1162

WorkManager - how to execute jobs sequentially

I have a service listening for incoming FCM notifications. When such a notification is received, I run a job with WorkManager.

When multiple notifications are received at the same time, how can I ensure that only one job is executed at the time? The jobs should be executed sequentially. In this case, I want to send an sms and these cannot not be sent simultaneously. (Note that I do other stuff like http requests in this job before and after sending sms, that's why I decided to create a job for it instead of sending an sms straight from the service.)

public class MyFirebaseMessagingService extends FirebaseMessagingService {
    public void onMessageReceived(RemoteMessage remoteMessage) {
        //...
        OneTimeWorkRequest sendSmsWorker = new OneTimeWorkRequest.Builder(SendSmsWorker.class).build();
        WorkManager.getInstance().enqueue(sendSmsWorker);
        //...
    }
}

I've checked the advanced section in the WorkManager docs; it mentions Chained sequences, however these must be chained to each other explicitly (beginWith/then/then/...).

Upvotes: 16

Views: 3713

Answers (3)

Petrakeas
Petrakeas

Reputation: 1690

Another way to execute Workers sequentially without the limitation of breaking the chain if you return a result other than Result.SUCCESS, is to extend ListenableWorker instead of Worker.

If you see the Worker implementation, they are using WorkManager's background executor to run all Worker instances. You can define your own executor that uses a single thread:

// Define your executor here
private static final Executor sExecutor = Executors.newSingleThreadExecutor();

@Override
    public final @NonNull ListenableFuture<Result> startWork() {
        mFuture = SettableFuture.create();
        sSerialExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Result result = doWork();
                    mFuture.set(result);
                } catch (Throwable throwable) {
                    mFuture.setException(throwable);
                }

            }
        });
        return mFuture;
    }

Note that you'll have to use Guava's SettableFuture.

Upvotes: 0

Hammad khan
Hammad khan

Reputation: 29

There is a concept in Work Manager, which is chaining though, which we can arrange workers as many orders as we want to.

Suppose there are some sequential tasks in which we have to

  1. Create some data
  2. Cache data
  3. Upload that data

Now, for achieving this we can chain them in this order so they run sequentially

WorkManager.getInstance(myContext)
   .beginWith(createDataWork) //This is going enqueue first
   .then(cacheWork)  // enqueue after returning success from create worker
   .then(uploadWork)// enqueue after returning success from cache worker
   .enqueue() 

Every then will returns a new WorkerContinuation() instance

Upvotes: 0

Perraco
Perraco

Reputation: 17380

You need to create the jobs as a OneTimeWorkRequest with the same group name, and enqueue them as a unique work with APPEND. This will make all the jobs to be appended to the same chain and execute sequentially.

Beware that you should always return Result.SUCCESS, or Result.RETRY at most. Returning Result.FAILURE will cancel all the enqueued jobs. If you need to know if a job has failed, then you could set a custom flag in the worker setOutputData to handle such scenario as you may require.

final String JOB_GROUP_NAME = "your_jobs_group_name";

......

OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(YOUR_WORK.class)
    .setInputData(YOUR_WORK_DATA_BUILDER.build())
    .build();

final WorkManager workManager = WorkManager.getInstance();
WorkContinuation work = workManager.beginUniqueWork(JOB_GROUP_NAME, ExistingWorkPolicy.APPEND, request);
work.enqueue();

Upvotes: 26

Related Questions