Another important thing introduced with Android Jetpack is WorkManager. In this Android WorkManager Tutorial we are going to learn how we can use the WorkManager API to schedule the tasks that we want to run in the background.
I have already posted a tutorial about Android AlarmManager. But now we should use WorkManager as it is recommended and very easy.
Table of Contents
Android WorkManager Tutorial – Video
An step by step explanation of everything about WorkManager is also available in my YouTube channel you can check it out if you want.
What is WorkManager?
WorkManager is part of Android Jetpack. WorkManager helps us to execute our tasks immediately or an appropriate time.
Earlier we had AlarmManager, JobScheduler, FirebaseJobDispatcher for scheduling the background tasks. But the issues were
- JobScheduler – Available only for API >= 21
- FirebaseJobDispatcher – For backward compatibility
So developer had to understand which method to use when. To overcome these issue we have WorkManager, and it will automatically choose the best method for your task and you do not need to write logic for it. So basically WorkManager is providing an abstraction layer. It gives us a clean interface hiding all the complexities and giving the guaranteed execution of the task.
WorkManager Features
- It is fully backward compatible so in your code you do not need to write if-else for checking the android version.
- With WorkManager we can check the status of the work.
- Tasks can be chained as well, for example when one task is finished it can start another.
- And it provides guaranteed execution with the constraints, we have many constrained available that we will see ahead.
Android WorkManager Tutorial
Now let’s learn how you can use WorkManager in your Android Project. So I would recommend you to start a new Android Studio project and yes you can name it anything like “Work Manager Example” or something.
Remember WorkManager is not available by default so first we need to add the dependency.
Adding WorkManager
- And it is very simple, just go to app level build.gradle file and add the following implementation line.
1 2 3 |
implementation "android.arch.work:work-runtime:1.0.0-alpha11" |
Understanding WorkManager Classes
Before writing the actual codes first we should understand the WorkManager classes.
- Worker: The main class where we will put the work that needs to be done.
- WorkRequest: It defines an individual task, like it will define which worker class should execute the task.
- WorkManager: The class used to enqueue the work requests.
- WorkInfo: The class contains information about the works. For each WorkRequest we can get a LiveData using WorkManager. The LiveData holds the WorkInfo and by observing it we can determine the Work Informations.
Creating a Worker
- For creating a worker we need to create a class and then in the class we need to extend Worker. So here I am creating a class named 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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
package net.simplifiedcoding.workmanager; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.support.annotation.NonNull; import android.support.v4.app.NotificationCompat; import androidx.work.Worker; import androidx.work.WorkerParameters; public class MyWorker extends Worker { public MyWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } /* * This method is responsible for doing the work * so whatever work that is needed to be performed * we will put it here * * For example, here I am calling the method displayNotification() * It will display a notification * So that we will understand the work is executed * */ @NonNull @Override public Result doWork() { displayNotification("My Worker", "Hey I finished my work"); return Result.SUCCESS; } /* * The method is doing nothing but only generating * a simple notification * If you are confused about it * you should check the Android Notification Tutorial * */ private void displayNotification(String title, String task) { NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel("simplifiedcoding", "simplifiedcoding", NotificationManager.IMPORTANCE_DEFAULT); notificationManager.createNotificationChannel(channel); } NotificationCompat.Builder notification = new NotificationCompat.Builder(getApplicationContext(), "simplifiedcoding") .setContentTitle(title) .setContentText(task) .setSmallIcon(R.mipmap.ic_launcher); notificationManager.notify(1, notification.build()); } } |
- So our worker class is ready that is going to perform the work.
- For this work I am going to execute it immediately, on a button click. So first we will also create a button inside activity_main.xml.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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" tools:context=".MainActivity"> <Button android:id="@+id/buttonEnqueue" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Enqueue Work" android:textAllCaps="false" /> </RelativeLayout> |
Performing the Work
- Now let’s perform the work that we created. For this first come inside MainActivity.java and write the following code.
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 34 35 36 37 38 39 |
package net.simplifiedcoding.workmanager; /* * Author: Belal Khan * Android Work Manager Tutorial * Performing a Simple Work * */ import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import androidx.work.OneTimeWorkRequest; import androidx.work.WorkManager; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //This is the subclass of our WorkRequest final OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWorker.class).build(); //A click listener for the button //inside the onClick method we will perform the work findViewById(R.id.buttonEnqueue).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //Enqueuing the work request WorkManager.getInstance().enqueue(workRequest); } }); } } |
- In the above code you can see first we created an object of OneTimeWorkRequest. Above I told you that we use WorkRequest but here we are using OneTimeWorkRequest. This is because WorkRequest is an abstract class and we have to use the direct subclasses of it. So we have two subsclasses for WorkRequest
- OneTimeWorkRequest: Used when we want to perform the work only once.
- PeriodicWorkRequest: Used when we need to perform the task periodically.
- Finally to enqueue the work we simple used the enqueue() method from WorkManager.
- Now you can run your application to see it is working fine.
- But you will say now why do we need to do a lot of things just to display a notification. So the answer here is this is only an example and we executed the work immediately. But you can set many constraints for your work and the best thing is it gets executed in background and it is needed in many scenarios.
Determining Work Status
So we successfully performed the work. Now let’s learn how we can determine the status of the work.
- First we will add a TextView in activity_main.xml to display the status.
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 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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" tools:context=".MainActivity"> <Button android:id="@+id/buttonEnqueue" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Enqueue Work" android:textAllCaps="false" /> <TextView android:id="@+id/textViewStatus" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="@color/colorPrimary" android:orientation="vertical" android:padding="16dp" android:textColor="#ffffff" /> </RelativeLayout> |
- Now we will modify the MainActivity.java as below.
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 34 |
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWorker.class).build(); findViewById(R.id.buttonEnqueue).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { WorkManager.getInstance().enqueue(workRequest); } }); //Getting the TextView final TextView textView = findViewById(R.id.textViewStatus); //Listening to the work status WorkManager.getInstance().getWorkInfoByIdLiveData(workRequest.getId()) .observe(this, new Observer<WorkInfo>() { @Override public void onChanged(@Nullable WorkInfo workInfo) { //Displaying the status into TextView textView.append(workInfo.getState().name() + "\n"); } }); } } |
- Now if you will run your application you will see the following output.
Sending And Receiving Data to/from WorkManager
We can also pass to data to our WorkManager class and we can also get back some data after finishing the work. So let’s see how we can do this.
Sending Data
- So to receive data we will do the following modification in our MyWorker class.
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 34 35 36 37 38 39 40 41 |
public class MyWorker extends Worker { //a public static string that will be used as the key //for sending and receiving data public static final String TASK_DESC = "task_desc"; public MyWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { //getting the input data String taskDesc = getInputData().getString(TASK_DESC); displayNotification("My Worker", taskDesc); return Result.SUCCESS; } private void displayNotification(String title, String task) { NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel("simplifiedcoding", "simplifiedcoding", NotificationManager.IMPORTANCE_DEFAULT); notificationManager.createNotificationChannel(channel); } NotificationCompat.Builder notification = new NotificationCompat.Builder(getApplicationContext(), "simplifiedcoding") .setContentTitle(title) .setContentText(task) .setSmallIcon(R.mipmap.ic_launcher); notificationManager.notify(1, notification.build()); } } |
- And we will do the following changes in our MainActivity class.
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 34 35 36 37 38 39 |
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //creating a data object //to pass the data with workRequest //we can put as many variables needed Data data = new Data.Builder() .putString(MyWorker.TASK_DESC, "The task data passed from MainActivity") .build(); final OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWorker.class) .setInputData(data) .build(); findViewById(R.id.buttonEnqueue).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { WorkManager.getInstance().enqueue(workRequest); } }); final TextView textView = findViewById(R.id.textViewStatus); WorkManager.getInstance().getWorkInfoByIdLiveData(workRequest.getId()) .observe(this, new Observer<WorkInfo>() { @Override public void onChanged(@Nullable WorkInfo workInfo) { textView.append(workInfo.getState().name() + "\n"); } }); } } |
- Now if you will run it you will see the string passed from the MainActivity in the notification.
Receiving Data
- For receiving we can again use the same concept inside doWork() method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@NonNull @Override public Result doWork() { String taskDesc = getInputData().getString(TASK_DESC); displayNotification("My Worker", taskDesc); //setting output data Data data = new Data.Builder() .putString(TASK_DESC, "The conclusion of the task") .build(); setOutputData(data); return Result.SUCCESS; } |
- And we can receive this data inside the observer in MainActivity.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
final TextView textView = findViewById(R.id.textViewStatus); WorkManager.getInstance().getWorkInfoByIdLiveData(workRequest.getId()) .observe(this, new Observer<WorkInfo>() { @Override public void onChanged(@Nullable WorkInfo workInfo) { //receiving back the data if(workInfo != null && workInfo.getState().isFinished()) textView.append(workInfo.getOutputData().getString(MyWorker.TASK_DESC) + "\n"); textView.append(workInfo.getState().name() + "\n"); } }); |
Adding Constraints
Now let’s add some constraint in our work so that it will execute at a specific time. We have many constraints available for example.
- setRequiresCharging(boolean b): If it is set true the work will be only done when the device is charging.
- setRequiresBatteryNotLow(boolean b): Work will be done only when the battery of the device is not low.
- setRequiresDeviceIdle(boolean b): Work will be done only when the device is idle.
For all the methods that are available you can refer to this official link.
Now let’s see how to add the constraints.
- We will do the following modification in the MainActivity.
1 2 3 4 5 6 7 8 9 10 11 12 |
//creating constraints Constraints constraints = new Constraints.Builder() .setRequiresCharging(true) // you can add as many constraints as you want .build(); final OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWorker.class) .setInputData(data) .setConstraints(constraints) .build(); |
- Now after doing this change if you will run your application then the work will only be executed if the device is charging.
Canceling Work
- We can also cancel the work if required. For this we have a method cancelWorkById(). It takes the work id as an argument that we can get from our WorkRequest object.
1 2 3 |
WorkManager.getInstance().cancelWorkById(workRequest.getId()); |
- We also have the following methods to cancel the work.
- cancelAllWork(): To cancel all the work.
- cancelAllWorkByTag(): To cancel all works of a specific tag.
- cancelUniqueWork(): To cancel a unique work.
PeriodicWorkRequest
Till now we were using OneTimeWorkRequest that will perform the work only once. But sometimes it is needed to perform the work periodically for example taking backup to the server. In scenarios like this we can use PeriodicWorkRequest class. Everything else is same.
1 2 3 4 5 |
final PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(MyWorker.class, 10, TimeUnit.HOURS) .build(); |
In the above code we are defining that the work should be done after every 10 hours.
Chaining Works
We can also chain the works that we need to perform.
1 2 3 4 5 6 7 |
WorkManager.getInstance(). beginWith(workRequest) .then(workRequest1) .then(workRequest2) .enqueue(); |
I hope the above code is explaining itself.
For now that’s all for this post, there are many things to learn but we will do this while creating some real apps in coming tutorials. Meanwhile if you found this post helpful then please SHARE it with your friends. And for any question you can leave your comments below. Thank You 🙂