WorkManager makes it easy to set up and schedule elaborate task requests. You can use the APIs for scenarios like these:
- Chained sequences of tasks that run in a specified order
- Unique named sequences, with rules for what happens if the app launches two sequences with the same name
- Tasks that pass and return values, including chained tasks where each task passes arguments to the next task in the chain
Chained tasks
Your app might need to run several tasks in a particular order.
WorkManager
allows you to create and enqueue a work sequence that
specifies multiple tasks, and what order they should run in.
For example, suppose your app has three
OneTimeWorkRequest
objects: workA
, workB
, and
workC
. The tasks must be run in that order. To enqueue them, create a sequence
with the
WorkManager.beginWith(OneTimeWorkRequest)
method, passing the first OneTimeWorkRequest
object;
that method returns a WorkContinuation
object, which
defines a sequence of tasks. Then add
the remaining OneTimeWorkRequest
objects, in order, with
WorkContinuation.then(OneTimeWorkRequest)
, and
finally, enqueue the whole sequence with
WorkContinuation.enqueue()
:
Kotlin
WorkManager.getInstance() .beginWith(workA) // Note: WorkManager.beginWith() returns a // WorkContinuation object; the following calls are // to WorkContinuation methods .then(workB) // FYI, then() returns a new WorkContinuation instance .then(workC) .enqueue()
Java
WorkManager.getInstance() .beginWith(workA) // Note: WorkManager.beginWith() returns a // WorkContinuation object; the following calls are // to WorkContinuation methods .then(workB) // FYI, then() returns a new WorkContinuation instance .then(workC) .enqueue();
The WorkManager
runs the tasks in the requested order,
according to each task's specified constraints. If any task returns
Result.failure()
,
the whole sequence ends.
You can also pass multiple OneTimeWorkRequest
objects to any of the
beginWith(List<OneTimeWorkRequest>)
and
then(List<OneTimeWorkRequest>)
calls. If you pass
several OneTimeWorkRequest
objects to a single method
call, the WorkManager
runs all of those tasks (in parallel)
before it runs the rest of the sequence. For example:
Kotlin
WorkManager.getInstance() // First, run all the A tasks (in parallel): .beginWith(Arrays.asList(workA1, workA2, workA3)) // ...when all A tasks are finished, run the single B task: .then(workB) // ...then run the C tasks (in any order): .then(Arrays.asList(workC1, workC2)) .enqueue()
Java
WorkManager.getInstance() // First, run all the A tasks (in parallel): .beginWith(Arrays.asList(workA1, workA2, workA3)) // ...when all A tasks are finished, run the single B task: .then(workB) // ...then run the C tasks (in any order): .then(Arrays.asList(workC1, workC2)) .enqueue();
You can create more complex sequences by joining multiple chains with the WorkContinuation.combine(List<OneTimeWorkRequest>)
methods. For example, suppose you want to run a sequence like this:
WorkContinuation
to set up complex chained tasks.
To set up this sequence, create two separate chains, then join them together into a third one:
Kotlin
val chain1 = WorkManager.getInstance() .beginWith(workA) .then(workB) val chain2 = WorkManager.getInstance() .beginWith(workC) .then(workD) val chain3 = WorkContinuation .combine(Arrays.asList(chain1, chain2)) .then(workE) chain3.enqueue()
Java
WorkContinuation chain1 = WorkManager.getInstance() .beginWith(workA) .then(workB); WorkContinuation chain2 = WorkManager.getInstance() .beginWith(workC) .then(workD); WorkContinuation chain3 = WorkContinuation .combine(Arrays.asList(chain1, chain2)) .then(workE); chain3.enqueue();
In this case, WorkManager
runs workA
before workB
. It also
runs workC
before workD
. After both workB
and workD
have finished,
WorkManager
runs workE
.
There are a number of variants of the WorkContinuation
methods that provide shorthands for particular situations. For details, see the
WorkContinuation
reference.
Unique work sequences
You can create a unique work sequence, by beginning the sequence with a
call to
beginUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest)
instead of beginWith(OneTimeWorkRequest)
.
Each unique work sequence has a name; the WorkManager
only
permits one work sequence with that name at a time. When you create a new unique
work sequence, you specify what WorkManager
should do if
there's already an unfinished sequence with the same name:
- Cancel the existing sequence and REPLACE it with the new one
- KEEP the existing sequence and ignore your new request
- APPEND your new sequence to the existing one, running the new sequence's first task after the existing sequence's last task finishes
Unique work sequences can be useful if you have a task that shouldn't be enqueued multiple times. For example, if your app needs to sync its data to the network, you might enqueue a sequence named "sync", and specify that your new task should be ignored if there's already a sequence with that name. Unique work sequences can also be useful if you need to gradually build up a long chain of tasks. For example, a photo editing app might let users undo a long chain of actions. Each of those undo operations might take a while, but they have to be performed in the correct order. In this case, the app could create an "undo" chain and append each undo operation to the chain as needed.
Input parameters and returned values
For greater flexibility, you can pass arguments to your tasks and have the tasks
return results. Passed and returned values are key-value pairs. To pass an
argument to a task, call the
WorkRequest.Builder.setInputData(Data)
method before you create the WorkRequest
object. That method takes a Data
object, which you create with
Data.Builder
. The Worker
class can access those
arguments by calling
Worker.getInputData()
. To
output a return value, the task should include it in the Result
(for example, returning
Result.success(Data)
.
You can get the output by observing the task's WorkInfo
.
For example, suppose you have a Worker
class that performs a
time-consuming calculation. The following code shows what the Worker
class would look like:
Kotlin
// Define the parameter keys: const val KEY_X_ARG = "X" const val KEY_Y_ARG = "Y" const val KEY_Z_ARG = "Z" // ...and the result key: const val KEY_RESULT = "result" // Define the Worker class: class MathWorker(context : Context, params : WorkerParameters) : Worker(context, params) { override fun doWork(): Result { val x = inputData.getInt(KEY_X_ARG, 0) val y = inputData.getInt(KEY_Y_ARG, 0) val z = inputData.getInt(KEY_Z_ARG, 0) // ...do the math... val result = myCrazyMathFunction(x, y, z); //...set the output, and we're done! val output: Data = mapOf(KEY_RESULT to result).toWorkData() return Result.success(output) } }
Java
// Define the Worker class: public class MathWorker extends Worker { // Define the parameter keys: public static final String KEY_X_ARG = "X"; public static final String KEY_Y_ARG = "Y"; public static final String KEY_Z_ARG = "Z"; // ...and the result key: public static final String KEY_RESULT = "result"; public MathWorker( @NonNull Context context, @NonNull WorkerParameters params) { super(context, params); } @Override public Result doWork() { // Fetch the arguments (and specify default values): int x = getInputData().getInt(KEY_X_ARG, 0); int y = getInputData().getInt(KEY_Y_ARG, 0); int z = getInputData().getInt(KEY_Z_ARG, 0); // ...do the math... int result = myCrazyMathFunction(x, y, z); //...set the output, and we're done! Data output = new Data.Builder() .putInt(KEY_RESULT, result) .build(); return Result.success(output); } }
To create the work and pass the arguments, you'd use code like this:
Kotlin
val myData: Data = mapOf("KEY_X_ARG" to 42, "KEY_Y_ARG" to 421, "KEY_Z_ARG" to 8675309) .toWorkData() // ...then create and enqueue a OneTimeWorkRequest that uses those arguments val mathWork = OneTimeWorkRequestBuilder<MathWorker>() .setInputData(myData) .build() WorkManager.getInstance().enqueue(mathWork)
Java
// Create the Data object: Data myData = new Data.Builder() // We need to pass three integers: X, Y, and Z .putInt(KEY_X_ARG, 42) .putInt(KEY_Y_ARG, 421) .putInt(KEY_Z_ARG, 8675309) // ... and build the actual Data object: .build(); // ...then create and enqueue a OneTimeWorkRequest that uses those arguments OneTimeWorkRequest mathWork = new OneTimeWorkRequest.Builder(MathWorker.class) .setInputData(myData) .build(); WorkManager.getInstance().enqueue(mathWork);
The returned value would be available in the task's WorkInfo
:
Kotlin
WorkManager.getInstance().getWorkInfoByIdLiveData(mathWork.id) .observe(this, Observer { info -> if (info != null && info.state.isFinished) { val myResult = info.outputData.getInt(KEY_RESULT, myDefaultValue) // ... do something with the result ... } })
Java
WorkManager.getInstance().getWorkInfoByIdLiveData(mathWork.getId()) .observe(lifecycleOwner, info -> { if (info != null && info.getState().isFinished()) { int myResult = info.getOutputData().getInt(KEY_RESULT, myDefaultValue)); // ... do something with the result ... } });
If you chain tasks, the outputs from one task are available as inputs to the
next task in the chain. If it's a simple chain, with a single
OneTimeWorkRequest
followed by another single
OneTimeWorkRequest
, the first task returns its result by
calling
Result.success(Data)
,
and the next task fetches that result by calling
getInputData()
. If the
chain is more complicated—for example, because several tasks all send output to
a single following task—you can define an InputMerger
on the OneTimeWorkRequest.Builder
to
specify what should happen if different tasks return an output with the same
key.
Additional resources
WorkManager
is an Android Jetpack architecture component. See it
in use in the Sunflower
demo app.