Skip to content

Instantly share code, notes, and snippets.

@ubarua123
Last active November 20, 2021 07:38
Show Gist options
  • Save ubarua123/ef719ef2bd136c176c2f351db08e8d0b to your computer and use it in GitHub Desktop.
Save ubarua123/ef719ef2bd136c176c2f351db08e8d0b to your computer and use it in GitHub Desktop.
[Android] WorkManager Builder Utility Class - Kotlin
/**
* Copyright 2021
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import android.content.Context
import android.net.Uri
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ExistingWorkPolicy
import androidx.work.ListenableWorker
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.Operation
import androidx.work.PeriodicWorkRequest
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkContinuation
import androidx.work.WorkManager
import androidx.work.WorkRequest
import java.time.Duration
import java.util.concurrent.TimeUnit
/**
* Utility method to construct a constrains builder.
*
* Properties:
* * [NetworkType]
* * requiresCharging
* * requiresStorageNotLow
* * requiresBatteryNotLow
* * requiresDeviceIdle
* * contentUriTriggers
* * triggerForDescendants
*
* @param block
* @return
*/
fun constraintsBuilder(block: ConstraintBuilder.() -> Unit): Constraints = ConstraintBuilder().apply(block).build()
val NETWORK_CONSTRAINT = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
class ConstraintBuilder {
var networkType: NetworkType? = null
var requiresCharging: Boolean = false
var requiresStorageNotLow: Boolean = false
var requiresBatteryNotLow: Boolean = false
var requiresDeviceIdle: Boolean = false
var contentUriTriggers: Uri? = null
var triggerForDescendants: Boolean = false
@RequiresApi(Build.VERSION_CODES.N)
var contentMaxDelay: Long = -1
@RequiresApi(Build.VERSION_CODES.N)
var contentDelay: Long = -1
var timeUnit: TimeUnit = TimeUnit.MILLISECONDS
fun build(): Constraints {
val builder = Constraints.Builder()
networkType?.let { builder.setRequiredNetworkType(it) }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setTriggerContentMaxDelay(contentMaxDelay, timeUnit)
builder.setTriggerContentUpdateDelay(contentDelay, timeUnit)
contentUriTriggers?.let { builder.addContentUriTrigger(it, triggerForDescendants) }
}
return builder.setRequiresCharging(requiresCharging)
.setRequiresBatteryNotLow(requiresBatteryNotLow)
.setRequiresDeviceIdle(requiresDeviceIdle)
.setRequiresStorageNotLow(requiresStorageNotLow).build()
}
}
/** Enquue a [OneTimeWorkRequest]. Use [OneTimeRequestBuilder] to construct a request in an Idiomatic Way */
fun enqueueGeneralWork(context: Context, oneTimeWorkRequest: OneTimeWorkRequest) {
WorkManager.getInstance(context).enqueue(oneTimeWorkRequest)
}
fun enqueMultipleRequests(context: Context, oneTimeWorkRequest: List<OneTimeWorkRequest>) {
WorkManager.getInstance(context).enqueue(oneTimeWorkRequest)
}
/**
* Enqueues a [OneTimeWorkRequest] as a unique work. Use one of the builders to construct a work request.
* @see OneTimeRequestBuilder
*/
fun enqueueUniqueWork(context: Context, uniqueWork: UniqueWork): Operation {
return WorkManager.getInstance(context).enqueueUniqueWork(uniqueWork.uniqueWorkName, uniqueWork.existingWorkPolicy, uniqueWork.workRequest as OneTimeWorkRequest)
}
fun beginUniqueWork(context: Context, uniqueWork: UniqueWork): WorkContinuation {
return WorkManager.getInstance(context).beginUniqueWork(uniqueWork.uniqueWorkName, uniqueWork.existingWorkPolicy, uniqueWork.workRequest as OneTimeWorkRequest)
}
/** Enqueue a PeriodicWork. All periodic works are Unique hence [UniqueWork]
*
* Usage
*
* ```
* enqueuePeriodicWork(applicationContext, uniqueWorkBuilder {
*
* this.workRequest = periodicWorkRequestBuilder<WorkerClass> {
* this.backOffPolicy = getDefaultBackOffPolicy()
*
* constraints = constraintsBuilder {
* networkType = NetworkType.CONNECTED
* initialDelayDuration = Duration.ofMinutes(1)
* }
*
* this.periodicExistingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.REPLACE
*
* this.data = dataBuilder {
* putInt("param1", 123)
* }
* }
* this.uniqueWorkTag = "work-tag-1"
* })
* ```
* @see [UniqueWorkBuilder] */
fun enqueuePeriodicWork(context: Context, uniqueWork: UniqueWork) {
WorkManager.getInstance(context).enqueueUniquePeriodicWork(uniqueWork.uniqueWorkName, uniqueWork.periodicWorkPolicy, uniqueWork.workRequest as PeriodicWorkRequest)
}
/** Constructs a [UniqueWorkBuilder].
* Parameters:
* ```
* existingWorkPolicy
* workRequest - [WorkRequest] (One time work request, Perodic Work Request etc)
* periodicExistingPeriodicWorkPolicy
* ```
*/
fun uniqueWorkBuilder(uniqueWorkName: String, block: UniqueWorkBuilder.() -> Unit): UniqueWork = UniqueWorkBuilder(uniqueWorkName).apply(block).build()
/**
* Helper class to build a [UniqueWork]
* Parameters:
* existingWorkPolicy
* workRequest - [WorkRequest] (One time work request, Perodic Work Request etc)
* periodicExistingPeriodicWorkPolicy
*/
class UniqueWorkBuilder constructor(val uniqueWorkName: String) {
/** Set an [ExistingWorkPolicy] */
var existingWorkPolicy: ExistingWorkPolicy = ExistingWorkPolicy.REPLACE
/** The [WorkRequest] object. Use [OneTimeWorkRequestBuilder] or [PeriodicWorkRequestBuilder]
* Note : Mandatory Field
*/
var workRequest: WorkRequest? = null
/** if its a periodic task, then set an [ExistingPeriodicWorkPolicy] */
var periodicExistingPeriodicWorkPolicy: ExistingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.REPLACE
fun build(): UniqueWork {
return UniqueWork(uniqueWorkName, existingWorkPolicy, periodicExistingPeriodicWorkPolicy, workRequest!!)
}
}
/** Data class to pass Work params for a unique task */
data class UniqueWork(val uniqueWorkName: String,
val existingWorkPolicy: ExistingWorkPolicy = ExistingWorkPolicy.REPLACE,
var periodicWorkPolicy: ExistingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.REPLACE,
val workRequest: WorkRequest)
/** Handy lamba type builder method to construct a [OneTimeWorkRequest]
*
* ```
* Fields
* * initialDelay
* * constraints
* * data
* * tag
* * backOffPolicy
* ```
* */
inline fun <reified T : ListenableWorker> oneTimeWorkRequestBuilder(block: OneTimeRequestBuilder<T>.() -> Unit): OneTimeWorkRequest {
return with(OneTimeRequestBuilder<T>()) {
workerClass = T::class.java
apply(block)
build()
}
}
/** [OneTimeWorkRequest] builder helper class in a kotlin idiomatic way */
class OneTimeRequestBuilder<T : ListenableWorker> {
/** Initiak Delay in milliseconds */
var initialDelay: Long = 0
/** Work constraints */
var constraints: Constraints? = null
/** [Data] */
var data: Data? = null
lateinit var workerClass: Class<T>
var tag: String? = null
var backOffPolicy: BackOffPolicyPoko? = null
@RequiresApi(Build.VERSION_CODES.O)
var initialDelayDuration: Duration? = Duration.ZERO
fun build(): OneTimeWorkRequest {
val builder = OneTimeWorkRequest.Builder(workerClass)
constraints?.let { builder.setConstraints(it) }
data?.let { builder.setInputData(it) }
tag?.let { builder.addTag(it) }
backOffPolicy?.let {
builder.setBackoffCriteria(it.backoffPolicy, it.delay, it.timeUnit)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && initialDelayDuration != null)
builder.setInitialDelay(initialDelayDuration!!)
else
builder.setInitialDelay(initialDelay, TimeUnit.MILLISECONDS)
return builder.build()
}
}
/** Handy lamba type builder method to construct a [PeriodicWorkRequest] */
inline fun <reified T : ListenableWorker> periodicWorkRequestBuilder(block: PeriodicRequestBuilder<T>.() -> Unit): PeriodicWorkRequest {
return with(PeriodicRequestBuilder<T>()) {
workerClass = T::class.java
apply(block)
build()
}
}
inline fun <reified T : ListenableWorker> periodicWorkRequestBuilderWithFlex(block: PeriodicRequestBuilder<T>.() -> Unit): PeriodicWorkRequest {
return with(PeriodicRequestBuilder<T>()) {
workerClass = T::class.java
apply(block)
buildWithFlexInterval()
}
}
@RequiresApi(Build.VERSION_CODES.O)
inline fun <reified T : ListenableWorker> periodicWorkRequestBuilderDuration(block: PeriodicRequestBuilder<T>.() -> Unit): PeriodicWorkRequest {
return with(PeriodicRequestBuilder<T>()) {
workerClass = T::class.java
apply(block)
buildDuration()
}
}
@RequiresApi(Build.VERSION_CODES.O)
inline fun <reified T : ListenableWorker> periodicWorkRequestBuilderWithFlexAndDuration(block: PeriodicRequestBuilder<T>.() -> Unit): PeriodicWorkRequest {
return with(PeriodicRequestBuilder<T>()) {
workerClass = T::class.java
apply(block)
buildWithDurationAndFlexInterval()
}
}
class PeriodicRequestBuilder<T : ListenableWorker> {
/** Initiak Delay in milliseconds */
var initialDelay: Long = 0
@RequiresApi(Build.VERSION_CODES.O)
var initialDelayDuration: Duration? = Duration.ZERO
/** Work constraints */
var constraints: Constraints? = null
/** [Data] */
var data: Data? = null
lateinit var workerClass: Class<T>
var tag: String? = null
var backOffPolicy: BackOffPolicyPoko? = null
var repeatInterval: Long = 0
/** Common [TimeUnit] for
* * RepeatInterval
* * Flex Interval
*/
var timeUnit: TimeUnit = TimeUnit.MILLISECONDS
var flexInterval: Long = 0
@RequiresApi(Build.VERSION_CODES.O)
var repeatIntervalDuration: Duration? = Duration.ZERO
@RequiresApi(Build.VERSION_CODES.O)
var flexIntervalDuration: Duration? = null
fun build(): PeriodicWorkRequest {
val builder = PeriodicWorkRequest.Builder(workerClass, repeatInterval, timeUnit)
setValues(builder)
return builder.build()
}
@RequiresApi(Build.VERSION_CODES.O)
fun buildDuration(): PeriodicWorkRequest {
val builder = PeriodicWorkRequest.Builder(workerClass, repeatIntervalDuration!!)
return builder.build()
}
fun buildWithFlexInterval(): PeriodicWorkRequest {
val builder = PeriodicWorkRequest.Builder(workerClass, repeatInterval, timeUnit, flexInterval, timeUnit)
setValues(builder)
return builder.build()
}
@RequiresApi(Build.VERSION_CODES.O)
fun buildWithDurationAndFlexInterval(): PeriodicWorkRequest {
val builder = PeriodicWorkRequest.Builder(workerClass, repeatIntervalDuration!!, flexIntervalDuration!!)
setValues(builder)
return builder.build()
}
private fun setValues(builder: PeriodicWorkRequest.Builder) {
constraints?.let { builder.setConstraints(it) }
data?.let { builder.setInputData(it) }
tag?.let { builder.addTag(it) }
backOffPolicy?.let {
builder.setBackoffCriteria(it.backoffPolicy, it.delay, it.timeUnit)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && initialDelayDuration != null)
builder.setInitialDelay(initialDelayDuration!!)
else
builder.setInitialDelay(initialDelay, TimeUnit.MILLISECONDS)
}
}
/** Helper method to prepare a [Data] object
*
* Usage:
* ```
* dataBuilder {
* putInt("param1", 24)
* putString("param2", "Name")
* .
* .
* .
* }
*
*/
inline fun dataBuilder(block: Data.Builder.() -> Unit): Data = Data.Builder().apply(block).build()
/** Prepare a [BackOffPolicyPoko] via this builder to apply to [OneTimeRequestBuilder] */
inline fun backOffPolicyBuilder(block: BackOffPolicyBuilder.() -> Unit): BackOffPolicyPoko = BackOffPolicyBuilder().apply(block).build()
/**
* Prepare a [BackOffPolicyPoko] With Default settings
* * [TimeUnit] = MILLISECONDS
* * Delay = OneTimeWorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS = 30,0000
* * [BackoffPolicy] = [BackoffPolicy.LINEAR]
*/
fun getDefaultBackOffPolicy(): BackOffPolicyPoko = BackOffPolicyBuilder().default()
class BackOffPolicyBuilder {
var backoffPolicy: BackoffPolicy = BackoffPolicy.LINEAR
/** Delay in Miliseconds */
var delay: Long = OneTimeWorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS
var timeUnit: TimeUnit = TimeUnit.MILLISECONDS
fun default(): BackOffPolicyPoko {
return BackOffPolicyPoko()
}
fun build(): BackOffPolicyPoko {
return BackOffPolicyPoko(backoffPolicy, delay, timeUnit)
}
}
/** Plain 'Ol Kotlin Object (Poko) for storing [BackoffPolicy] */
data class BackOffPolicyPoko(val backoffPolicy: BackoffPolicy = BackoffPolicy.LINEAR,
val delay: Long = OneTimeWorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS,
val timeUnit: TimeUnit = TimeUnit.MILLISECONDS)
@ubarua123
Copy link
Author

Sample Usage:
1.

enqueuePeriodicWork(context, uniqueWorkBuilder(uniqueWorkName = "database-cleaner") {
            workRequest = periodicWorkRequestBuilder<WorkerClass> {
                constraints = constraintsBuilder {
                    requiresBatteryNotLow = true
                }
                tag = "my-fav-tag"
                timeUnit = TimeUnit.DAYS
                repeatInterval = 30 // monthly
            }
            periodicExistingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.KEEP
        })
enqueueGeneralWork(context, oneTimeWorkRequestBuilder<WorkerClass> {
            tag = "SomeTag"
            data = dataBuilder {
                putInt(ID, entityId)
            }
            constraints = constraintsBuilder {
                networkType = NetworkType.CONNECTED
            }
        })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment