Using work manager’s periodic work for less than 15 minutes

30 / Nov / 2022 by Kuldeep Singh 0 comments

During our development journey, we do face situations where we require to perform some periodic and background tasks.

So, to do so, we have multiple ways, but if it comes to the best way to perform these types of tasks in the context of clean code and optimized device resources usages, then we need to think twice before implementation.

And after considering above mentioned things, we can go with the WorkManager.

We can use WorkManager to trigger the OneTimeWorkRequest and PeriodicWorkRequest as per the requirements. We can create and enqueue multiple requests and can start conditionally.

In this blog, we’ll be covering the OneTimeWorkRequest and use it as a periodic request for less than 15 min.

Why OneTimeWorkRequest as periodic request for 0–14 min?

Because PeriodicWorkRequest allows us to schedule tasks for minimum 15 min but if our requirement is to create it for less time, then what.

Lets code and achieve it

Add the required dependency in app level gradle:

//work manager
implementation 'androidx.work:work-runtime-ktx:2.7.1'

Create a class MyWorker and extend Worker:

package com.myworkmanager

import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters

class MyWorker(appContext: Context, workerParams: WorkerParameters) :
    Worker(appContext, workerParams) {
    /**
     * it will be called after the specified delay
     */
    override fun doWork(): Result {
        Log.i("MyWorker", "doWork")

        //you can notify back to user
        WorkHandler.onDoWork()

        //or if you need to do some background task e.g., upload or download
        //then pass the reference of the required resources in MyWorker()
        doFurtherTask()

        return Result.success()
    }

    /**
     * or if you need to do some background task e.g., upload or download
     * then pass the reference of the required resources in MyWorker()
     */
    private fun doFurtherTask() {
        Log.i("MyWorker", "doFurtherTask")
        // do your background work here if required
    }
}

Create WorkHandler:

package com.myworkmanager

import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.work.Constraints
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit


object WorkHandler {
    private const val tag = "WorkHandler"
    private val workManager = WorkManager.getInstance(MyWorkManagerApplication.appInstance)

    /**
     * create work request
     *
     * @param delay
     */
    fun createWork(delay: Long) {
        val constraints = Constraints.Builder()
            .setRequiresBatteryNotLow(true)
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .build()

        val request = OneTimeWorkRequestBuilder<MyWorker>()

        val work = request.setConstraints(constraints)
            .addTag(tag)
            .setInitialDelay(delay, TimeUnit.MINUTES)
            .build()

        workManager.enqueue(work)

        Log.i(tag, "work created")
    }

    /**
     * do UI related work because this method is supporting foreground work
     * If you have any other requirement then please update the conditions
     */
    fun onDoWork() {
        try {
            val activity = BaseActivity.currentActivityInstance.get()

            if (activity?.lifecycle?.currentState?.isAtLeast(Lifecycle.State.RESUMED) == true) {
                //if current activity is active
                //do UI related work
                Log.i(tag, "app is active, do UI work")

                //recreate the work if required
                Log.i(tag, "recreateWork")

                createWork(WORK_DELAY_IN_MIN)

            } else if (activity?.lifecycle?.currentState?.isAtLeast(Lifecycle.State.DESTROYED) == true) {
                //if current activity is in background for any reason then
                //recreate the work if required
                Log.i(tag, "recreateWork")

                createWork(WORK_DELAY_IN_MIN)

            } else {
                //cancel work if app got killed, if you want to continue then remove this condition
                //and, keep recreate work logic
                Log.i(tag, "safe: cancel work if any")
                cancelWork()
            }

        } catch (ex: Exception) {
            Log.e(tag, ex.toString())
        }
    }

    /**
     * cancel enqueued work by the given tag
     */
    fun cancelWork() {
        workManager.cancelAllWorkByTag(tag)
        
        Log.i(tag, "work canceled")
    }
}

Work initiator MyWorkManagerApplication:

package com.myworkmanager

import android.app.Application

class MyWorkManagerApplication : Application() {

    init {
        appInstance = this
    }

    companion object {
        lateinit var appInstance: MyWorkManagerApplication
    }

    override fun onCreate() {
        super.onCreate()

        //start work on application start
        //or you can start work as per your need
        //e.g., from MainActivity, SplashActivity, via user action, etc.
        WorkHandler.createWork(WORK_DELAY_IN_MIN)
    }
}

Rest of the files to support the complete flow:

package com.myworkmanager

import androidx.appcompat.app.AppCompatActivity
import java.lang.ref.WeakReference

open class BaseActivity : AppCompatActivity() {
    companion object {
        var currentActivityInstance: WeakReference<AppCompatActivity?> =
            WeakReference<AppCompatActivity?>(null)
    }

    override fun onResume() {
        super.onResume()

        //keep current activity instance and use to perform any UI related task on work completion
        currentActivityInstance = WeakReference(this)
    }
}
package com.myworkmanager

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Button

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

        Log.i("MainActivity", "launched")

        findViewById<Button>(R.id.go_to_next_screen_button)?.setOnClickListener {
            startActivity(Intent(this, YourActivity::class.java))
        }

        findViewById<Button>(R.id.cancel_work_button)?.setOnClickListener {
            //cancel on going work anytime
            WorkHandler.cancelWork()
        }
    }
}
Constants.kt

package com.myworkmanager

const val WORK_DELAY_IN_MIN: Long = 7
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">


    <Button
        android:id="@+id/go_to_next_screen_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="157dp"
        android:layout_marginTop="215dp"
        android:layout_marginEnd="160dp"
        android:text="@string/go_to_next_screen"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:ignore="DuplicateSpeakableTextCheck" />

    <Button
        android:id="@+id/cancel_work_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="160dp"
        android:layout_marginTop="91dp"
        android:layout_marginEnd="157dp"
        android:layout_marginBottom="330dp"
        android:text="@string/cancel_work"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/go_to_next_screen_button" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="177dp"
        android:layout_marginTop="36dp"
        android:layout_marginEnd="176dp"
        android:text="@string/screen_one"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

So far, we have looked into the code and achieved the periodic work behavior using OneTimeWorkRequest. We can modify the logic according to our use cases.

During your development, if you find any other use case, please feel free to comment, and we can resolve the issues together.

Read more:  Android Katha: onActivityResult is Deprecated. Now What?

FOUND THIS USEFUL? SHARE IT

Leave a Reply

Your email address will not be published. Required fields are marked *