Once you’ve defined your
Worker
and
your WorkRequest
,
the last step is to enqueue your work. The simplest way to enqueue work
is to call the WorkManager enqueue()
method, passing the WorkRequest
you
want to run.
Kotlin
val myWork: WorkRequest = // ... OneTime or PeriodicWork WorkManager.getInstance(requireContext()).enqueue(myWork)
Java
WorkRequest myWork = // ... OneTime or PeriodicWork WorkManager.getInstance(requireContext()).enqueue(myWork);
Use caution when enqueuing work to avoid duplication. For example, an app might try to upload its logs to a backend service every 24 hours. If you aren't careful, you might end up enqueuing the same task many times, even though the job only needs to run once. To achieve this goal, you can schedule the work as unique work.
Unique Work
Unique work is a powerful concept that guarantees that you only have one instance of work with a particular name at a time. Unlike IDs, unique names are human-readable and specified by the developer instead of being auto-generated by WorkManager. Unlike tags, unique names are only associated with a single instance of work.
Unique work can be applied to both one-time and periodic work. You can create a unique work sequence by calling one of these methods, depending on whether you’re scheduling repeating work or one time work.
WorkManager.enqueueUniqueWork()
for one time workWorkManager.enqueueUniquePeriodicWork()
for periodic work
Both of these methods accept 3 arguments:
- uniqueWorkName - A
String
used to uniquely identify the work request. - existingWorkPolicy - An
enum
which tells WorkManager what to do if there's already an unfinished chain of work with that unique name. See conflict resolution policy for more information. - work - the
WorkRequest
to schedule.
Using unique work, we can fix our duplicate scheduling issue noted earlier.
Kotlin
val sendLogsWorkRequest = PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS) .setConstraints(Constraints.Builder() .setRequiresCharging(true) .build() ) .build() WorkManager.getInstance(this).enqueueUniquePeriodicWork( "sendLogs", ExistingPeriodicWorkPolicy.KEEP, sendLogsWorkRequest )
Java
PeriodicWorkRequest sendLogsWorkRequest = new PeriodicWorkRequest.Builder(SendLogsWorker.class, 24, TimeUnit.HOURS) .setConstraints(new Constraints.Builder() .setRequiresCharging(true) .build() ) .build(); WorkManager.getInstance(this).enqueueUniquePeriodicWork( "sendLogs", ExistingPeriodicWorkPolicy.KEEP, sendLogsWorkRequest);
Now, if the code runs while a sendLogs job is already in the queue, the existing job is kept and no new job is added.
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. See Chaining work for more details.
Conflict resolution policy
When scheduling unique work, you must tell WorkManager what action to take when there is a conflict. You do this by passing an enum when enqueuing the work.
For one-time work, you provide an
ExistingWorkPolicy
, which
supports 4 options for handling the conflict.
REPLACE
existing work with the new work. This option cancels the existing work.KEEP
existing work and ignore the new work.APPEND
the new work to the end of the existing work. This policy will cause your new work to be chained to the existing work, running after the existing work finishes.
The existing work becomes a prerequisite to the new work. If the existing work
becomes CANCELLED
or FAILED
, the new work is also CANCELLED
or FAILED
.
If you want the new work to run regardless of the status of the existing work,
use APPEND_OR_REPLACE
instead.
APPEND_OR_REPLACE
functions similarly toAPPEND
, except that it is not dependent on prerequisite work status. If the existing work isCANCELLED
orFAILED
, the new work still runs.
For period work, you provide an
ExistingPeriodicWorkPolicy
,
which supports 2 options, REPLACE
and KEEP
. These options function the same
as their ExistingWorkPolicy counterparts.
Observing your work
At any point after enqueuing work, you can check its status by querying
WorkManager by its name
, id
or by a tag
associated with it.
Kotlin
// by id workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo> // by name workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>> // by tag workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>
Java
// by id workManager.getWorkInfoById(syncWorker.id); // ListenableFuture<WorkInfo> // by name workManager.getWorkInfosForUniqueWork("sync"); // ListenableFuture<List<WorkInfo>> // by tag workManager.getWorkInfosByTag("syncTag"); // ListenableFuture<List<WorkInfo>>
The query returns a
ListenableFuture
of a WorkInfo
object, which includes the
id
of the work, its tags, its
current State
, and any output data
set via
Result.success(outputData)
.
A LiveData
variant of each of the
methods allows you to observe changes to the WorkInfo
by registering
a listener. For example, if you wanted to display a message to the user when
some work finishes successfully, you could set it up as follows:
Kotlin
workManager.getWorkInfoByIdLiveData(syncWorker.id) .observe(viewLifecycleOwner) { workInfo -> if(workInfo?.state == WorkInfo.State.SUCCEEDED) { Snackbar.make(requireView(), R.string.work_completed, Snackbar.LENGTH_SHORT) .show() } }
Java
workManager.getWorkInfoByIdLiveData(syncWorker.id) .observe(getViewLifecycleOwner(), workInfo -> { if (workInfo.getState() != null && workInfo.getState() == WorkInfo.State.SUCCEEDED) { Snackbar.make(requireView(), R.string.work_completed, Snackbar.LENGTH_SHORT) .show(); } });
Complex work queries
WorkManager 2.4.0 and higher supports complex querying for enqueued jobs using
WorkQuery
objects. WorkQuery supports
querying for work by a combination of it’s tag(s), state and unique work name.
The following example shows how you can find all work with the tag, “syncTag”,
that is in the FAILED
or CANCELLED
state and has a unique work name of
either “preProcess” or “sync”.
Kotlin
val workQuery = WorkQuery.Builder .fromTags(listOf("syncTag")) .addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED)) .addUniqueWorkNames(listOf("preProcess", "sync") ) .build() val workInfos: ListenableFuture<List<WorkInfo>> = workManager.getWorkInfos(workQuery)
Java
WorkQuery workQuery = WorkQuery.Builder .fromTags(Arrays.asList("syncTag")) .addStates(Arrays.asList(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED)) .addUniqueWorkNames(Arrays.asList("preProcess", "sync") ) .build(); ListenableFuture<List<WorkInfo>> workInfos = workManager.getWorkInfos(workQuery);
Each component (tag, state, or name) in a WorkQuery
is AND
-ed with the
others. Each value in a component is OR
-ed. For example: (name1 OR name2
OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...)
.
WorkQuery
also works with the LiveData equivalent,
getWorkInfosLiveData()
.
Cancelling and stopping work
If you no longer need your previously enqueued work to run, you can ask for it
to be cancelled. Work can be cancelled by its name
, id
or by a tag
associated with it.
Kotlin
// by id workManager.cancelWorkById(syncWorker.id) // by name workManager.cancelUniqueWork("sync") // by tag workManager.cancelAllWorkByTag("syncTag")
Java
// by id workManager.cancelWorkById(syncWorker.id); // by name workManager.cancelUniqueWork("sync"); // by tag workManager.cancelAllWorkByTag("syncTag");
Under the hood, WorkManager checks the
State
of the work. If the work is
already finished,
nothing happens. Otherwise, the work's state is changed to
CANCELLED
and the work
will not run in the future. Any
WorkRequest
jobs that are dependent
on this work will
also be CANCELLED
.
Currently RUNNING
work
receives a call to
ListenableWorker.onStopped()
.
Override this method to handle any potential cleanup. See stop a
running worker for more information.
Stop a running Worker
There are a few different reasons your running Worker
might be stopped by WorkManager:
- You explicitly asked for it to be cancelled (by calling
WorkManager.cancelWorkById(UUID)
, for example). - In the case of unique work,
you explicitly enqueued a new
WorkRequest
with anExistingWorkPolicy
ofREPLACE
. The oldWorkRequest
is immediately considered cancelled. - Your work's constraints are no longer met.
- The system instructed your app to stop your work for some reason. This can happen if you exceed the execution deadline of 10 minutes. The work is scheduled for retry at a later time.
Under these conditions, your Worker is stopped.
You should cooperatively abort any work you had in progress and release any resources your Worker is holding onto. For example, you should close open handles to databases and files at this point. There are two mechanisms at your disposal to understand when your Worker is stopping.
onStopped() callback
WorkManager invokes
ListenableWorker.onStopped()
as soon as your Worker has been stopped. Override this method to close
any resources you may be holding onto.
isStopped() property
You can call the
ListenableWorker.isStopped()
method to check if your worker has already been stopped. If you're
performing long-running or repetitive operations in your Worker, you should
check this property frequently and use it as a signal for stopping work as soon
as possible.
Note: WorkManager ignores the
Result
set by a Worker
that has received the onStop signal, because the Worker is already considered
stopped.