Schedule Onetime Notification With Android WorkManager

Welcome to the new world of Android WorkManager. If you have developed an application before which requires certain task to be executed at a latter time, one of the android APIs that will come to your mind is AlarmManager.

Yes, AlarmManager has served us and will continue to do so. With the introduction of android WorkManager, android has simplified the way to differ execution of a task with WorkManager.

WorkManager is added to android as part of android Jetpack and it is backward compatibility. Task added to WorkManager can be executed immediately or a later time in the future.

Android WorkManager runs task in its own thread while the app is running so it does not interfere with the Android UI thread. If the app is closed when WorkManager wants to execute a task, it will create a background thread using either AlarmManager, JobScheduler  or Firebase JobDispatcher depending on the device API level.

Benefits of Using WorkManager

According to Google Codelab tutorial below are the benefits of using WorkManager.

WorkManager is a simple, but incredibly flexible library that has many additional benefits. These include:

  1. Support for both asynchronous one-off and periodic tasks
  2. Support for constraints such as network conditions, storage space, and charging status
  3. Chaining of complex work requests, including running work in parallel
  4. Output from one work request used as input for the next
  5. Handles API level compatibility back to API level 14
  6. Works with or without Google Play services
  7. Follows system health best practices
  8. LiveData support to easily display work request state in UI

When to use WorkManager

With what we covered so far, the question in your mind right now is when do I need to use WorkManager? We always get to a point when we asked the same question so you are not alone.

WorkManager comes in handy when we want to accomplish any of the following task below.

  1. Uploading logs
  2. Applying filters to images and saving the image
  3. Periodically syncing local data with the network
  4. Sending periodic notification

One of the major benefit of using WorkManager is it’s guarantee task execution.

How to use WorkManager in android project

WorkManager API has some abstract, interface and concrete classes available to use to enqueue, scheduling and execution tasks.

We will first of all look at the Worker Class.

Worker Class

Worker class is an abstract class that is use to specify the task to be executed. Since it is an abstract class, we will need an implementation of the class.

The worker class has a single abstract method doWork() that has to be implement. This method is called automatically by WorkManager to execute the task it has implemented.

Below is a simple implementation of concrete Worker class

public class NotificationHandler extends Worker {

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

    @NonNull
    @Override
    public ListenableWorker.Result doWork() {

        return Result.success();
    }
}

WorkRequest Class

Another important abstract class in WorkManager API is the WorkRequest class. The WorkRequest class represent an individual Worker class and specify which worker class task should be executed. The WorkRequest uses a constraint to specified the conditions under which a task should be executed.

The WorkRequest has a unique id which identifies its Worker and can also be used to cancel its task.

We will learn more about Constraint in the next section.

Since the WorkRequest is an abstract class, we will use one of its concrete classes OneTimeWorkRequest or PeriodicWorkRequest

To create an instance of a WorkRequest, there is a Builder helper classto create a WorkRequest.

Below is a simple example on how to create a WorkRequest.

OneTimeWorkRequest notificationWork = new 
       OneTimeWorkRequest.Builder(NotificationHandler.class).build();

Constraints Class

Constraints are specific restrictions on when a task should be run. An example of such constraints is to run a task when the battery level is more than 50 percent.

Constraints like the WorkRequest can be created with a Builder helper class.

WorkManager Class

The WorkManager class takes a WorkRequest instance and queue it with other tasks to be executed. The WorkManager manages work request and decide how to spread the tasks out to run them at an appropriate time while honoring their constraints.

WorkManager is created as a singleton instance by calling the WorkManager getInstance() method. Below is a simple code example.

WorkManager.getInstance().enqueue(notificationWork);

Create Android Notification using WorkManager

Now that we have done with the theoretical aspect of WorkManager API we needed to understand, we will create a new android project which will use the WorkManager API to send a one time notification.

APP SCREENSHOTS

1. CREATE A NEW ANDROID PROJECT

  • Open Android Studio
  • Go to file menu
  • Select  new
  • Enter project name
  • Enter activity name
  • Keep other default settings
  • Click on finish button to create a new android project

2. ADD LIBRARY DEPENDENCIES IN BUILD.GRADLE FILE

In other to use WorkManager API in our android project we need to add the WorkManager Library in our project build.gradle file.

Open build.gradle file and add the library code below.

//Work
implementation "android.arch.work:work-runtime:1.0.0-beta01"
implementation "android.arch.work:work-firebase:1.0.0-alpha11"

3. Open the activity_main.xml

In our activity layout file, we are going to add few widgets in the layout. An EditText which we will use to get user input and a Button which will take the user input value and set the time to trigger the notification.

Open the layout file and add the code below.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="24dp"
    android:orientation="vertical"
    android:gravity="center"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#000000"
        android:textSize="12sp"
        android:layout_marginTop="6dp"
        android:text="@string/notification"/>

    <EditText
        android:id="@+id/alert_time"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:inputType="number"
        tools:ignore="Autofill,LabelFor" />

    <Button
        android:id="@+id/set_alert_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:padding="8dp"
        android:textColor="#ffffff"
        android:text="@string/set_alert"
        android:background="@color/colorAccent"/>

</LinearLayout>

4. Create A New Java Class

We will create a new java file by right clicking on the src folder > New > Java Class

Name the class NotificationHandler.java or any name of your choice. The NotificationHandler class will extends the abstract Worker class from WorkManager API.

Open the class and add the code below.

public class NotificationHandler extends Worker {


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


    public static void scheduleReminder(long duration, Data data, String tag) {
        OneTimeWorkRequest notificationWork = new OneTimeWorkRequest.Builder(NotificationHandler.class)
                .setInitialDelay(duration, TimeUnit.MILLISECONDS).addTag(tag)
                .setInputData(data).build();

        WorkManager instance = WorkManager.getInstance();
        instance.enqueue(notificationWork);
    }


    public static void cancelReminder(String tag) {
        WorkManager instance = WorkManager.getInstance();
        instance.cancelAllWorkByTag(tag);
    }


    @NonNull
    @Override
    public ListenableWorker.Result doWork() {

        String title = getInputData().getString(Constants.EXTRA_TITLE);
        String text = getInputData().getString(Constants.EXTRA_TEXT);
        int id = (int) getInputData().getLong(Constants.EXTRA_ID, 0);

        sendNotification(title, text, id);
        return Result.success();
    }


    private void sendNotification(String title, String text, int id) {
        Intent intent = new Intent(getApplicationContext(), MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        intent.putExtra(Constants.EXTRA_ID, id);

        PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);

        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("default", "Default", NotificationManager.IMPORTANCE_DEFAULT);
            Objects.requireNonNull(notificationManager).createNotificationChannel(channel);
        }

        NotificationCompat.Builder notification = new NotificationCompat.Builder(getApplicationContext(), "default")
                .setContentTitle(title)
                .setContentText(text)
                .setContentIntent(pendingIntent)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setAutoCancel(true);

        Objects.requireNonNull(notificationManager).notify(id, notification.build());
    }

}

As can be seen in the code above, we have added a method that will create a new android Notification instance for us and this method is called inside the doWork()Worker class method.

Another important piece of code is the setInputData()method which was used to pass data to the doWork()method. We will create a different tutorial on this topic.

5. Open the MainActivity class

In the MainActivity class, we will get the instances of the EditText and Button widgets. When the set alert button is click, we will get the user input value. If the EditText content is empty, the user will be show a toast to notify the user that the EditText is empty.

Once the user input value is validated, will call the Worker class method to set the notification using the inputted value.

Copy and paste the code below to MainActivity class.

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

    private EditText alertInputValue;

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

        alertInputValue = (EditText)findViewById(R.id.alert_time);

        Button alertButton = (Button)findViewById(R.id.set_alert_button);
        alertButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String inputValue = alertInputValue.getText().toString();
                if(inputValue.equals("")){
                    Toast.makeText(MainActivity.this, "Input value must not be empty", Toast.LENGTH_SHORT).show();
                }

                //Generate notification string tag
                String tag = generateKey();

                //Get time before alarm
                int minutesBeforeAlert = Integer.valueOf(inputValue);
                long alertTime = getAlertTime(minutesBeforeAlert) - System.currentTimeMillis();
                long current =  System.currentTimeMillis();

                Log.d(TAG, "Alert time - " + alertTime + "Current time " + current);

                int random = (int )(Math.random() * 50 + 1);

                //Data
                Data data = createWorkInputData(Constants.TITLE, Constants.TEXT, random);

                NotificationHandler.scheduleReminder(alertTime, data, tag);
            }
        });
    }

    private Data createWorkInputData(String title, String text, int id){
        return new Data.Builder()
                .putString(Constants.EXTRA_TITLE, title)
                .putString(Constants.EXTRA_TEXT, text)
                .putInt(Constants.EXTRA_ID, id)
                .build();
    }

    private long getAlertTime(int userInput){
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.MINUTE, userInput);
        return cal.getTimeInMillis();
    }

    private String generateKey(){
        return UUID.randomUUID().toString();
    }
}

5. Create a Constants.Java

Like previously, we will create a new java class and name it Constants.Java. This class will store all our constant variables.

Open the created class and paste the code below inside the class.

package com.inducesmile.workmanager.helper;

public class Constants {

    private Constants() {
    }

    public static final String EXTRA_TITLE = "title";
    public static final String EXTRA_TEXT = "text";
    public static final String EXTRA_ID = "id";

    public static final String TITLE = "Sleeping time";
    public static final String TEXT = "Hello, this is to reminder of your sleeping time";
}

Run Your App Now

If you have gotten this far, it is time to test your application and check that everything works as expected.

You can also download the complete source code in our Github repository here.

If you have any questions or suggestions kindly use the comment box below or contact us using the contact page.

 

 

Add a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.