WorkManager is a library for scheduling and executing deferrable background work in Android. It is the recommended replacement for Firebase JobDispatcher. The following guide will walk you through the process of migrating your Firebase JobDispatcher implementation to WorkManager.
Gradle setup
To import the WorkManager library into your Android project, add the following
dependencies to your app's build.gradle file:
dependencies {
def work_version = "2.3.4"
// (Java only)
implementation "androidx.work:work-runtime:$work_version"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
// optional - RxJava2 support
implementation "androidx.work:work-rxjava2:$work_version"
// optional - GCMNetworkManager support
implementation "androidx.work:work-gcm:$work_version"
// optional - Test helpers
androidTestImplementation "androidx.work:work-testing:$work_version"
}
From JobService to Workers
FirebaseJobDispatcher
uses a subclass of
JobService
as an entry point for defining the work which needs to be done. You might be
using JobService directly, or using
SimpleJobService.
A JobService will look something like this:
Kotlin
import com.firebase.jobdispatcher.JobParameters
import com.firebase.jobdispatcher.JobService
class MyJobService : JobService() {
override fun onStartJob(job: JobParameters): Boolean {
// Do some work here
return false // Answers the question: "Is there still work going on?"
}
override fun onStopJob(job: JobParameters): Boolean {
return false // Answers the question: "Should this job be retried?"
}
}
Java
import com.firebase.jobdispatcher.JobParameters;
import com.firebase.jobdispatcher.JobService;
public class MyJobService extends JobService {
@Override
public boolean onStartJob(JobParameters job) {
// Do some work here
return false; // Answers the question: "Is there still work going on?"
}
@Override
public boolean onStopJob(JobParameters job) {
return false; // Answers the question: "Should this job be retried?"
}
}
If you are using SimpleJobService you will have overridden onRunJob(),
which returns a @JobResult int type.
The key difference is when you are using JobService directly, onStartJob()
is called on the main thread, and it is the app’s responsibility to offload the
work to a background thread. On the other hand, if you are using
SimpleJobService, that service is responsible for executing your work on a
background thread.
WorkManager has similar concepts. The fundamental unit of work in
WorkManager is a
ListenableWorker. There are also
other useful subtypes of workers like
Worker,
RxWorker, and CoroutineWorker
(when using Kotlin coroutines).
JobService maps to a ListenableWorker
If you are using JobService directly, then the worker it maps to is a
ListenableWorker. If you are using SimpleJobService then you should use
Worker instead.
Let’s use the above example (MyJobService) and look at how we can convert it
to a ListenableWorker.
Kotlin
import android.content.Context
import androidx.work.ListenableWorker
import androidx.work.ListenableWorker.Result
import androidx.work.WorkerParameters
import com.google.common.util.concurrent.ListenableFuture
class MyWorker(appContext: Context, params: WorkerParameters) :
ListenableWorker(appContext, params) {
override fun startWork(): ListenableFuture<ListenableWorker.Result> {
// Do your work here.
TODO("Return a ListenableFuture<Result>")
}
override fun onStopped() {
// Cleanup because you are being stopped.
}
}
Java
import android.content.Context;
import androidx.work.ListenableWorker;
import androidx.work.ListenableWorker.Result;
import androidx.work.WorkerParameters;
import com.google.common.util.concurrent.ListenableFuture;
class MyWorker extends ListenableWorker {
public MyWorker(@NonNull Context appContext, @NonNull WorkerParameters params) {
super(appContext, params);
}
@Override
public ListenableFuture<ListenableWorker.Result> startWork() {
// Do your work here.
Data input = getInputData();
// Return a ListenableFuture<>
}
@Override
public void onStopped() {
// Cleanup because you are being stopped.
}
}
The basic unit of work in WorkManager is a ListenableWorker. Just like
JobService.onStartJob(), startWork() is called on the main thread. Here
MyWorker implements ListenableWorker and returns an instance of
ListenableFuture,
which is used to signal work completion asynchronously. You should choose your
own threading strategy here.
The ListenableFuture here eventually returns a ListenableWorker.Result type
which can be one of Result.success(), Result.success(Data outputData),
Result.retry(), Result.failure(), or Result.failure(Data outputData). For
more information, please see the reference page for
ListenableWorker.Result.
onStopped() is called to signal that the ListenableWorker needs to stop,
either because the constraints are no longer being met (for example, because the
network is no longer available), or because a WorkManager.cancel…() method was
called. onStopped() may also be called if the OS decides to shut down your
work for some reason.
SimpleJobService maps to a Worker
When using SimpleJobService the above worker will look like:
Kotlin
import android.content.Context;
import androidx.work.Data;
import androidx.work.ListenableWorker.Result;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
TODO("Return a Result")
}
override fun onStopped() {
super.onStopped()
TODO("Cleanup, because you are being stopped")
}
}
Java
import android.content.Context;
import androidx.work.Data;
import androidx.work.ListenableWorker.Result;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
class MyWorker extends Worker {
public MyWorker(@NonNull Context appContext, @NonNull WorkerParameters params) {
super(appContext, params);
}
@Override
public ListenableWorker.Result doWork() {
// Do your work here.
Data input = getInputData();
// Return a ListenableWorker.Result
Data outputData = new Data.Builder()
.putString(“Key”, “value”)
.build();
return Result.success(outputData);
}
@Override
public void onStopped() {
// Cleanup because you are being stopped.
}
}
Here doWork() returns an instance of ListenableWorker.Result to signal work
completion synchronously. This is similar to SimpleJobService, which schedules
jobs on a background thread.
JobBuilder maps to WorkRequests
FirebaseJobBuilder uses Job.Builder to represent Job metadata. WorkManager
uses WorkRequest to fill this role.
WorkManager has two types of WorkRequests:
OneTimeWorkRequest and
PeriodicWorkRequest.
If you are currently using Job.Builder.setRecurring(true), then you should
create a new PeriodicWorkRequest. Otherwise, you should use a
OneTimeWorkRequest.
Let’s look at what scheduling a complex Job with FirebaseJobDispatcher might
look like:
Kotlin
val input:Bundle = Bundle().apply {
putString("some_key", "some_value")
}
val job = dispatcher.newJobBuilder()
// the JobService that will be called
.setService(MyService::class.java)
// uniquely identifies the job
.setTag("my-unique-tag")
// one-off job
.setRecurring(false)
// don't persist past a device reboot
.setLifetime(Lifetime.UNTIL_NEXT_BOOT)
// start between 0 and 60 seconds from now
.setTrigger(Trigger.executionWindow(0, 60))
// don't overwrite an existing job with the same tag
.setReplaceCurrent(false)
// retry with exponential backoff
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
.setConstraints(
// only run on an unmetered network
Constraint.ON_UNMETERED_NETWORK,
// // only run when the device is charging
Constraint.DEVICE_CHARGING
)
.setExtras(input)
.build()
dispatcher.mustSchedule(job)
Java
Bundle input = new Bundle();
input.putString("some_key", "some_value");
Job myJob = dispatcher.newJobBuilder()
// the JobService that will be called
.setService(MyJobService.class)
// uniquely identifies the job
.setTag("my-unique-tag")
// one-off job
.setRecurring(false)
// don't persist past a device reboot
.setLifetime(Lifetime.UNTIL_NEXT_BOOT)
// start between 0 and 60 seconds from now
.setTrigger(Trigger.executionWindow(0, 60))
// don't overwrite an existing job with the same tag
.setReplaceCurrent(false)
// retry with exponential backoff
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
// constraints that need to be satisfied for the job to run
.setConstraints(
// only run on an unmetered network
Constraint.ON_UNMETERED_NETWORK,
// only run when the device is charging
Constraint.DEVICE_CHARGING
)
.setExtras(input)
.build();
dispatcher.mustSchedule(myJob);
To achieve the same with WorkManager you will need to:
- Build input data which can be used as input for the
Worker. - Build a
WorkRequestwith the input data and constraints similar to the ones defined above forFirebaseJobDispatcher. - Enqueue the
WorkRequest.
Setting up inputs for the Worker
FirebaseJobDispatcher uses a Bundle to send input data to the JobService.
WorkManager uses Data instead. So
that becomes:
Kotlin
import androidx.work.workDataOf
val data = workDataOf("some_key" to "some_val")
Java
import androidx.work.Data;
Data input = new Data.Builder()
.putString("some_key", "some_value")
.build();
Setting up Constraints for the Worker
FirebaseJobDispatcher uses
Job.Builder.setConstaints(...)
to set up constraints on jobs. WorkManager uses
Constraints instead.
Kotlin
import androidx.work.*
val constraints: Constraints = Constraints.Builder().apply {
setRequiredNetworkType(NetworkType.CONNECTED)
setRequiresCharging(true)
}.build()
Java
import androidx.work.Constraints;
import androidx.work.Constraints.Builder;
import androidx.work.NetworkType;
Constraints constraints = new Constraints.Builder()
// The Worker needs Network connectivity
.setRequiredNetworkType(NetworkType.CONNECTED)
// Needs the device to be charging
.setRequiresCharging(true)
.build();
Creating the WorkRequest (OneTime or Periodic)
To create OneTimeWorkRequests and PeriodicWorkRequests you should use
OneTimeWorkRequest.Builder
and PeriodicWorkRequest.Builder.
To create a OneTimeWorkRequest which is similar to the above Job you should
do the following:
Kotlin
import androidx.work.*
import java.util.concurrent.TimeUnit
val constraints: Constraints = TODO("Define constraints as above")
val request: OneTimeWorkRequest =
// Tell which work to execute
OneTimeWorkRequestBuilder<MyWorker>()
// Sets the input data for the ListenableWorker
.setInputData(input)
// If you want to delay the start of work by 60 seconds
.setInitialDelay(60, TimeUnit.SECONDS)
// Set a backoff criteria to be used when retry-ing
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30000, TimeUnit.MILLISECONDS)
// Set additional constraints
.setConstraints(constraints)
.build()
Java
import androidx.work.BackoffCriteria;
import androidx.work.Constraints;
import androidx.work.Constraints.Builder;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.OneTimeWorkRequest.Builder;
import androidx.work.Data;
// Define constraints (as above)
Constraints constraints = ...
OneTimeWorkRequest request =
// Tell which work to execute
new OneTimeWorkRequest.Builder(MyWorker.class)
// Sets the input data for the ListenableWorker
.setInputData(inputData)
// If you want to delay the start of work by 60 seconds
.setInitialDelay(60, TimeUnit.SECONDS)
// Set a backoff criteria to be used when retry-ing
.setBackoffCriteria(BackoffCriteria.EXPONENTIAL, 30000, TimeUnit.MILLISECONDS)
// Set additional constraints
.setConstraints(constraints)
.build();
The key difference here is that WorkManager’s jobs are always persisted across device reboot automatically.
If you want to create a PeriodicWorkRequest then you would do something like:
Kotlin
val constraints: Constraints = TODO("Define constraints as above")
val request: PeriodicWorkRequest =
PeriodicWorkRequestBuilder<MyWorker>(15, TimeUnit.MINUTES)
// Sets the input data for the ListenableWorker
.setInputData(input)
// Other setters
.build()
Java
import androidx.work.BackoffCriteria;
import androidx.work.Constraints;
import androidx.work.Constraints.Builder;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.PeriodicWorkRequest.Builder;
import androidx.work.Data;
// Define constraints (as above)
Constraints constraints = ...
PeriodicWorkRequest request =
// Executes MyWorker every 15 minutes
new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.MINUTES)
// Sets the input data for the ListenableWorker
.setInputData(input)
. // other setters (as above)
.build();
Scheduling work
Now that you have defined a Worker and a WorkRequest, you are ready to
schedule work.
Every Job defined with FirebaseJobDispatcher had a tag which was used to
uniquely identify a Job. It also provided a way for the application to tell
the scheduler if this instance of a Job was to replace an existing copy of the
Job by calling setReplaceCurrent.
Kotlin
val job = dispatcher.newJobBuilder()
// the JobService that will be called
.setService(MyService::class.java)
// uniquely identifies the job
.setTag("my-unique-tag")
// don't overwrite an existing job with the same tag
.setRecurring(false)
// Other setters...
.build()
Java
Job myJob = dispatcher.newJobBuilder()
// the JobService that will be called
.setService(MyJobService.class)
// uniquely identifies the job
.setTag("my-unique-tag")
// don't overwrite an existing job with the same tag
.setReplaceCurrent(false)
// other setters
// ...
dispatcher.mustSchedule(myJob);
When using WorkManager, you can achieve the same result by using
enqueueUniqueWork() and enqueueUniquePeriodicWork() APIs (when using a
OneTimeWorkRequest and a PeriodicWorkRequest, respectively). For more
information, see the reference pages for
WorkManager.enqueueUniqueWork()
and WorkManager.enqueueUniquePeriodicWork().
This will look something like:
Kotlin
import androidx.work.*
val request: OneTimeWorkRequest = TODO("A WorkRequest")
WorkManager.getInstance(myContext)
.enqueueUniqueWork("my-unique-name", ExistingWorkPolicy.KEEP, request)
Java
import androidx.work.ExistingWorkPolicy;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
OneTimeWorkRequest workRequest = // a WorkRequest;
WorkManager.getInstance(myContext)
// Use ExistingWorkPolicy.REPLACE to cancel and delete any existing pending
// (uncompleted) work with the same unique name. Then, insert the newly-specified
// work.
.enqueueUniqueWork("my-unique-name", ExistingWorkPolicy.KEEP, workRequest);
Cancelling work
With FirebaseJobDispatcher you could cancel work using:
Kotlin
dispatcher.cancel("my-unique-tag")
Java
dispatcher.cancel("my-unique-tag");
When using WorkManager you can use:
Kotlin
import androidx.work.WorkManager
WorkManager.getInstance(myContext).cancelUniqueWork("my-unique-name")
Java
import androidx.work.WorkManager;
WorkManager.getInstance(myContext).cancelUniqueWork("my-unique-name");
Initializing WorkManager
WorkManager needs to be initialized once per app, typically using a
ContentProvider or an Application.onCreate().
WorkManager typically initializes itself by using a ContentProvider. However,
there are some subtle differences in defaults with regard to the size of the
threadpool, and the number of workers that can be scheduled at a given time. So
you might need to customize WorkManager.
Typically, this customization is done using
WorkManager.initialize().
This allows you to
customize the background Executor used to run Workers, and the
WorkerFactory used to construct
Workers. (WorkerFactory is useful in the context of dependency injection).
Please read the documentation for this method to make sure you stop automatic
initialization of WorkManager.
For more information, see the documentation for initialize() and for
Configuration.Builder.

