Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Embracing WorkManager Pietro Maggi | Android DevRel Engineer @ Google bit.ly/pfm-wm-devfest19

Slide 3

Slide 3 text

The basics ..or what is WorkManager

Slide 4

Slide 4 text

Part of Android Jetpack Accelerate development Eliminate boilerplate code Build high quality, robust apps

Slide 5

Slide 5 text

WorkManager is an Android library that runs deferrable background work when the constraints are satisfied. WorkManager

Slide 6

Slide 6 text

WorkManager is an Android library that runs deferrable background work when the constraints are satisfied. WorkManager

Slide 7

Slide 7 text

WorkManager is an Android library that runs deferrable background work when the constraints are satisfied. WorkManager is intended for tasks that require a guarantee that the system will run them even if the app exits. WorkManager

Slide 8

Slide 8 text

WorkManager is an Android library that runs deferrable background work when the constraints are satisfied. WorkManager is intended for tasks that require a guarantee that the system will run them even if the app exits. WorkManager

Slide 9

Slide 9 text

● Asynchronous one-off and periodic tasks WorkManager Benefits Compress

Slide 10

Slide 10 text

● Asynchronous one-off and periodic tasks ● Chaining with Input/Output WorkManager Benefits Upload Compress Filter Image 2 Filter Image 3 Filter Image 1

Slide 11

Slide 11 text

● Asynchronous one-off and periodic tasks ● Chaining with Input/Output ● Constraints WorkManager Benefits Upload Compress Filter Image 2 Filter Image 3 Filter Image 1 Battery not low Storage not low Has Network

Slide 12

Slide 12 text

● Asynchronous one-off and periodic tasks ● Chaining with Input/Output ● Constraints ● Handles compatibility ● System health best practices ● Guaranteed execution WorkManager Benefits Upload Compress Filter Image 2 Filter Image 3 Filter Image 1 Battery not low Storage not low Has Network

Slide 13

Slide 13 text

I need to run a task

Slide 14

Slide 14 text

Has to finish? I need to run a task

Slide 15

Slide 15 text

Executor* or Coroutines Has to finish? I need to run a task NO *If you have system triggers, use a broadcast receiver to get notified

Slide 16

Slide 16 text

Executor* or Coroutines Is Deferrable? Has to finish? I need to run a task YES NO *If you have system triggers, use a broadcast receiver to get notified

Slide 17

Slide 17 text

NO Executor* or Coroutines Is Deferrable? Has to finish? I need to run a task YES NO Foreground Service* *If you have system triggers, use a broadcast receiver to get notified

Slide 18

Slide 18 text

NO Executor* or Coroutines WorkManager Is Deferrable? Has to finish? I need to run a task YES NO Foreground Service* *If you have system triggers, use a broadcast receiver to get notified YES

Slide 19

Slide 19 text

NO Executor* or Coroutines WorkManager Is Deferrable? Has to finish? I need to run a task YES NO Foreground Service* *If you have system triggers, use a broadcast receiver to get notified YES v 2.3-alpha02

Slide 20

Slide 20 text

Release History 1.0 / 2.0 Initial release in support lib (1.0) / AndroidX (2.0) 1.0 / 2.0

Slide 21

Slide 21 text

Release History 1.0 / 2.0 On-demand initialization, new testing support 2.1 2.1

Slide 22

Slide 22 text

Release History Present GcmNetworkManager support (API 22 and below) 2.1 2.2 1.0 / 2.0

Slide 23

Slide 23 text

Release History Future Progress and setForeground APIs 2.1 2.3 1.0 / 2.0 2.2

Slide 24

Slide 24 text

WorkManager’s Worker classes

Slide 25

Slide 25 text

ListenableWorker Worker Meet the Worker Family! RxWorker CoroutineWorker* *Only available using Kotlin

Slide 26

Slide 26 text

class LocationWorker(context : Context, params : WorkerParameters) : ListenableWorker(context, params) { val future = ResolvableFuture.create() override fun startWork(): ListenableFuture { if (hasPermissions()) { getLocation() } else { future.set(Result.failure()) } return future } } ListenableWorker

Slide 27

Slide 27 text

class LocationWorker(context : Context, params : WorkerParameters) : ListenableWorker(context, params) { val future = ResolvableFuture.create() override fun startWork(): ListenableFuture { if (hasPermissions()) { getLocation() } else { future.set(Result.failure()) } return future } } ListenableWorker

Slide 28

Slide 28 text

class LocationWorker(context : Context, params : WorkerParameters) : ListenableWorker(context, params) { val future = ResolvableFuture.create() override fun startWork(): ListenableFuture { if (hasPermissions()) { getLocation() } else { future.set(Result.failure()) } return future } } ListenableWorker Runs on main thread

Slide 29

Slide 29 text

class MyWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { val in = inputData.getString(KEY_STRING) return try { // Do something that can fail Result.success(workDataOf(KEY_INT to 42)) } catch (throwable: Throwable) { // For errors, return FAILURE or RETRY Result.failure() } } } Worker

Slide 30

Slide 30 text

class MyWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { val in = inputData.getString(KEY_STRING) return try { // Do something that can fail Result.success(workDataOf(KEY_INT to 42)) } catch (throwable: Throwable) { // For errors, return FAILURE or RETRY Result.failure() } } } Worker

Slide 31

Slide 31 text

class MyWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { val in = inputData.getString(KEY_STRING) return try { // Do something that can fail Result.success(workDataOf(KEY_INT to 42)) } catch (throwable: Throwable) { // For errors, return FAILURE or RETRY Result.failure() } } } Worker Needs to be Synchronous

Slide 32

Slide 32 text

class RefreshMainDataWork(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { override suspend fun doWork(): Result { val database = getDatabase(applicationContext) val repository = TitleRepository(MainNetworkImpl, database.titleDao) return try { repository.refreshTitle() Result.success() } catch (error: TitleRefreshError) { Result.failure() } } } CoroutineWorker

Slide 33

Slide 33 text

class RefreshMainDataWork(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { override suspend fun doWork(): Result { val database = getDatabase(applicationContext) val repository = TitleRepository(MainNetworkImpl, database.titleDao) return try { repository.refreshTitle() Result.success() } catch (error: TitleRefreshError) { Result.failure() } } } CoroutineWorker

Slide 34

Slide 34 text

class RefreshMainDataWork(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { override suspend fun doWork(): Result { val database = getDatabase(applicationContext) val repository = TitleRepository(MainNetworkImpl, database.titleDao) return try { repository.refreshTitle() Result.success() } catch (error: TitleRefreshError) { Result.failure() } } } CoroutineWorker

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

How does WorkManager interact with Android?

Slide 42

Slide 42 text

class UploadWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) { override fun doWork(): Result { uploadImages() return Result.success() } } val uploadWorkRequest = OneTimeWorkRequestBuilder() .setConstraints(constraints) .build() WorkManager.getInstance(myContext).enqueue(uploadWorkRequest) Enqueue a WorkRequest

Slide 43

Slide 43 text

Store in WorkManager Database How does WorkManager persist work?

Slide 44

Slide 44 text

JobScheduler Store in WorkManager Database How does WorkManager persist work? API 23+

Slide 45

Slide 45 text

JobScheduler Store in WorkManager Database How does WorkManager persist work? API 23+ Has Google Play and work-gcm? API 22-

Slide 46

Slide 46 text

JobScheduler Store in WorkManager Database GcmNetworkManager How does WorkManager persist work? API 23+ Has Google Play and work-gcm? Yes API 22-

Slide 47

Slide 47 text

JobScheduler Store in WorkManager Database GcmNetworkManager How does WorkManager persist work? API 23+ Has Google Play and work-gcm? AlarmManager Yes No API 22-

Slide 48

Slide 48 text

JobScheduler GcmNetworkManager How does WorkManager run your Work? AlarmManager GreedyScheduler Start app process (if needed) Ask WorkManager to Run Work

Slide 49

Slide 49 text

JobScheduler GcmNetworkManager AlarmManager GreedyScheduler Start app process (if needed) Ask WorkManager to Run Work How does WorkManager run your Work?

Slide 50

Slide 50 text

Observe Work

Slide 51

Slide 51 text

val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build() WorkManager’s WorkRequest

Slide 52

Slide 52 text

val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build() WorkManager.getInstance().getWorkInfoById(save.id) WorkInfo from UUID WorkManager.getInstance().getWorkInfoByIdLiveData(save.id)

Slide 53

Slide 53 text

val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build() WorkManager.getInstance().getWorkInfoById(save.id) WorkInfo from UUID WorkManager.getInstance().getWorkInfoByIdLiveData(save.id)

Slide 54

Slide 54 text

val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build() WorkManager.getInstance().getWorkInfoById(save.id) WorkInfo(s) from TAG WorkManager.getInstance().getWorkInfoByIdLiveData(save.id) WorkManager.getInstance().getWorkInfosByTag(TAG_SAVE) WorkManager.getInstance().getWorkInfosByTagLiveData(TAG_SAVE)

Slide 55

Slide 55 text

val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build() WorkManager.getInstance().getWorkInfoById(save.id) WorkInfo(s) from TAG WorkManager.getInstance().getWorkInfoByIdLiveData(save.id) WorkManager.getInstance().getWorkInfosByTag(TAG_SAVE) WorkManager.getInstance().getWorkInfosByTagLiveData(TAG_SAVE)

Slide 56

Slide 56 text

BLOCKED Life of OneTimeWorkRequest

Slide 57

Slide 57 text

BLOCKED ENQUEUED Life of OneTimeWorkRequest

Slide 58

Slide 58 text

BLOCKED ENQUEUED Life of OneTimeWorkRequest RUNNING

Slide 59

Slide 59 text

BLOCKED ENQUEUED Life of OneTimeWorkRequest RUNNING SUCCEEDED SUCCESS

Slide 60

Slide 60 text

BLOCKED ENQUEUED Life of OneTimeWorkRequest RUNNING SUCCEEDED CANCELLED SUCCESS RETRY

Slide 61

Slide 61 text

BLOCKED ENQUEUED Life of OneTimeWorkRequest RUNNING SUCCEEDED FAILED CANCELLED SUCCESS FAILURE RETRY

Slide 62

Slide 62 text

BLOCKED ENQUEUED Life of OneTimeWorkRequest RUNNING SUCCEEDED FAILED CANCELLED CANCEL SUCCESS FAILURE RETRY

Slide 63

Slide 63 text

ENQUEUED Life of PeriodicWorkRequest

Slide 64

Slide 64 text

ENQUEUED Life of PeriodicWorkRequest RUNNING

Slide 65

Slide 65 text

ENQUEUED Life of PeriodicWorkRequest RUNNING SUCCESS

Slide 66

Slide 66 text

SUCCESS, RETRY ENQUEUED Life of PeriodicWorkRequest RUNNING SUCCESS

Slide 67

Slide 67 text

SUCCESS, RETRY SUCCESS, RETRY, FAILURE ENQUEUED Life of PeriodicWorkRequest RUNNING SUCCESS

Slide 68

Slide 68 text

SUCCESS, RETRY SUCCESS, RETRY, FAILURE ENQUEUED Life of PeriodicWorkRequest RUNNING CANCELLED CANCEL SUCCESS

Slide 69

Slide 69 text

Life of a chain of work If a unit of work fails, all dependent work is marked as FAILED

Slide 70

Slide 70 text

Life of a chain of work If a unit of work fails, all dependent work is marked as FAILED If a unit of work is cancelled, all dependent work is marked as CANCELLED

Slide 71

Slide 71 text

Cancel Work and Handling Stoppages

Slide 72

Slide 72 text

val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build() WorkManager’s WorkRequest

Slide 73

Slide 73 text

val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build() WorkManager.getInstance().cancelWorkById(save.id) Cancel Work from UUID

Slide 74

Slide 74 text

val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build() WorkManager.getInstance().cancelWorkById(save.id) Cancel Work from TAG WorkManager.getInstance().cancelAllWorkByTag(TAG_SAVE)

Slide 75

Slide 75 text

class MyWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { val in = inputData.getString(KEY_STRING) return try { // Do something that can fail if (isStopped()) return Result.failure() Result.success(workDataOf(KEY_INT to 42)) } catch (throwable: Throwable) { // For errors, return FAILURE or RETRY Result.failure() } } } Handle Stopping Work

Slide 76

Slide 76 text

class MyWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { val in = inputData.getString(KEY_STRING) return try { // Do something that can fail if (isStopped()) return Result.failure() Result.success(workDataOf(KEY_INT to 42)) } catch (throwable: Throwable) { // For errors, return FAILURE or RETRY Result.failure() } } } Handling Stoppages

Slide 77

Slide 77 text

class MyWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { val in = inputData.getString(KEY_STRING) return try { // Do something that can fail if (isStopped()) return Result.success() // this is not used Result.success(workDataOf(KEY_INT to 42)) } catch (throwable: Throwable) { // For errors, return FAILURE or RETRY Result.failure() } } } Handling Stoppages

Slide 78

Slide 78 text

class MyWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { // … } override fun onStopped() { super.onStopped() // Cleanup } } Handling Stoppages

Slide 79

Slide 79 text

class MyWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { // … } override fun onStopped() { super.onStopped() // Cleanup } } Handling Stoppages

Slide 80

Slide 80 text

Testing

Slide 81

Slide 81 text

@Before fun setup() { context = ApplicationProvider.getApplicationContext() workManager = WorkManager.getInstance(context) } @Test fun testRefreshMainDataWork() { val worker = TestListenableWorkerBuilder(context).build() val result = worker.startWork().get() assertThat(result, `is` (Result.success())) } TestListenableWorkerBuilder (v2.1)

Slide 82

Slide 82 text

@Before fun setup() { context = ApplicationProvider.getApplicationContext() workManager = WorkManager.getInstance(context) } @Test fun testRefreshMainDataWork() { val worker = TestListenableWorkerBuilder(context).build() val result = worker.startWork().get() assertThat(result, `is` (Result.success())) } Build your Worker

Slide 83

Slide 83 text

@Before fun setup() { context = ApplicationProvider.getApplicationContext() workManager = WorkManager.getInstance(context) } @Test fun testRefreshMainDataWork() { val worker = TestListenableWorkerBuilder(context).build() val result = worker.startWork().get() assertThat(result, `is` (Result.success())) } Directly invoke your Work

Slide 84

Slide 84 text

@Before fun setup() { context = ApplicationProvider.getApplicationContext() workManager = WorkManager.getInstance(context) } @Test fun testRefreshMainDataWork() { val worker = TestListenableWorkerBuilder(context).build() val result = worker.startWork().get() assertThat(result, `is` (Result.success())) } Test!

Slide 85

Slide 85 text

@Test fun testMyWorkRetry() { val data = workDataOf("SERVER_URL" to "http://fake.url") // Get the ListenableWorker with a RunAttemptCount of 2 val worker = TestListenableWorkerBuilder(context) .setInputData(data) .setRunAttemptCount(2) .build() // Start the work synchronously val result = worker.startWork().get() assertThat(result, `is`(Result.retry())) } TestListenableWorkerBuilder (v2.1)

Slide 86

Slide 86 text

@Test fun testMyWorkRetry() { val data = workDataOf("SERVER_URL" to "http://fake.url") // Get the ListenableWorker with a RunAttemptCount of 2 val worker = TestListenableWorkerBuilder(context) .setInputData(data) .setRunAttemptCount(2) .build() // Start the work synchronously val result = worker.startWork().get() assertThat(result, `is`(Result.retry())) } Test Work with input data

Slide 87

Slide 87 text

@Test fun testMyWorkRetry() { val data = workDataOf("SERVER_URL" to "http://fake.url") // Get the ListenableWorker with a RunAttemptCount of 2 val worker = TestListenableWorkerBuilder(context) .setInputData(data) .setRunAttemptCount(2) .build() // Start the work synchronously val result = worker.startWork().get() assertThat(result, `is`(Result.retry())) } Test Work for Retry behaviour

Slide 88

Slide 88 text

@Test fun testMyWorkRetry() { val data = workDataOf("SERVER_URL" to "http://fake.url") // Get the ListenableWorker with a RunAttemptCount of 2 val worker = TestListenableWorkerBuilder(context) .setInputData(data) .setRunAttemptCount(2) .build() // Start the work synchronously val result = worker.startWork().get() assertThat(result, `is`(Result.retry())) } Test Work for Retry behaviour

Slide 89

Slide 89 text

Where to go… ... from here?

Slide 90

Slide 90 text

Resources: Just Starting? --> Codelab: codelabs.developers.google.com/codelabs/android-workmanager-kt/ Want to deep dive? --> Documentation: d.android.com/topic/libraries/architecture/workmanager/ I know what I’m doing! --> Release Notes: d.android.com/jetpack/androidx/releases/work bit.ly/pfm-wm-devfest19

Slide 91

Slide 91 text

More Resources: Videos: - ADS2018 - Working with WorkManager - ADS2019 - WorkManager: Beyond the Basics Medium Blogs: - Introducing WorkManager - WorkManager Basics - WorkManager Periodicity - WorkManager meets Kotlin - More coming... bit.ly/pfm-wm-devfest19

Slide 92

Slide 92 text

THANKS! Pietro Maggi, @pfmaggi bit.ly/pfm-wm-devfest19