--

Monday 7 January 2019

Chapter -5 : Modern Android Architecture ‘Android Jetpack’ - Handling background tasks (WorkManager)

        In this Android Jetpack, team at Google release one library specifically designed for scheduling and managing the background tasks. It’s called “WorkManager”. The WorkManager API makes it easy to specify deferrable, asynchronous tasks and when they should run. These APIs let you create a task and hand it off to WorkManager to run immediately or at an appropriate time.

Drawbacks of existing background task APIs

       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 issues 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

  1. It is fully backward compatible so in your code you do not need to write if-else for checking the android version.
  2. So it has benefits over the Firebase Job Dispatcher which backwards compatible but not work for device without Google play service.
  3. With WorkManager we can check the status of the work.
  4. Tasks can be chained as well, for example when one task is finished it can start another.
  5. And it provides guaranteed execution with the constraints, we have many constrained available that we will see ahead. 

While scheduling background/long running task like network availability, device in doze mode (Like, if your app is in DOZE mode then it wouldn’t execute, it wouldn’t wake up your phone just to do that work), sync data only when charging so many but WorkManager is taking care of all these constraints and give app guaranteed to be executed even if device get rebooted or app get exited without executing that task.


Technical details of Base classes

Worker - Specifies what tasks we need to perform. By extending this class and perform the work here.
WorkRequest - Represents an individual task. At a minimum, a WorkRequest object specifies which Worker class should perform the task. Every WorkRequest has an auto-generated unique ID; we can use the ID to do things like cancel a queued task or get the task's state. We will be using one of the direct subclasses, OneTimeWorkRequest or 

PeriodicWorkRequest.
WorkManager - Enqueues and manages the work requests. You pass your WorkRequest object to WorkManager to enqueue the task.
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 NotificationWorker. This class has one abstract method called doWork(). As the name suggests, you need to perform the work you want to do in the background. This method will be called in the background/worker thread. Write a program to perform the task in this method.

public class NotificationWorker extends Worker {
public static final String TASK_DESC = "Desc";
public NotificationWorker(@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("Hello", "Welcome to my world!"+"\n"+taskDesc);
//setting output data
Data data = new Data.Builder()
.putString(TASK_DESC, "The notification is shown.")
.build();
setOutputData(data);
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("truedreamz", "truedreamz", NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
NotificationCompat.Builder notification = new NotificationCompat.Builder(getApplicationContext(), "truedreamz")
.setContentTitle(title)
.setContentText(task)
.setSmallIcon(R.mipmap.ic_launcher);
notificationManager.notify(1, notification.build());
}
}


WorkResult has three values
1) SUCCESS
2) FAILURE
3) RETRY : Means encountered a transient error i.e if device lost network connection in the middle so we will have to retry after little bit some back off.


Performing the Work


Now let’s perform the work that we created. For this first come inside MainActivity.java and write the following code.




OneTimeWorkRequest: Used when we want to perform the work only once. This is because WorkRequest is an abstract class and we have to use the direct subclasses of it.
Finally to enqueue the work we simple used the enqueue() method from WorkManager.

Determining Work Status


So we successfully performed the work. Now let’s learn how we can determine the status of the work.



Now you can run your application to see it is working fine.



Data transfer to/from WorkManager


We can also pass data to our WorkManager class and we can also get back some data after finishing the work.

Sending Data

Data data = new Data.Builder()
.putString(NotificationWorker.TASK_DESC, "The data passed from MainActivity")
.build();
final OneTimeWorkRequest oneTimeWorkRequest=
new OneTimeWorkRequest.Builder(NotificationWorker.class)
.setInputData(data)
.build();


So to receive data we will do the following modification in our NotificationWorker class.
String taskDesc = getInputData().getString(TASK_DESC);

Adding Constraints


Now let’s add some constraints 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 only be 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.

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.

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


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 the same.

public class PeriodicToastWorker extends Worker {
public PeriodicToastWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
//getting the input data
displayNotification("Hello", "Periodic notification!");
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("truedreamz", "truedreamz", NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
NotificationCompat.Builder notification = new NotificationCompat.Builder(getApplicationContext(), "truedreamz")
.setContentTitle(title)
.setContentText(task)
.setSmallIcon(R.mipmap.ic_launcher);
notificationManager.notify(1, notification.build());
}
}




The WorkManager attempts to run your task at the interval you request, subject to the constraints you impose and its other requirements.
I have uploaded the latest source code in GitHub for your reference. Kindly raise your queries in the command section.

References :
https://developer.android.com/topic/libraries/architecture/workmanager/
https://developer.android.com/topic/libraries/architecture/workmanager/basics#java
https://www.simplifiedcoding.net/android-workmanager-tutorial/


Happy coding!!!
Cheers!!!