Slide 1

Slide 1 text

Embrace WorkManager Pietro Maggi @pfmaggi

Slide 2

Slide 2 text

.plan ● What is WorkManager ● Which Worker class is right for my job ● WorkManager’s status ● Cancel Work ● Unique Work ● Periodic Work ● Testing WorkManager ● WorkManager Configuration

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Jetpack categories Foundation Android KTX AppCompat Auto Benchmark Multidex Security Test TV Wear OS by Google Behavior CameraX Download manager Media & playback Notifications Permissions Preferences Sharing Slices UI Animation & transitions Emoji Fragment Layout Palette Architecture Data Binding Lifecycles LiveData Navigation Paging Room ViewModel WorkManager

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

WorkManager WorkManager is an Android library that runs deferrable background work when the work's constraints are satisfied. WorkManager is intended for tasks that require a guarantee that the system will run them even if the app exits. Introducing WorkManager: bit.ly/WM_Introduction

Slide 9

Slide 9 text

I need to run a task

Slide 10

Slide 10 text

Has to finish? I need to run a task

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

● Asynchronous one-off and periodic tasks WorkManager Benefits Compress

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

WorkManager Benefits ● Asynchronous one-off and periodic tasks ● Chaining with Input/Output ● Constraints ● Handles compatibility ● System health best practices ● Guaranteed execution ● Query state to display in UI Uploading...

Slide 21

Slide 21 text

WorkManager Benefits ● Asynchronous one-off and periodic tasks ● Chaining with Input/Output ● Constraints ● Handles compatibility ● System health best practices ● Guaranteed execution ● Query state to display in UI Finished

Slide 22

Slide 22 text

Choose the Background Scheduler API 23+ JobScheduler* YES NO AlarmManager and BroadcastReceiver * Available since API level 21, but used only on devices with API level 23+

Slide 23

Slide 23 text

WorkManager’s Worker classes

Slide 24

Slide 24 text

ListenableWorker Worker RxWorker CoroutineWorker* * Only available using Kotlin Meet the Worker family

Slide 25

Slide 25 text

ListenableWorkers 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 } }

Slide 26

Slide 26 text

ListenableWorkers 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 } }

Slide 27

Slide 27 text

ListenableWorkers 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 } } runs on main thread

Slide 28

Slide 28 text

Worker 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() } } }

Slide 29

Slide 29 text

Worker 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() } } }

Slide 30

Slide 30 text

Worker 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() } } } Needs to be Synchronous

Slide 31

Slide 31 text

A CoroutineWorker sample 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() } } }

Slide 32

Slide 32 text

A CoroutineWorker sample 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() } } } WorkManager meets Kotlin: bit.ly/WM_Kotlin

Slide 33

Slide 33 text

RxWorker, for the RxJava2 users class RxDownloadWorker(context : Context, params : WorkerParameters) : RxWorker(context, params ) { override fun createWork() : Single { return Observable.range(0, 100) .flatMap { download("https://www.google.com") } .toList() .map { Result.success() }; } }

Slide 34

Slide 34 text

WorkManager Dependencies dependencies { def work_version = '2.0.1' implementation "androidx.work:work-runtime:$work_version" implementation "androidx.work:work-runtime-ktx:$work_version" implementation "androidx.work:work-rxjava2:$work_version" androidTestImplementation "androidx.work:work-testing:$work_version" }

Slide 35

Slide 35 text

WorkManager Dependencies dependencies { def work_version = '2.0.1' implementation "androidx.work:work-runtime:$work_version" implementation "androidx.work:work-runtime-ktx:$work_version" implementation "androidx.work:work-rxjava2:$work_version" androidTestImplementation "androidx.work:work-testing:$work_version" }

Slide 36

Slide 36 text

WorkManager Dependencies dependencies { def work_version = '2.0.1' implementation "androidx.work:work-runtime:$work_version" implementation "androidx.work:work-runtime-ktx:$work_version" implementation "androidx.work:work-rxjava2:$work_version" androidTestImplementation "androidx.work:work-testing:$work_version" }

Slide 37

Slide 37 text

WorkManager Dependencies dependencies { def work_version = '2.0.1' implementation "androidx.work:work-runtime:$work_version" implementation "androidx.work:work-runtime-ktx:$work_version" implementation "androidx.work:work-rxjava2:$work_version" androidTestImplementation "androidx.work:work-testing:$work_version" }

Slide 38

Slide 38 text

WorkManager Dependencies dependencies { def work_version = '2.0.1' implementation "androidx.work:work-runtime:$work_version" implementation "androidx.work:work-runtime-ktx:$work_version" implementation "androidx.work:work-rxjava2:$work_version" androidTestImplementation "androidx.work:work-testing:$work_version" }

Slide 39

Slide 39 text

WorkManager’s Status

Slide 40

Slide 40 text

WorkManager’s WorkRequest WorkManager Basics: bit.ly/WM_Basics val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build()

Slide 41

Slide 41 text

WorkManager’s WorkRequest - WorkInfo from UUID WorkManager Basics: bit.ly/WM_Basics val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build() WorkManager.getInstance().getWorkInfoById(save.id)

Slide 42

Slide 42 text

WorkManager’s WorkRequest - WorkInfo from UUID WorkManager Basics: bit.ly/WM_Basics val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build() WorkManager.getInstance().getWorkInfoById(save.id) WorkManager.getInstance().getWorkInfoByIdLiveData(save.id)

Slide 43

Slide 43 text

WorkManager’s WorkRequest - WorkInfo(s) from TAG WorkManager Basics: bit.ly/WM_Basics val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build() WorkManager.getInstance().getWorkInfoById(save.id) WorkManager.getInstance().getWorkInfoByIdLiveData(save.id) WorkManager.getInstance().getWorkInfosByTag(TAG_SAVE)

Slide 44

Slide 44 text

WorkManager.getInstance().getWorkInfosByTagLiveData(TAG_SAVE) WorkManager’s WorkRequest - WorkInfo(s) from TAG WorkManager Basics: bit.ly/WM_Basics val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build() WorkManager.getInstance().getWorkInfoById(save.id) WorkManager.getInstance().getWorkInfoByIdLiveData(save.id) WorkManager.getInstance().getWorkInfosByTag(TAG_SAVE)

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Cancel Work

Slide 47

Slide 47 text

Cancel Work val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build() WorkManager.getInstance().cancelWorkById(save.id) Source: developer.android.com/topic/libraries/architecture/workmanager/how-to/cancel-stop-work

Slide 48

Slide 48 text

Cancel Work val save = OneTimeWorkRequestBuilder() .addTag(TAG_SAVE) .build() Source: developer.android.com/topic/libraries/architecture/workmanager/how-to/cancel-stop-work WorkManager.getInstance().cancelWorkById(save.id) WorkManager.getInstance().cancelAllWorkByTag(TAG_SAVE)

Slide 49

Slide 49 text

Worker 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() } } }

Slide 50

Slide 50 text

Worker 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() } } }

Slide 51

Slide 51 text

Worker 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() } } }

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Unique Work

Slide 55

Slide 55 text

Unique Work Three different policies for OneTimeWorker: ● KEEP ● REPLACE ● APPEND Two different policies for PeriodicWorker: ● KEEP ● REPLACE ● APPEND

Slide 56

Slide 56 text

Unique Work - Periodic class MyApplication: Application() { override fun onCreate() { super.onCreate() val backup = PeriodicWorkRequestBuilder(8, TimeUnit.HOURS) .build() WorkManager.getInstance().enqueueUniquePeriodicWork(( "BackupWork", ExistingPeriodicWorkPolicy.KEEP, myWork) } }

Slide 57

Slide 57 text

Unique Work - Periodic class MyApplication: Application() { override fun onCreate() { super.onCreate() val backup = PeriodicWorkRequestBuilder(8, TimeUnit.HOURS) .build() WorkManager.getInstance().enqueueUniquePeriodicWork(( "BackupWork", ExistingPeriodicWorkPolicy.KEEP, myWork) } }

Slide 58

Slide 58 text

Unique Work - Periodic class MyApplication: Application() { override fun onCreate() { super.onCreate() val backup = PeriodicWorkRequestBuilder(8, TimeUnit.HOURS) .build() WorkManager.getInstance().enqueueUniquePeriodicWork(( "BackupWork", ExistingPeriodicWorkPolicy.KEEP, myWork) } } val workManager = WorkManager.getInstance() workManager.getWorkInfosForUniqueWorkLiveData("BackupWork")

Slide 59

Slide 59 text

Unique Work - Periodic class MyApplication: Application() { override fun onCreate() { super.onCreate() val backup = PeriodicWorkRequestBuilder(8, TimeUnit.HOURS) .build() WorkManager.getInstance().enqueueUniquePeriodicWork(( "BackupWork", ExistingPeriodicWorkPolicy.KEEP, myWork) } } val workManager = WorkManager.getInstance() workManager.getWorkInfosForUniqueWorkLiveData("BackupWork")

Slide 60

Slide 60 text

Periodic Work

Slide 61

Slide 61 text

PeriodicWorkRequest Few differences from OneTimeWorkRequest: ● No InitialDelay

Slide 62

Slide 62 text

PeriodicWorkRequest Few differences from OneTimeWorkRequest: ● No InitialDelay ● InitialDelay available in WorkManager v2.1.0-alpha02+

Slide 63

Slide 63 text

PeriodicWorkRequest Few differences from OneTimeWorkRequest: ● No InitialDelay ● InitialDelay available in WorkManager v2.1.0-alpha02+ ● UniqueWork cannot be APPENDED (only KEEP or REPLACE)

Slide 64

Slide 64 text

PeriodicWorkRequest Few differences from OneTimeWorkRequest: ● No InitialDelay ● InitialDelay available in WorkManager v2.1.0-alpha02+ ● UniqueWork cannot be APPENDED (only KEEP or REPLACE) ● There’s no SUCCEEDED state

Slide 65

Slide 65 text

PeriodicWorkRequest Few differences from OneTimeWorkRequest: ● No InitialDelay ● InitialDelay available in WorkManager v2.1.0-alpha02+ ● UniqueWork cannot be APPENDED (only KEEP or REPLACE) ● There’s no SUCCEEDED state (and no FAILED state)

Slide 66

Slide 66 text

RUNNING Life of Periodic Work ENQUEUED CANCELLED RETRY, SUCCESS or FAILURE CANCEL WorkManager Periodicity: bit.ly/WM_Periodic

Slide 67

Slide 67 text

Periodic Work - Initial Delay class DelayPeriodicWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { // Schedule periodic work val periodicRequest = PeriodicWorkRequestBuilder( 1, TimeUnit.HOURS ).build() WorkManager.getInstance().enqueue(periodicRequest) return Result.success() } } val delayedWorkRequest = OneTimeWorkRequestBuilder() .setInitialDelay(6, TimeUnit.HOURS) .build() WorkManager.getInstance().enqueue(delayedWorkRequest)

Slide 68

Slide 68 text

Testing WorkManager

Slide 69

Slide 69 text

WorkManager TestInitHelper targetContext = InstrumentationRegistry.getInstrumentation().targetContext WorkManagerTestInitHelper.initializeTestWorkManager(targetContext) targetContext = InstrumentationRegistry.getInstrumentation().targetContext configuration = Configuration.Builder() .setMinimumLoggingLevel(Log.DEBUG) .setExecutor(SynchronousExecutor()) .build() WorkManagerTestInitHelper.initializeTestWorkManager(targetContext, configuration) Source: developer.android.com/topic/libraries/architecture/workmanager/how-to/testing

Slide 70

Slide 70 text

A sample test val inputData = workDataOf(KEY_IMAGE_URI to inputDataUri.toString()) val request = OneTimeWorkRequestBuilder() .setInputData(inputData) .build() workManager.enqueue(request).result.get() val workInfo = workManager.getWorkInfoById(request.id).get() val outputUri = workInfo.outputData.getString(KEY_IMAGE_URI) assertThat(uriFileExists(targetContext, outputUri), `is`(true)) assertThat(workInfo.state, `is`(WorkInfo.State.SUCCEEDED)) Source: developer.android.com/topic/libraries/architecture/workmanager/how-to/testing

Slide 71

Slide 71 text

TestWorkerBuilder and TestListenableWorkerBuilder (v2.1) @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())) } developer.android.com/topic/libraries/architecture/workmanager/how-to/testing-210

Slide 72

Slide 72 text

TestWorkerBuilder and TestListenableWorkerBuilder (v2.1) @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())) } WorkManager meets Kotlin: bit.ly/WM_Kotlin

Slide 73

Slide 73 text

TestWorkerBuilder and TestListenableWorkerBuilder (v2.1) @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())) } WorkManager meets Kotlin: bit.ly/WM_Kotlin

Slide 74

Slide 74 text

TestWorkerBuilder and TestListenableWorkerBuilder (v2.1) @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())) } WorkManager meets Kotlin: bit.ly/WM_Kotlin

Slide 75

Slide 75 text

WorkManager Configuration

Slide 76

Slide 76 text

Why? ● Provide your custom executor ● Setup a different logging level ● Setup a custom WorkerFactory

Slide 77

Slide 77 text

Why? ● Provide your custom executor ● Setup a different logging level ● Setup a custom WorkerFactory (for DI)

Slide 78

Slide 78 text

WorkManager configuration - Disable the default initialization AndroidManifest.xml

Slide 79

Slide 79 text

WorkManager configuration - Make a new configuration class MyApplication: Application() { override fun onCreate() { super.onCreate() // provide custom configuration val config = Configuration.Builder() .setMinimumLoggingLevel(android.util.Log.INFO) .setWorkerFactory(MyWorkerFactory()) .build() //initialize WorkManager WorkManager.initialize(this, config) val workManager = WorkManager.getInstance() } }

Slide 80

Slide 80 text

WorkManager configuration - Make a new configuration class MyApplication: Application() { override fun onCreate() { super.onCreate() // provide custom configuration val config = Configuration.Builder() .setMinimumLoggingLevel(android.util.Log.INFO) .setWorkerFactory(MyWorkerFactory()) .build() //initialize WorkManager WorkManager.initialize(this, config) val workManager = WorkManager.getInstance() } }

Slide 81

Slide 81 text

WorkManager configuration - Make a new configuration class MyApplication: Application() { override fun onCreate() { super.onCreate() // provide custom configuration val config = Configuration.Builder() .setMinimumLoggingLevel(android.util.Log.INFO) .setWorkerFactory(MyWorkerFactory()) .build() //initialize WorkManager WorkManager.initialize(this, config) val workManager = WorkManager.getInstance() } }

Slide 82

Slide 82 text

WorkManager configuration - Make a new configuration class MyApplication: Application() { override fun onCreate() { super.onCreate() // provide custom configuration val config = Configuration.Builder() .setMinimumLoggingLevel(android.util.Log.INFO) .setWorkerFactory(MyWorkerFactory()) .build() //initialize WorkManager WorkManager.initialize(this, config) val workManager = WorkManager.getInstance() } }

Slide 83

Slide 83 text

Timeline WorkManager Initialize App Starts WorkManager executes WorkRequests

Slide 84

Slide 84 text

WorkManager configuration - Build your factory class MyWorkerFactory(private val appContext: Context) : WorkerFactory() { override fun createWorker(appContext: Context, workerClassName: String, workerParameters: WorkerParameters): ListenableWorker { if (workerClassName.equals("com.sample.myWorker")) { return UpvoteStoryWorker(appContext, workerParameters) } else { throw Exception("Unexpected Worker classname: $workerClassName") } } }

Slide 85

Slide 85 text

WorkManager configuration - New 2.1.x on demand initialization class MyApplication : Application(), Configuration.Provider { override fun getWorkManagerConfiguration(): Configuration = // provide custom configuration Configuration.Builder() .setMinimumLoggingLevel(android.util.Log.INFO) .setWorkerFactory(MyWorkerFactory(this)) .build() }

Slide 86

Slide 86 text

WorkManager configuration - New 2.1.x on demand initialization class MyApplication : Application(), Configuration.Provider { override fun getWorkManagerConfiguration(): Configuration = // provide custom configuration Configuration.Builder() .setMinimumLoggingLevel(android.util.Log.INFO) .setWorkerFactory(MyWorkerFactory(this)) .build() }

Slide 87

Slide 87 text

WorkManager configuration - New 2.1.x on demand initialization class MyApplication : Application(), Configuration.Provider { override fun getWorkManagerConfiguration(): Configuration = // provide custom configuration Configuration.Builder() .setMinimumLoggingLevel(android.util.Log.INFO) .setWorkerFactory(MyWorkerFactory(this)) .build() } WorkManager.getInstance(context)

Slide 88

Slide 88 text

Dagger?

Slide 89

Slide 89 text

I’m not a Dagger expert! - Different requirements - Different architectures - Different setups

Slide 90

Slide 90 text

Plaid

Slide 91

Slide 91 text

Plaid Modules StoryComponent CoreComponent Dribbble Component Home Component About Component bit.ly/plaid-dagger

Slide 92

Slide 92 text

WorkManager in Plaid class UpvoteStoryWorker(appContext: Context, workerParams: WorkerParameters, private val service: DesignerNewsService) : CoroutineWorker(appContext, workerParams) { override suspend fun doWork(): Result { return try { val storyId = inputData.getLong(KEY_STORY_ID, 0) val userId = inputData.getLong(KEY_USER_ID, 0) val request = UpvoteStoryRequest(storyId, userId) val response = service.upvoteStoryV2(request).await() if (response.isSuccessful) { Result.success() } else { Result.failure() } } catch (e: Exception) { Result.failure() } } }

Slide 93

Slide 93 text

WorkManager in Plaid class UpvoteStoryWorkerFactory(private val service: DesignerNewsService) : WorkerFactory() { override fun createWorker( appContext: Context, workerClassName: String, workerParameters: WorkerParameters ): ListenableWorker { if (workerClassName.equals("io.plaidapp.designernews.worker.UpvoteStoryWorker")) { return UpvoteStoryWorker(appContext, workerParameters, service) } else { throw Exception("Unexpected Worker classname: $workerClassName") } } }

Slide 94

Slide 94 text

DelegatingWorkerFactory (v2.1) A WorkerFactory which delegates to other factories. Factories can register themselves as delegates, and they will be invoked in order until a delegated factory returns a non-null ListenableWorker instance. Source: developer.android.com/reference/androidx/work/DelegatingWorkerFactory

Slide 95

Slide 95 text

@Provides @FeatureScope fun provideWorkManager(service: DesignerNewsService): WorkManager { // provide custom configuration val appContext = context.applicationContext if (appContext is Configuration.Provider) { val factory = appContext.getWorkManagerConfiguration().workerFactory as PlaidWorkerFactory factory.addFactory(UpvoteStoryWorkerFactory(service)) } return WorkManager.getInstance(context) } DelegatingWorkerFactory (v2.1)

Slide 96

Slide 96 text

Things to keep in mind - Register your WorkerFactory - Renaming a Worker requires to cancel their work requests - WorkManager and on demand Delivery...

Slide 97

Slide 97 text

Conclusion

Slide 98

Slide 98 text

● New developments on v2.x branch ● Fixes backported to v1.x branch ● Use Kotlin? CoroutineWorker ● Don’t forget to handle cancellation and stopping ● If you need to do something once, make it unique ● WorkRequest are persisted in a DB (including Data) Few things before I take off..

Slide 99

Slide 99 text

Resources Documentation: developer.android.com/topic/libraries/architecture/workmanager/ Codelab: codelabs.developers.google.com/codelabs/android-workmanager-kt/ Release Notes: developer.android.com/jetpack/androidx/releases/work Working with WorkManager talk at ADS: www.youtube.com/watch?v=83a4rYXsDs0 Guide to background processing: developer.android.com/guide/background/ WorkManager Source Code (part of AOSP): android.googlesource.com/platform/frameworks/support/+/master/work

Slide 100

Slide 100 text

Resources WorkManager Blog Series: ● Introducing WorkManager bit.ly/WM_Introduction ● WorkManager Basics bit.ly/WM_Basics ● WorkManager meets Kotlin bit.ly/WM_Kotlin ● WorkManager Periodicity bit.ly/WM_Periodic

Slide 101

Slide 101 text

Q&A Pietro Maggi @pfmaggi

Slide 102

Slide 102 text

Thank you! Pietro Maggi @pfmaggi