WorkManager : A brief Introduction

WorkManager : A brief Introduction

What is WorkManager?

WorkManager is part of Android Jetpack. WorkManager helps us to execute our tasks immediately or at an appropriate time. It is an API that makes it easy to schedule deferrable, asynchronous tasks that are expected to run even if the app gets killed or device restarts. Deferrable tasks are those tasks that are not required to run immediately but reliably even if app gets killed. WorkManager API is a suitable replacement for all previous Android background scheduling APIs, including FirebaseJobDispatcher, GcmNetworkManager and Job Scheduler. This API works back to API level 14 while also being conscious of battery life.

Features of WorkManager:

  1. It is fully backward compatible, means you don’t need to write if-else for checking the android version.
  2. It provides functionality to check the status of the work.
  3. It provides the functionality of chaining tasks, means when one task finishes it can start another.
  4. It guarantees execution of the task.
  5. It provides Work Constraints using which you can define optimal conditions for your work to run.

Let’s see how to use WorkManager in an actual android project.

To use WorkManager we need to add its dependency first as it is not present by default. · In your app level build.gradle file add the following

def work_version = "2.5.0"
implementation "androidx.work:work-runtime:$work_version"

Before proceeding lets understand the WorkManager classes that we will be using.

  1. Worker — Worker class is the main class where we will write the code that needs to be executed (our task).
  2. WorkRequest — It is used to define each and every task, for example which worker class should execute the task. There are two subsclasses for WorkRequest a. OneTimeWorkRequest: Used when we want to perform the work only once.
    b. PeriodicWorkRequest: Used when we need to perform the task periodically.
  3. WorkManager — It is the class which is used to enqueue the work requests.
  4. WorkInfo — This class contains information about works. We can monitor the status of a request using LiveData provided by WorkManager. LiveData contains WorkInfo and we can use an observer to monitor any changes in our request state.

Creating Worker Class. I have created a class MyWorker as below:

package com.example.workmanager;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;

import static com.example.workmanager.MainActivity.MY_DATA_KEY;

public class MyWorker extends Worker {

    public static final String MY_OUTPUT_DATA_KEY = "output_key";

    public MyWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {
        //do your task here
        Data data = getInputData();
        String myTaskDesc = data.getString(MY_DATA_KEY);
        displayNotification("Hey Rohit", myTaskDesc);
        Data outputData = new Data.Builder()
                .putString(MY_OUTPUT_DATA_KEY, "output data")
                .build();
        return Result.success(outputData);
    }

    private void displayNotification(String title, String task) {
        NotificationManager notificationManager = (NotificationManager) getApplicationContext()
                .getSystemService(Context.NOTIFICATION_SERVICE);

        NotificationChannel channel = new NotificationChannel("iamr0h1t", "iamr0h1t",
                NotificationManager.IMPORTANCE_DEFAULT);
        notificationManager.createNotificationChannel(channel);

        NotificationCompat.Builder notification = new NotificationCompat.Builder(
                getApplicationContext(), "iamr0h1t")
                .setContentTitle(title)
                .setContentText(task)
                .setSmallIcon(R.mipmap.ic_launcher);
        notificationManager.notify(1, notification.build());
    }
}

doWork() — This is the method where we write the code to execute our task.

Now to perform the work we have created an Activity with a Button and TextView to check status of work.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="STATUS\n"
        android:textSize="40dp" />

    <Button
        android:id="@+id/button"
        android:layout_width="270dp"
        android:layout_height="50dp"
        android:text="PERFORM TASK" />

</LinearLayout>

MainActivity.java

package com.example.workmanager;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.WorkRequest;

import static com.example.workmanager.MyWorker.MY_OUTPUT_DATA_KEY;

public class MainActivity extends AppCompatActivity {

    public static final String MY_DATA_KEY = "key";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        //create input data here
        Data inputData = new Data.Builder()
                .putString(MY_DATA_KEY, "input data")
                .build();

        Constraints constraints = new Constraints.Builder()
                .setRequiresCharging(true)
                .build();

        WorkRequest myWorkRequest =
                new OneTimeWorkRequest.Builder(MyWorker.class)
                        .setInputData(inputData)
                        //set input data here
                        .setConstraints(constraints)
                        //set constraints here
                        .build();

        Button button = findViewById(R.id.button);
        TextView textView = findViewById(R.id.text);
        button.setOnClickListener(view -> WorkManager
                .getInstance(MainActivity.this)
                .enqueue(myWorkRequest));

        WorkManager.getInstance(this).getWorkInfoByIdLiveData(myWorkRequest.getId())
                .observe(this, workInfo -> {

            if (workInfo != null) {
                if (workInfo.getState().isFinished()) {
                    Data outputData = workInfo.getOutputData();
                    String outputString = outputData.getString(MY_OUTPUT_DATA_KEY);
                    textView.append(outputString + "\n");
                }

                String status = workInfo.getState().toString();
                textView.append(status + "\n");
            }
        });
    }
}

Sending and Receiving Data. Here we have created a Data object to pass to our Worker Class. Further we can also receive data from the Worker Class when our task is finished.

Data inputData = new Data.Builder()
        .putString(MY_DATA_KEY, "input data")
        .build();

Below code is in doWork method of our Worker class using which we can receive data when our task is done.

Data outputData = new Data.Builder()
        .putString(MY_OUTPUT_DATA_KEY, "output data")
        .build();
return Result.success(outputData);

Adding Constraints Here we have added a constraint so that our work wil be executed only when our device is charging.

Constraints constraints = new Constraints.Builder()
        .setRequiresCharging(true)
        .build();

We have many constraints available. Some of them are :

  1. setRequiresCharging(boolean b): If it is set true the work will be only done when the device is charging.
  2. setRequiresBatteryNotLow(boolean b): Work will be done only when the battery of the device is not low.
  3. setRequiresDeviceIdle(boolean b): Work will be done only when the device is idle.

Creating WorkRequest As we discussed earlier there are two subclasses of WorkRequest . Here we have used OneTimeWorkRequest.

OneTimeWorkRequest myWorkRequest =
        new OneTimeWorkRequest.Builder(MyWorker.class)
                .setInputData(inputData)//set input data here
                .setConstraints(constraints)//set constraints here
                .build();

Enqueue the WorkRequest Enqueue the request to WorkManager for execution. The task will be executed when the required constraints are fulfilled. In our case task will be performed when device is plugged into charging.

button.setOnClickListener(view -> WorkManager
        .getInstance(MainActivity.this)
       button.setOnClickListener(view -> WorkManager
        .getInstance(MainActivity.this)
        .enqueue(myWorkRequest)); .enqueue(myWorkRequest));

Observing the WorkInfo Here we are observing the workInfo by applying a observer on our workRequest Id. We also receive data sent by the Worker class here on successful execution.

        .observe(this, workInfo -> {

    if (workInfo != null) {
        if (workInfo.getState().isFinished()) {
            Data outputData = workInfo.getOutputData();
            String outputString = outputData.getString(MY_OUTPUT_DATA_KEY);
            textView.append(outputString + "\n");
        }

        String status = workInfo.getState().toString();
        textView.append(status + "\n");
    }
});

We can also cancel our work request using its id like below.

WorkManager.getInstance(this).cancelWorkById(myWorkRequest.getId());

Using WorkManager we can also Chain our work requests like one task executes after other finishes. Below is sample for this.

WorkManager.getInstance(this)
        .beginWith(workRequest)
        .then(workRequest1)
        .then(workRequest2)
        .enqueue();

That’s it from my side for the basic introduction of Android’s WorkManager. Hope you guys found it useful.