Using work manager’s periodic work for less than 15 minutes
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?