Android JetPack - Scheduling Tasks with WorkManager


Introduction:

WorkManager is one of the Android JetPack component. It is specifically designed for scheduling and managing background tasks. If the app is running, task will be executed in a new thread. Otherwise, WorkManager automatically chooses an appropriate way to do so. It can be AlarmManager, JobScheduler or Firebase JobDispatcher, depends on device's API level and its dependencies.

WorkManager Basics:

  • Worker
  • WorkRequest
  • WorkManager
  • WorkInfo

(i) Worker:

It specifies what task need to be performed.


class SampleWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {

    override fun doWork(): ListenableWorker.Result {
        //do the work here
        return ListenableWorker.Result.SUCCESS
    }
}

(ii) WorkRequest:

It represents which Worker should perform the task. Also we can specify any constraints/circumstances, based on which the task should be performed. Constraints can be of NetworkType, charging status, battery status, etc.

Two kinds of WorkRequest are there:

    (a) OneTimeWorkRequest - Non-repeating work request. 


val requestBuilder = OneTimeWorkRequest.Builder(SampleWorker::class.java).build()

    (b) PeriodicWorkRequest - WorkRequest that repeats periodically.


val requestBuilder = PeriodicWorkRequest.Builder(SampleWorker::class.java, 24, TimeUnit.HOURS)

(iii) WorkManager:

It enqueues and manages the work requests.


WorkManager.getInstance().enqueue(requestBuilder)

We can also enqueue WorkRequest as unique, so that the same WorkRequest will not be assigned again and again.

val workTag = "workTag"
WorkManager.getInstance().beginUniqueWork(workTag, ExistingWorkPolicy.REPLACE, requestBuilder).enqueue()

(iv) WorkInfo:

It contains information about a particular task. We can get the WorkInfo of every WorkRequest, by observing LiveData.


 WorkManager.getInstance().getStatusByIdLiveData(requestBuilder.id).observe(this@DataActivity, android.arch.lifecycle.Observer { workerStatus ->
                if (workerStatus != null && workerStatus.state.isFinished) {
                    Toast.makeText(this@DataActivity, workerStatus.outputData.getString(Constants.EXTRA_OUTPUT_MESSAGE), Toast.LENGTH_SHORT).show()
                }

            })

You can find the Project on Github.

Implementation:

Let's first add WorkManager in our app-level gradle.

 implementation "android.arch.work:work-runtime-ktx:1.0.0-alpha10"

build.gradle

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-kapt'


android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.yamikrish.app.workmanagerdemo"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
// Temporary fix until alpha10
    packagingOptions {
        exclude 'META-INF/proguard/androidx-annotations.pro'
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation "android.arch.work:work-runtime-ktx:1.0.0-alpha10"
    implementation "androidx.lifecycle:lifecycle-livedata:2.0.0"
    kapt "androidx.lifecycle:lifecycle-compiler:2.0.0"
 

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

Let's see about WorkManager in detail with example. So first, let's create an activity, where we can have no. of options to check each kind.

BaseActivity.kt

It have 4 buttons for showing SingleTask, PeriodicTask, ChainedTask and Constraints & Data Task.

import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import com.yamikrish.app.workmanagerdemo.R
import com.yamikrish.app.workmanagerdemo.activity.ChainTaskActivity
import com.yamikrish.app.workmanagerdemo.activity.ConstraintsDataActivity
import com.yamikrish.app.workmanagerdemo.activity.PeriodicTaskActivity
import com.yamikrish.app.workmanagerdemo.activity.SingleTaskActivity
import kotlinx.android.synthetic.main.activity_main.*

class BaseActivity : AppCompatActivity(), View.OnClickListener {
    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.btn_periodic_task -> startActivity(Intent(this@BaseActivity, PeriodicTaskActivity::class.java))

            R.id.btn_single_task -> startActivity(Intent(this@BaseActivity, SingleTaskActivity::class.java))

            R.id.btn_recurring_task -> startActivity(Intent(this@BaseActivity, ChainTaskActivity::class.java))

            R.id.btn_constraints_data -> startActivity(Intent(this@BaseActivity, ConstraintsDataActivity::class.java))


        }
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn_periodic_task.setOnClickListener(this)
        btn_single_task.setOnClickListener(this)
        btn_recurring_task.setOnClickListener(this)
        btn_constraints_data.setOnClickListener(this)
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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">

    <Button
        android:id="@+id/btn_single_task"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/single_task"
        app:layout_constraintBottom_toTopOf="@+id/btn_periodic_task"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_periodic_task"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/periodic_task"
        app:layout_constraintBottom_toTopOf="@+id/btn_recurring_task"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_single_task" />


    <Button
        android:id="@+id/btn_recurring_task"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/chain_task"
        app:layout_constraintBottom_toTopOf="@+id/btn_constraints_data"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_periodic_task" />


    <Button
        android:id="@+id/btn_constraints_data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/constraints"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_recurring_task" />


</android.support.constraint.ConstraintLayout>



(i) Simple Work:

Lets begin with simple one ;) We can create a Worker, which will send a push-notification after starts. So, create an activity with one button to start the Worker. Once the button is clicked, we can enqueue the WorkRequest (sending pushnotification worker) to WorkManager.

We can also delay the start of the task with the help of setInitialDelay() method.

setInitialDelay(10, TimeUnit.MINUTES)

SingleTaskActivity.kt

import android.os.Bundle
import android.support.annotation.Nullable
import android.support.v7.app.AppCompatActivity
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.yamikrish.app.workmanagerdemo.worker.SingleWorker
import com.yamikrish.app.workmanagerdemo.R
import kotlinx.android.synthetic.main.activity_start_task.*


class SingleTaskActivity : AppCompatActivity() {

    private lateinit var workManager: WorkManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_start_task)

        supportActionBar?.apply {
            setHomeButtonEnabled(true)
            setDisplayHomeAsUpEnabled(true)
            title = getString(R.string.single_task)
        }

        workManager = WorkManager.getInstance()

        start_single_task.setOnClickListener {
            val requestBuilder = OneTimeWorkRequest.Builder(SingleWorker::class.java).build()
            workManager.enqueue(requestBuilder)
        }
    }

    override fun onSupportNavigateUp(): Boolean {
        finish()
        return super.onSupportNavigateUp()
    }

    override fun onBackPressed() {
        finish()
        super.onBackPressed()
    }
}

SingleWorker.kt

This Worker class is to create and send notifications in the app.

import android.R
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.support.v4.app.NotificationCompat
import androidx.work.ListenableWorker
import androidx.work.Worker
import androidx.work.WorkerParameters

class SingleWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
    val mContext = context
    override fun doWork(): ListenableWorker.Result {
        sendNotification()
        return ListenableWorker.Result.SUCCESS
    }

    fun sendNotification() {
        val notificationManager = mContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val channelId = "WorkManager_00"
        //If on Oreo then notification required a notification channel.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, "WorkManager", NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager.createNotificationChannel(channel)
        }

        val notification = NotificationCompat.Builder(mContext, channelId)
                .setContentTitle("Single Worker")
                .setContentText("This notification is from Single Worker!!")
                .setSmallIcon(R.drawable.ic_lock_idle_alarm)

        notificationManager.notify(1, notification.build())
    }
}

activity_start_task.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <Button
        android:id="@+id/start_single_task"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/start"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</android.support.constraint.ConstraintLayout>


 


(ii) Periodic Tasks:

PeriodicTaskActivity.kt

Next, we will see about PeriodicWorker. PeriodicWorker can schedule a task to be performed, for every specified time. It can be of MINUTES, HOURS or DAY. Time should be minimum of 15 minutes.

Let's create a Worker to send notification every 15 mins.

import android.content.Context
import android.content.SharedPreferences
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import androidx.work.*
import com.yamikrish.app.workmanagerdemo.worker.periodicWorker.NotifyWorker
import kotlinx.android.synthetic.main.activity_periodic_notifier.*
import java.util.concurrent.TimeUnit
import com.yamikrish.app.workmanagerdemo.R
import com.yamikrish.app.workmanagerdemo.worker.periodicWorker.NotifyInitialWorker

class PeriodicTaskActivity : AppCompatActivity() {

    private lateinit var workManager: WorkManager
    private val workTag = "starterNotifiWork"

    private lateinit var prefs: SharedPreferences
    private lateinit var editor: SharedPreferences.Editor
    private val NOTIFICATION_STATUS = "notification_status"
    private lateinit var requestBuilder: OneTimeWorkRequest

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_periodic_notifier)

        supportActionBar?.apply {
            setHomeButtonEnabled(true)
            setDisplayHomeAsUpEnabled(true)
            title = getString(R.string.single_task)
        }

        prefs = getPreferences(Context.MODE_PRIVATE)
        editor = prefs.edit()

        workManager = WorkManager.getInstance()


        remindSwitch.isChecked = prefs.getBoolean(NOTIFICATION_STATUS, false)

        remindSwitch.setOnClickListener {
            if (remindSwitch.isChecked) {
                val requestBuilder = PeriodicWorkRequest.Builder(NotifyWorker::class.java, 15, TimeUnit.MINUTES)
                workManager.enqueueUniquePeriodicWork(workTag, ExistingPeriodicWorkPolicy.REPLACE, requestBuilder.build())
            } else {
                workManager.cancelUniqueWork(workTag)
            }
            editor.putBoolean(NOTIFICATION_STATUS, remindSwitch.isChecked)
            editor.apply()
        }     

    override fun onSupportNavigateUp(): Boolean {
        finish()
        return super.onSupportNavigateUp()
    }

    override fun onBackPressed() {
        finish()
        super.onBackPressed()
    }
}


NotifyWorker.kt


import android.util.Log
import androidx.work.Worker
import android.support.v4.app.NotificationCompat
import android.app.NotificationManager
import android.app.NotificationChannel
import android.content.Context
import android.content.Context.NOTIFICATION_SERVICE
import android.R;
import androidx.work.ListenableWorker
import androidx.work.WorkerParameters
import android.support.annotation.NonNull


class NotifyWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {

    val mContext = context

    override fun doWork(): ListenableWorker.Result {
        sendNotification()
        return ListenableWorker.Result.SUCCESS
    }

    fun sendNotification() {
        val notificationManager = mContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val channelId = "WorkManager_01"

        //If on Oreo then notification required a notification channel.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, "WorkManager", NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager.createNotificationChannel(channel)
        }

        val notification = NotificationCompat.Builder(mContext, channelId)
                .setContentTitle("Timer Reminder")
                .setContentText("It's been 15 minutes!!!")
                .setSmallIcon(R.drawable.ic_lock_idle_alarm)

        notificationManager.notify(2, notification.build())
    }
}

The above works fine at scheduling notification every 15 mins, even when the app is not in foreground or background state, also after device restart.

But one issue I encountered is, we can't set initial delay for PeriodicRequest, so that the task is sending notification while starting itself. It's not the correct way of doing it, know? (What to do??!!!) 

Let's use some magic.. Nah..! Logic!!.

First we can create a OneTimeRequest with intialDelay of 15 minutes, then in that worker's doWork(), we can schedule the periodic tasks to show notification. Problem solved!!

PeriodicTaskActivity.kt

import android.content.Context
import android.content.SharedPreferences
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import androidx.work.*
import com.yamikrish.app.workmanagerdemo.worker.periodicWorker.NotifyWorker
import kotlinx.android.synthetic.main.activity_periodic_notifier.*
import java.util.concurrent.TimeUnit
import com.yamikrish.app.workmanagerdemo.R
import com.yamikrish.app.workmanagerdemo.worker.periodicWorker.NotifyInitialWorker

class PeriodicTaskActivity : AppCompatActivity() {

    private lateinit var workManager: WorkManager
    private val workTag = "starterNotifiWork"

    private lateinit var prefs: SharedPreferences
    private lateinit var editor: SharedPreferences.Editor
    private val NOTIFICATION_STATUS = "notification_status"
    private lateinit var requestBuilder: OneTimeWorkRequest

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_periodic_notifier)

        supportActionBar?.apply {
            setHomeButtonEnabled(true)
            setDisplayHomeAsUpEnabled(true)
            title = getString(R.string.single_task)
        }

        prefs = getPreferences(Context.MODE_PRIVATE)
        editor = prefs.edit()

        workManager = WorkManager.getInstance()


        remindSwitch.isChecked = prefs.getBoolean(NOTIFICATION_STATUS, false)

        remindSwitch.setOnClickListener {
            if (remindSwitch.isChecked) {
                requestBuilder = OneTimeWorkRequest.Builder(NotifyInitialWorker::class.java)
                        .setInitialDelay(1, TimeUnit.MINUTES)
                        .build()
                workManager.getStatusByIdLiveData(requestBuilder.id).observe(this@PeriodicTaskActivity, android.arch.lifecycle.Observer { workerStatus ->
                    if (workerStatus != null && workerStatus.state.isFinished) {
                        startNotifyWorker()
                    }

                })
                workManager.beginUniqueWork(workTag, ExistingWorkPolicy.REPLACE, requestBuilder).enqueue()
            } else {
                workManager.cancelAllWork()
            }
            editor.putBoolean(NOTIFICATION_STATUS, remindSwitch.isChecked)
            editor.apply()
        }

    }
    /** Schedule PeriodicWorkRequest
    private fun startNotifyWorker() {
        val requestBuilder = PeriodicWorkRequest.Builder(NotifyWorker::class.java, 15, TimeUnit.MINUTES)
        WorkManager.getInstance().enqueueUniquePeriodicWork(workTag, ExistingPeriodicWorkPolicy.REPLACE, requestBuilder.build())
    }

    override fun onSupportNavigateUp(): Boolean {
        finish()
        return super.onSupportNavigateUp()
    }

    override fun onBackPressed() {
        finish()
        super.onBackPressed()
    }
}


NotifyInitialWorker.kt


import android.content.Context
import androidx.work.*

class NotifyInitialWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {

    override fun doWork(): ListenableWorker.Result {
        return ListenableWorker.Result.SUCCESS
    }

}

activity_periodic_notifier.xml


<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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=".activity.PeriodicTaskActivity">

    <TextView
        android:id="@+id/infoText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Remind me every 15 minutes"
        android:padding="10dp"
        android:textAppearance="@android:style/TextAppearance.Medium"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Switch
        android:id="@+id/remindSwitch"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        android:layout_margin="10dp"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

 

(iii) Chained Tasks

WorkManager can also schedule series of task to be executed in serial or parallel. Lets create three workers to send notifications.

To enqueue it serially,

WorkManager.getInstance().beginWith(request1).then(request2).then(request3).enqueue()

To enqueue it parallelly,

WorkManager.getInstance().beginWith(request1).then(request2, request3).enqueue()

Here, we can setInitialDelay to workers to exactly find out how it works.

ChainTaskActivity.kt

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.yamikrish.app.workmanagerdemo.worker.chainWorker.FirstWorker
import com.yamikrish.app.workmanagerdemo.worker.chainWorker.SecondWorker
import com.yamikrish.app.workmanagerdemo.worker.chainWorker.ThirdWorker
import kotlinx.android.synthetic.main.activity_start_task.*
import com.yamikrish.app.workmanagerdemo.R
import java.util.concurrent.TimeUnit


class ChainTaskActivity : AppCompatActivity() {

    private lateinit var workManager: WorkManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_start_task)

        supportActionBar?.apply {
            setHomeButtonEnabled(true)
            setDisplayHomeAsUpEnabled(true)
            title = getString(R.string.single_task)
        }

        workManager = WorkManager.getInstance()


        start_single_task.setOnClickListener {
            val request1 = OneTimeWorkRequest.Builder(FirstWorker::class.java).build()
            val request2 = OneTimeWorkRequest.Builder(SecondWorker::class.java).setInitialDelay(30, TimeUnit.SECONDS).build()
            val request3 = OneTimeWorkRequest.Builder(ThirdWorker::class.java).setInitialDelay(30, TimeUnit.SECONDS).build()
  
       workManager.beginWith(request1).then(request2).then(request3).enqueue() 

        }
    }

    override fun onSupportNavigateUp(): Boolean {
        finish()
        return super.onSupportNavigateUp()
    }

    override fun onBackPressed() {
        finish()
        super.onBackPressed()
    }
}

We can also make requests run parrallelly, by queuing like below:


workManager.beginWith(request1).then(request2, request3).enqueue()

FirstWorker.kt


import android.R
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.support.v4.app.NotificationCompat
import androidx.work.ListenableWorker
import androidx.work.Worker
import androidx.work.WorkerParameters

class FirstWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {

    val mContext = context

    override fun doWork(): ListenableWorker.Result {
        sendNotification()
        return ListenableWorker.Result.SUCCESS
    }

    fun sendNotification() {
        val notificationManager = mContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val channelId = "WorkManager_02"

        //If on Oreo then notification required a notification channel.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, "WorkManager", NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager.createNotificationChannel(channel)
        }

        val notification = NotificationCompat.Builder(mContext, channelId)
                .setContentTitle("WorkManager")
                .setContentText("Message from First Worker!!")
                .setSmallIcon(R.drawable.ic_lock_idle_alarm)

        notificationManager.notify(3, notification.build())
    }

}

SecondWorker.kt

import android.R
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.support.v4.app.NotificationCompat
import androidx.work.ListenableWorker
import androidx.work.Worker
import androidx.work.WorkerParameters

class SecondWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {

    val mContext = context

    override fun doWork(): ListenableWorker.Result {
        sendNotification()
        return ListenableWorker.Result.SUCCESS
    }

    fun sendNotification() {
        val notificationManager = mContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val channelId = "WorkManager_03"

        //If on Oreo then notification required a notification channel.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, "WorkManager", NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager.createNotificationChannel(channel)
        }

        val notification = NotificationCompat.Builder(mContext, channelId)
                .setContentTitle("WorkManager")
                .setContentText("Message from Second Worker!!")
                .setSmallIcon(R.drawable.ic_lock_idle_alarm)

        notificationManager.notify(4, notification.build())
    }

}

ThirdWorker.kt

import android.R
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.support.v4.app.NotificationCompat
import androidx.work.ListenableWorker
import androidx.work.Worker
import androidx.work.WorkerParameters


class ThirdWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {

    val mContext = context

    override fun doWork(): ListenableWorker.Result {
        sendNotification()
        return ListenableWorker.Result.SUCCESS
    }

    fun sendNotification() {
        val notificationManager = mContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val channelId = "WorkManager_04"

        //If on Oreo then notification required a notification channel.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, "WorkManager", NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager.createNotificationChannel(channel)
        }

        val notification = NotificationCompat.Builder(mContext, channelId)
                .setContentTitle("WorkManager")
                .setContentText("Message from Third Worker!!")
                .setSmallIcon(R.drawable.ic_lock_idle_alarm)

        notificationManager.notify(5, notification.build())
    }

}

 

(iv) Constraints & Data

Now, let's see how to set constraints and how to send and receive data to/from Worker. We can easily set constraints to the Worker, by using,

val constraints = Constraints.Builder().setRequiresCharging(true).build()

We can send data to Worker and also can receive data from Worker.
To send data:

val data = Data.Builder()
                .putString(Constants.EXTRA_TITLE, "Title")
                .putString(Constants.EXTRA_MESSAGE, "Message sent!!")
                .build()


 val requestBuilder = OneTimeWorkRequest.Builder(MyWorker::class.java)
                    .setInputData(data)
                    .build()

To receive data:

title = inputData.getString(Constants.EXTRA_TITLE)
message = inputData.getString(Constants.EXTRA_MESSAGE)

We can send OutputData from Worker to Activity/Fragment as well. We have to set OutputData before success return, ListenableWorker.Result.SUCCESS.

val output = Data.Builder()
                .putString(EXTRA_OUTPUT_MESSAGE, "Message from Worker!")
                .build()
outputData = output

ConstraintDataActivity.kt


In this class, let's send Data from Activity to Worker and send notification with that data. Once done, send OutputData back to the called Activity. To receive that output data, that Activity can listen by using LiveData.

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.Toast
import androidx.work.Constraints
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.yamikrish.app.workmanagerdemo.utils.Constants
import com.yamikrish.app.workmanagerdemo.worker.constraintDataWorker.ConstraintWorker
import kotlinx.android.synthetic.main.activity_start_task.*
import com.yamikrish.app.workmanagerdemo.R


class ConstraintsDataActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_start_task)

        supportActionBar?.apply {
            setHomeButtonEnabled(true)
            setDisplayHomeAsUpEnabled(true)
            title = getString(R.string.single_task)
        }

        val constraints = Constraints.Builder().setRequiresCharging(true).build()

        val data = Data.Builder()
                .putString(Constants.EXTRA_TITLE, "Message!!")
                .putString(Constants.EXTRA_MESSAGE, "Message sent from Activity")
                .build()

        start_single_task.setOnClickListener {
            val requestBuilder = OneTimeWorkRequest.Builder(ConstraintWorker::class.java)
                    .setConstraints(constraints)
                    .setInputData(data)
                    .build()
            WorkManager.getInstance().enqueue(requestBuilder)

            WorkManager.getInstance().getStatusByIdLiveData(requestBuilder.id).observe(this@ConstraintsDataActivity, android.arch.lifecycle.Observer { workerStatus ->
                if (workerStatus != null && workerStatus.state.isFinished) {
                    Toast.makeText(this@ConstraintsDataActivity, workerStatus.outputData.getString(Constants.EXTRA_OUTPUT_MESSAGE), Toast.LENGTH_SHORT).show()
                }

            })
        }
    }

    override fun onSupportNavigateUp(): Boolean {
        finish()
        return super.onSupportNavigateUp()
    }

    override fun onBackPressed() {
        finish()
        super.onBackPressed()
    }

}

ConstraintWorker.kt


import android.R
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.support.v4.app.NotificationCompat
import androidx.work.Data
import androidx.work.ListenableWorker
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.yamikrish.app.workmanagerdemo.utils.Constants
import com.yamikrish.app.workmanagerdemo.utils.Constants.Companion.EXTRA_OUTPUT_MESSAGE

class ConstraintWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {

    val mContext = context
    var title: String? = ""
    var message: String? = ""

    override fun doWork(): ListenableWorker.Result {
        title = inputData.getString(Constants.EXTRA_TITLE)
        message = inputData.getString(Constants.EXTRA_MESSAGE)

        sendNotification()

        val output = Data.Builder()
                .putString(EXTRA_OUTPUT_MESSAGE, "Message from ConstraintWorker!")
                .build()

        outputData = output
        return ListenableWorker.Result.SUCCESS
    }

    fun sendNotification() {
        val notificationManager = mContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val channelId = "WorkManager_05"

        //If on Oreo then notification required a notification channel.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, "WorkManager", NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager.createNotificationChannel(channel)
        }

        val notification = NotificationCompat.Builder(mContext, channelId)
                .setContentTitle(title)
                .setContentText(message)
                .setSmallIcon(R.drawable.ic_lock_idle_alarm)

        notificationManager.notify(6, notification.build())
    }

}

DataWorker.kt


import android.R
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.support.v4.app.NotificationCompat
import androidx.work.ListenableWorker
import androidx.work.Worker
import androidx.work.WorkerParameters

class DataWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {

    val mContext = context

    override fun doWork(): ListenableWorker.Result {
        sendNotification()
        return ListenableWorker.Result.SUCCESS
    }

    fun sendNotification() {
        val notificationManager = mContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val channelId = "WorkManager_06"

        //In Oreo, notification required a notification channel.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, "WorkManager", NotificationManager.IMPORTANCE_DEFAULT)
            notificationManager.createNotificationChannel(channel)
        }

        val notification = NotificationCompat.Builder(mContext, channelId)
                .setContentTitle("WorkManager")
                .setContentText("Message from Data Worker!!")
                .setSmallIcon(R.drawable.ic_lock_idle_alarm)

        notificationManager.notify(7, notification.build())
    }

}

Constants.kt


class Constants {
    companion object {
        val EXTRA_TITLE = "TITLE_TEXT"
        val EXTRA_MESSAGE = "MESSAGE_TEXT"
        val EXTRA_OUTPUT_MESSAGE = "OUTPUT_TEXT"
    }
}



 

That's it for WorkManager. Bubyeeee!!!

Comments

Popular posts from this blog

SOAP Client using ksoap2 in Android - Kotlin

Exploring Android Slices - JetPack

Databinding in RecyclerView - Android - Kotlin

Stripe Integration in Android - Kotlin

Braintree Integration in Android - Kotlin

Exploring Android Navigation Architecture Component - MVVM - Kotlin

Shimmer Layout like Facebook in Android - Kotlin

Map, Location update and AutoComplete Places - Kotlin

Firebase Storage - Upload Files - Kotlin

RecyclerView with different number of columns using SpanSizeLookup