So you are here to learn about WorkManager, before actually teaching you how to use it let me tell you what it is and what it does. WorkManager lets you schedule tasks in the background. Say your app needs to sync data or move a file to different folder or some other database related work, you can use WorkManager to schedule these tasks with a very simple and easy to use API. Under the hood, WorkManager uses JobScheduler, AlarmManager or Firebase JobDispatcher to schedule work.
In this tutorial you will be developing a simple app that schedules work and that work will be sending a notification. The code for this tutorial is available at this GitHub repo.
Adding Dependency
In your app.gradle add the WorkManager dependency.
1 |
implementation "android.arch.work:work-runtime:1.0.0-alpha04" |
Understanding the concept of WorkManager
Lets take a look at the concept of WorkManager . Initially you need to create a new class that extends the Worker class and override the doWork method. What ever work you want to do will go in this function. Then you need to create an object of WorkRequest specifying the class that you made previously. Then using WorkManager , you will schedule the work.
Let’s Code!
Go ahead and create a new project and add the WorkManager dependency in it. In the MainActivity layout file add a Button, that is all we need for now.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" tools:context=".MainActivity"> <Button android:id="@+id/simpleWorkButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Simple Work" /> </LinearLayout> |
Now create a class named MyWorker that extends Worker and override the doWork() method.
1 2 3 4 5 6 7 8 |
public class MyWorker extends Worker { @NonNull @Override public Result doWork() { return Result.SUCCESS; } } |
The return type here defines the result of the work that was conducted. There are 3 types of Result that you can return.
- Result.SUCCESS : The work completed successfully.
- Result.RETRY: The work failed and WorkManager should retry it again.
- Result.FAILURE: The work failed and there is no need to retry.
In this class make a method that sends a simple notification with a title and text, call this method in the doWork method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class MyWorker extends Worker { @NonNull @Override public Result doWork() { sendNotification("Simple Work Manager", "I have been send by WorkManager."); return Result.SUCCESS; } public void sendNotification(String title, String message) { NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); //If on Oreo then notification required a notification channel. if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel("default", "Default", NotificationManager.IMPORTANCE_DEFAULT); notificationManager.createNotificationChannel(channel); } NotificationCompat.Builder notification = new NotificationCompat.Builder(getApplicationContext(), "default") .setContentTitle(title) .setContentText(message) .setSmallIcon(R.mipmap.ic_launcher); notificationManager.notify(1, notification.build()); } } |
In your MainActivity create a new WorkRequest. There are two types of WorkRequest that you can create, OneTimeWorkRequest (runs only one time) and PeriodicWorkRequest (runs periodically over a specified period of time). Create an instance of OneTimeWorkRequest using the builder class.
1 2 |
OneTimeWorkRequest simpleRequest = new OneTimeWorkRequest.Builder(MyWorker.class) .build(); |
Now all you have to do is give this object to WorkManager by using the enqueue method. Add a click listener on the Button your created and schedule the work.
1 2 3 4 5 6 |
findViewById(R.id.simpleWorkButton).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { WorkManager.getInstance().enqueue(simpleRequest); } }); |
Once you click the button, a notification will pop up.
WorkManager schedules all the work on a different thread and not on the main thread.
Listening to Work Status
WorkManager gives you the ability to listen to the status of work. It returns a LiveData object to listen to work status. (If you don’t know about LiveData read this tutorial).
Go ahead and add a TextView in the main_activity.xml layout file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" tools:context=".MainActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/simpleWorkButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Simple Work" /> </LinearLayout> |
Get the reference to this TextView in your MainActivity. By getting the ID of OneTimeWorkRequest, you can listen to WorkStatus. The code is pretty simple and self explanatory:
1 2 3 4 5 6 7 8 9 |
WorkManager.getInstance().getStatusById(simpleRequest.getId()) .observe(this, new Observer<WorkStatus>() { @Override public void onChanged(@Nullable WorkStatus workStatus) { if (workStatus != null) { mTextView.append("SimpleWorkRequest: " + workStatus.getState().name() + "\n"); } } }); |
Run the app and click the button to see various states of the WorkRequest.
Communicating Data with Worker
Let’s see how you can send data to and from MyWorker. In the MyWorker class, define 3 String constants.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class MyWorker extends Worker { public static final String EXTRA_TITLE = "title"; public static final String EXTRA_TEXT = "text"; public static final String EXTRA_OUTPUT_MESSAGE = "output_message"; @NonNull @Override public Result doWork() { sendNotification("Simple Work Manager", "I have been send by WorkManager."); return Result.SUCCESS; } public void sendNotification(String title, String message) { NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); //If on Oreo then notification required a notification channel. if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel("default", "Default", NotificationManager.IMPORTANCE_DEFAULT); notificationManager.createNotificationChannel(channel); } NotificationCompat.Builder notification = new NotificationCompat.Builder(getApplicationContext(), "default") .setContentTitle(title) .setContentText(message) .setSmallIcon(R.mipmap.ic_launcher); notificationManager.notify(1, notification.build()); } } |
To send data you need to create an instance of Data class, put appropriate data and attach it to the OneTimeWorkRequest. OneTimeWorkRequest.Builder provides a method named setInputData that takes in the Data object.
1 2 3 4 5 6 7 8 |
Data data = new Data.Builder() .putString(MyWorker.EXTRA_TITLE, "Message from Activity!") .putString(MyWorker.EXTRA_TEXT, "Hi! I have come from activity.") .build(); final OneTimeWorkRequest simpleRequest = new OneTimeWorkRequest.Builder(MyWorker.class) .setInputData(data) .build(); |
In the MyWorker class extract this data (using getInputData method) and pass it to the sendNotification method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class MyWorker extends Worker { public static final String EXTRA_TITLE = "title"; public static final String EXTRA_TEXT = "text"; public static final String EXTRA_OUTPUT_MESSAGE = "output_message"; @NonNull @Override public Result doWork() { String title = getInputData().getString(EXTRA_TITLE, "Default Title"); String text = getInputData().getString(EXTRA_TEXT, "Default Text"); sendNotification("Simple Work Manager", "I have been send by WorkManager."); return Result.SUCCESS; } public void sendNotification(String title, String message) { ... } } |
Now let’s see how you can send data from MyWorker to MainActivity. The process is same, you have to create a instance of Data class and add data to it, then pass this object to the setOutputData method in MyWorker.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class MyWorker extends Worker { public static final String EXTRA_TITLE = "title"; public static final String EXTRA_TEXT = "text"; public static final String EXTRA_OUTPUT_MESSAGE = "output_message"; @NonNull @Override public Result doWork() { String title = getInputData().getString(EXTRA_TITLE, "Default Title"); String text = getInputData().getString(EXTRA_TEXT, "Default Text"); sendNotification("Simple Work Manager", "I have been send by WorkManager."); Data output = new Data.Builder() .putString(EXTRA_OUTPUT_MESSAGE, "I have come from MyWorker!") .build(); setOutputData(output); return Result.SUCCESS; } public void sendNotification(String title, String message) { ... } } |
In MainActivity, where you added the listener for listening to work status changes, add the code to extract the data, make sure to check the work is complete by using isFinished.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
WorkManager.getInstance().getStatusById(simpleRequest.getId()) .observe(this, new Observer<WorkStatus>() { @Override public void onChanged(@Nullable WorkStatus workStatus) { if (workStatus != null) { mTextView.append("SimpleWorkRequest: " + workStatus.getState().name() + "\n"); } if (workStatus != null && workStatus.getState().isFinished()) { String message = workStatus.getOutputData().getString(MyWorker.EXTRA_OUTPUT_MESSAGE, "Default message"); mTextView.append("SimpleWorkRequest (Data): " + message); } } }); |
Never miss a post from TheTechnoCafe
Adding Constraints
You can add many conditions to your work request like, only execute the work when the device is charging and is idle. There are many other constraints available, you can check all of them out in Android Studio using the code predictor. Use the setContrainsts function to add them to your WorkRequest.
1 2 3 4 5 6 7 8 9 |
Constraints constraints = new Constraints.Builder() .setRequiresCharging(true) .setRequiresDeviceIdle(true) .build(); OneTimeWorkRequest simpleRequest = new OneTimeWorkRequest.Builder(MyWorker.class) .setInputData(data) .setConstraints(constraints) .build(); |
Disconnect your device from charging and click the button, you will see that no Notification appears. You can see the status of work, it is only enqueued. Once you connect back the cable you won’t immediately get the Notification. WorkManager starts the WorkRequests between certain time windows. So your task might not run immediately once the conditions are met, but it will eventually run. Don’t Worry!
Cancelling Work
So you have enqueued your work, but now you want to cancel it! WorkManager provides support for that.
You can cancel Work by using the work id. Go ahead and add a new Button in the layout file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" tools:context=".MainActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/simpleWorkButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Simple Work" /> <Button android:id="@+id/cancelWorkButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Cancel Work" /> </LinearLayout> |
Since there is very little happening in the your doWork function, once your press the button you won’t have enough time to cancel the task as it will be completed instantly. So lets add a delay in MyWorker using Thread.sleep().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public class MyWorker extends Worker { public static final String EXTRA_TITLE = "title"; public static final String EXTRA_TEXT = "text"; public static final String EXTRA_OUTPUT_MESSAGE = "output_message"; @NonNull @Override public Result doWork() { String title = getInputData().getString(EXTRA_TITLE, "Default Title"); String text = getInputData().getString(EXTRA_TEXT, "Default Text"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } sendNotification("Simple Work Manager", "I have been send by WorkManager."); Data output = new Data.Builder() .putString(EXTRA_OUTPUT_MESSAGE, "I have come from MyWorker!") .build(); setOutputData(output); return Result.SUCCESS; } public void sendNotification(String title, String message) { ... } } |
Now cancel the task using the cancelWorkById provided by WorkManager.
1 2 3 4 5 6 7 8 |
final UUID workId = simpleRequest.getId(); findViewById(R.id.cancelWorkButton).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { WorkManager.getInstance().cancelWorkById(workId); } }); |
To every work request you make, you can assign a tag. You can use this tag to cancel tasks. Suppose you want to cancel all tasks that are uploading images selected by the user, just give all of them the same tag and call cancelWorkByTag.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
final OneTimeWorkRequest simpleRequest = new OneTimeWorkRequest.Builder(MyWorker.class) .setInputData(data) .setConstraints(constraints) .addTag("simple_work") .build(); findViewById(R.id.cancelWorkButton).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { WorkManager.getInstance().cancelAllWorkByTag("simple_work"); //WorkManager.getInstance().cancelWorkById(workId); } }); |
Tap on the button to enqueue the work and then tap the cancel button.
PeriodicWorkRequest
If you want to schedule work that runs periodically, use PeriodicWorkRequest.
1 2 3 4 5 6 7 8 9 10 |
final PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(MyWorker.class, 12, TimeUnit.HOURS) .addTag("periodic_work") .build(); findViewById(R.id.simpleWorkButton).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { WorkManager.getInstance().enqueue(periodicWorkRequest); } }); |
PeriodicWorkRequest provides exactly the same methods and abilities that OneTimeWorkRequest provides, it only takes 2 parameters extra in its constructor. Both of them specify the time internal that your request should be run periodically. First one is the the actual value and second one is the TimeUnit for the value.
Chaining Tasks
You can chain multiple tasks together to run in a sequence. For example:
1 2 3 4 5 |
WorkManager.getInstance() .beginWith(workA) .then(workB) .then(workC) .enqueue(); |
First workA will run and the workB and on and on. If any of the task returns Result.FAILURE, then the subsequent tasks won’t run.
There is much more to chaining tasks, I recommend you to look at the documentation on chaining.
That is it for this tutorial. I hope you like it. Many more tutorials to come, so stay connected.
How to create a Widget in Android.
How to make two pane layout in Android.
Guide to Notifications in Android.
Getting Started with Retrofit in Android.
7 Comments
rushang · July 24, 2018 at 11:50 am
Nice workmanager exmaple there are all thinks that you should know about WorkManager.
Michael Mossman · September 3, 2018 at 11:57 pm
Thank you Gurleen for this easy-to-follow example =)
Gurleen Sethi · September 6, 2018 at 3:23 pm
Appreciate the feedback 🙂
Venugopal · September 18, 2018 at 11:19 am
But this Notification not coming when app force closed by user by swiping right from recent app list. Any idea how we can run this periodic notification even when user force closes the app?
Amit Sharma · October 20, 2018 at 2:21 pm
Hello Sir, your example is very easy to understand and really helpful but i am having a problem while using PeriodicWorkRequest. In PeriodicWorkRequest we can set time Interval to repeat the time so i set the time interval to 1 Minute but it is not repeating the task for every minute. My code is as below:
final PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(BackgroundWork.class, 1, TimeUnit.MINUTES)
.addTag(“periodic_work”)
.build();
WorkManager.getInstance().enqueue(periodicWorkRequest);
plz reply me on this if i am doing anything wrong…this is working only for once when app is started.
arash · November 19, 2018 at 7:43 am
see this link https://github.com/googlecodelabs/android-workmanager/issues/69
Donzaala · June 30, 2019 at 8:40 pm
Thanks for this awesome tutorial. Please how do I start the WorkManager as soon as app is installed and not first run. Thanks in advance