Slide 1

Slide 1 text

Introduction to WorkManager Bartosz Ostrowski Lead Software Engineer at GlobalLogic GDG Szczecin @bartoszostrowsk

Slide 2

Slide 2 text

What is WorkManager?

Slide 3

Slide 3 text

Android Jetpack Components ● Foundations ● Architecture ● Behaviour ● UI

Slide 4

Slide 4 text

Architecture Components ● DataBinding ● Room ● WorkManager ● LiveData ● Navigation ● Paging ● ViewModel ● Lifecycle

Slide 5

Slide 5 text

What is it for? WorkManager is intended for tasks that are deferrable—that is, not required to run immediately — and required to run reliably even if the app exits or the device restarts.

Slide 6

Slide 6 text

Key features ● Compatibility up to Android 4.0 ● Work constraints: network, battery level, ... ● Scheduling single of recurring tasks ● Chaining tasks ● Monitoring and managing scheduled tasks

Slide 7

Slide 7 text

Sample scenarios ● Google Drive app - one-time worker for sending scan (waits for WiFi connection to sync) ● Language learning apps (e.g. Duolingo or Memrise) - for daily notifications about new words to learn (recurring worker) ● Instagram - chained worker for more complex tasks: take photo, apply filter, upload to server, share on Facebook and/or Twitter

Slide 8

Slide 8 text

Compatibility Backward compatibility up to API 14 ● Devices running API 14-22 : AlarmManager + BroadcastReceiver ● Devices running API 23+: JobScheduler

Slide 9

Slide 9 text

Getting started

Slide 10

Slide 10 text

Dependencies Top-level build.gradle allprojects { repositories { google() jcenter() } }

Slide 11

Slide 11 text

dependencies { def work_version = "2.3.1" // (Java only) implementation "androidx.work:work-runtime:$work_version" // Kotlin + coroutines implementation "androidx.work:work-runtime-ktx:$work_version" // RxJava2 support implementation "androidx.work:work-rxjava2:$work_version" // GCMNetworkManager support implementation "androidx.work:work-gcm:$work_version" // Test helpers androidTestImplementation "androidx.work:work-testing:$work_version" }

Slide 12

Slide 12 text

class SampleWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { // Do sth! return Result.success() } } val request: OneTimeWorkRequest = OneTimeWorkRequest.Builder(SampleWorker::class.java) .build() WorkManager.getInstance(applicationContext).enqueue(request)

Slide 13

Slide 13 text

Supported constraints ● Network connection (by type: connected, metered, unmetered, not required, not roaming) ● Battery not low ● Charging ● Storage not low ● Device idle

Slide 14

Slide 14 text

val constraints = Constraints.Builder() .setRequiresBatteryNotLow(true) .setRequiredNetworkType(NetworkType.UNMETERED) .setRequiresCharging(false) .setRequiresStorageNotLow(true) .setRequiresDeviceIdle(true) .build() val request: OneTimeWorkRequest = OneTimeWorkRequest.Builder(SampleWorker::class.java) .setConstraints(constraints) .build()

Slide 15

Slide 15 text

What if the job fails? Work may finish with the following results: ● SUCCESS ● RETRY ● FAILURE WorkManager has defined back-off policies: ● LINEAR ● EXPONENTIAL

Slide 16

Slide 16 text

// Request builder val retryingRequest = OneTimeWorkRequest.Builder(RetryingWorker::class.java) .setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.MINUTES) .build() WorkManager.getInstance(applicationContext).enqueue(retryingRequest) // Worker override fun doWork(): Result { if (runAttemptCount > 3) { return Result.failure() } return Result.retry() }

Slide 17

Slide 17 text

Repeating task ● Dedicated for regular, cyclic tasks, e.g. database backup ● Can be scheduled with minimal 15 minute frequency ● Cannot be used as a timer. It is not fully deterministic in timely manner.

Slide 18

Slide 18 text

val request = PeriodicWorkRequest .Builder(RecurringWorker::class.java, 4, TimeUnit.HOURS) .build() WorkManager.getInstance(applicationContext) .enqueueUniquePeriodicWork("recurring", ExistingPeriodicWorkPolicy.REPLACE, request) WorkManager.getInstance(applicationContext) .enqueueUniquePeriodicWork("recurring", ExistingPeriodicWorkPolicy.KEEP, request)

Slide 19

Slide 19 text

Old vs. new approach... … for cyclic notifications ● Boot complete broadcast receiver ● App update broadcast receiver ● Manifest update: permissions, receivers ● Maintaining AlarmManager for notifications ● Worker + request builder Old New

Slide 20

Slide 20 text

Chaining work ● Can be scheduled to run in parallel ● Can be schedule to run sequentially, feeding latter worker with a result of the prior task.

Slide 21

Slide 21 text

Proprietary + Confidential Source: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis non erat sem Chaining work

Slide 22

Slide 22 text

WorkManager.getInstance(applicationContext) .beginWith(listOf(blur, noise, glow)) .then(compress) .then(upload) .enqueue()

Slide 23

Slide 23 text

class UploadWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { val input = inputData.getString("value") val time = System.currentTimeMillis() // Fake job for 15 seconds Thread.sleep(15000) val output = Data.Builder() .putString("value", "$input + UPLOAD") .build() Log.d(TAG, "doWork() : Work is done. Took = " + (System.currentTimeMillis() - time) + " ms. Output = " + output.getString("value")) return Result.success(output) }

Slide 24

Slide 24 text

class CompressWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { val input = inputData.getString("value") val time = System.currentTimeMillis() // Fake job for 15 seconds Thread.sleep(15000) val output = Data.Builder() .putString("value", "$input + COMPRESS") .build() Log.d(TAG, "doWork() : Work is done. Took = " + (System.currentTimeMillis() - time) + " ms. Output = " + output.getString("value")) return Result.success(output) }

Slide 25

Slide 25 text

Chaining work... InputMergers ● OverwritingInputMerger ● ArrayCreatingInputMerger

Slide 26

Slide 26 text

val output = Data.Builder() .putString("keyA", "value1") .putInt("keyB", 1) .putString("keyC", "valueC") .build() val output = Data.Builder() .putString("keyA", "value2") .putInt("keyB", 2) .putString("keyD", "valueD") .build() // Result of Worker3 -> {keyA = value1, keyB = 1, keyC = valueC, keyD = valueD} // or // Result of Worker3 -> {keyA = value2, keyB = 2, keyC = valueC, keyD = valueD}

Slide 27

Slide 27 text

val output = Data.Builder() .putString("keyA", "value1") .putInt("keyB", 1) .putString("keyC", "valueC") .build() val output = Data.Builder() .putString("keyA", "value2") .putInt("keyB", 2) .putString("keyD", "valueD") .build() // keyA = [value1, value2] // keyB = [1, 2] // keyC = [valueC] // keyD = [valueD]

Slide 28

Slide 28 text

Further topics to cover ● Testing ● Conjunction with LiveData and RxJava ● Getting statuses of a worker ● Cancelling and stopping workers ● Existing workers - replace, keep, append ● News in WorkManager - e.g. LongWorker

Slide 29

Slide 29 text

Reference ● Android Developer Documentation ● Background work with WorkManager - Codelabs ● WorkManager Basics - Article on Medium ● InputMerger with WorkManager ● https://github.com/bartoszostrowski/WorkManagerExample

Slide 30

Slide 30 text

Thank You! Bartosz Ostrowski Lead Software Engineer at GlobalLogic GDG Szczecin @bartoszostrowsk