Save 37% off PRO during our Black Friday Sale! »

Embrace WorkManager

Embrace WorkManager

After all of 2018 spent as alpha and beta, WorkManager graduated in 2019 to its first stable release and it is now the recommended solution for scheduling and executing deferrable background tasks in Android.
In this talk we’ll quickly look into which use cases are a good fit for WorkManager, and which are better handled by different tools/APIs. Then we will jump into the API itself covering the different options available, from the simple Worker class to the newer CoroutineWorker.
All this with an eye on how you can (and should) test your Workers and how you can implement some of the few missing features that are not yet covered by WorkManager out of the box.

3dfa913cbcfe896af79e0484084c316b?s=128

Pietro F. Maggi

July 03, 2019
Tweet

Transcript

  1. Embrace WorkManager Pietro Maggi @pfmaggi

  2. .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
  3. Part of Jetpack Accelerate development Eliminate boilerplate code Build high

    quality, robust apps
  4. 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
  5. WorkManager WorkManager is an Android library that runs deferrable background

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

    work when the work's constraints are satisfied.
  7. 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.
  8. 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
  9. I need to run a task

  10. Has to finish? I need to run a task

  11. 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
  12. 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
  13. 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
  14. 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
  15. • Asynchronous one-off and periodic tasks WorkManager Benefits Compress

  16. Compress Upload • Asynchronous one-off and periodic tasks • Chaining

    WorkManager Benefits Filter Image 2 Filter Image 3 Filter Image 1
  17. Compress Upload WorkManager Benefits • Asynchronous one-off and periodic tasks

    • Chaining with Input/Output Filter Image 2 Filter Image 3 Filter Image 1
  18. 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
  19. 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
  20. 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...
  21. 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
  22. 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+
  23. WorkManager’s Worker classes

  24. ListenableWorker Worker RxWorker CoroutineWorker* * Only available using Kotlin Meet

    the Worker family
  25. ListenableWorkers class LocationWorker(context : Context, params : WorkerParameters) : ListenableWorker(context,

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

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

    params) { val future = ResolvableFuture.create<Result>() override fun startWork(): ListenableFuture<Result> { if (hasPermissions()) { getLocation() } else { future.set(Result.failure()) } return future } } runs on main thread
  28. 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() } } }
  29. 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() } } }
  30. 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
  31. 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() } } }
  32. 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
  33. RxWorker, for the RxJava2 users class RxDownloadWorker(context : Context, params

    : WorkerParameters) : RxWorker(context, params ) { override fun createWork() : Single<Result> { return Observable.range(0, 100) .flatMap { download("https://www.google.com") } .toList() .map { Result.success() }; } }
  34. 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" }
  35. 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" }
  36. 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" }
  37. 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" }
  38. 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" }
  39. WorkManager’s Status

  40. WorkManager’s WorkRequest WorkManager Basics: bit.ly/WM_Basics val save = OneTimeWorkRequestBuilder<SaveImageWorker>() .addTag(TAG_SAVE)

    .build()
  41. WorkManager’s WorkRequest - WorkInfo from UUID WorkManager Basics: bit.ly/WM_Basics val

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

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

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

    val save = OneTimeWorkRequestBuilder<SaveImageWorker>() .addTag(TAG_SAVE) .build() WorkManager.getInstance().getWorkInfoById(save.id) WorkManager.getInstance().getWorkInfoByIdLiveData(save.id) WorkManager.getInstance().getWorkInfosByTag(TAG_SAVE)
  45. RUNNING Life of OneTime Work ENQUEUED SUCCEEDED BLOCKED FAILED CANCELLED

    RETRY SUCCESS FAILURE CANCEL
  46. Cancel Work

  47. Cancel Work val save = OneTimeWorkRequestBuilder<SaveImageWorker>() .addTag(TAG_SAVE) .build() WorkManager.getInstance().cancelWorkById(save.id) Source:

    developer.android.com/topic/libraries/architecture/workmanager/how-to/cancel-stop-work
  48. Cancel Work val save = OneTimeWorkRequestBuilder<SaveImageWorker>() .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)
  49. 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() } } }
  50. 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() } } }
  51. 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() } } }
  52. Worker class MyWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {

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

    override fun doWork(): Result { // … } override fun onStopped() { super.onStopped() // Cleanup } }
  54. Unique Work

  55. Unique Work Three different policies for OneTimeWorker: • KEEP •

    REPLACE • APPEND Two different policies for PeriodicWorker: • KEEP • REPLACE • APPEND
  56. Unique Work - Periodic class MyApplication: Application() { override fun

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

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

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

    onCreate() { super.onCreate() val backup = PeriodicWorkRequestBuilder<BackupWorker>(8, TimeUnit.HOURS) .build() WorkManager.getInstance().enqueueUniquePeriodicWork(( "BackupWork", ExistingPeriodicWorkPolicy.KEEP, myWork) } } val workManager = WorkManager.getInstance() workManager.getWorkInfosForUniqueWorkLiveData("BackupWork")
  60. Periodic Work

  61. PeriodicWorkRequest Few differences from OneTimeWorkRequest: • No InitialDelay

  62. PeriodicWorkRequest Few differences from OneTimeWorkRequest: • No InitialDelay • InitialDelay

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

    available in WorkManager v2.1.0-alpha02+ • UniqueWork cannot be APPENDED (only KEEP or REPLACE)
  64. 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
  65. 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)
  66. RUNNING Life of Periodic Work ENQUEUED CANCELLED RETRY, SUCCESS or

    FAILURE CANCEL WorkManager Periodicity: bit.ly/WM_Periodic
  67. Periodic Work - Initial Delay class DelayPeriodicWorker(ctx: Context, params: WorkerParameters)

    : Worker(ctx, params) { override fun doWork(): Result { // Schedule periodic work val periodicRequest = PeriodicWorkRequestBuilder<MyWorker>( 1, TimeUnit.HOURS ).build() WorkManager.getInstance().enqueue(periodicRequest) return Result.success() } } val delayedWorkRequest = OneTimeWorkRequestBuilder<DelayPeriodicWorker>() .setInitialDelay(6, TimeUnit.HOURS) .build() WorkManager.getInstance().enqueue(delayedWorkRequest)
  68. Testing WorkManager

  69. 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
  70. A sample test val inputData = workDataOf(KEY_IMAGE_URI to inputDataUri.toString()) val

    request = OneTimeWorkRequestBuilder<TestWorker>() .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
  71. TestWorkerBuilder and TestListenableWorkerBuilder (v2.1) @Before fun setup() { context =

    ApplicationProvider.getApplicationContext() workManager = WorkManager.getInstance(context) } @Test fun testRefreshMainDataWork() { val worker = TestListenableWorkerBuilder<TestWorker>(context).build() val result = worker.startWork().get() assertThat(result, `is` (Result.success())) } developer.android.com/topic/libraries/architecture/workmanager/how-to/testing-210
  72. 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<MyWork>(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
  73. 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<MyWork>(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
  74. 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<MyWork>(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
  75. WorkManager Configuration

  76. Why? • Provide your custom executor • Setup a different

    logging level • Setup a custom WorkerFactory
  77. Why? • Provide your custom executor • Setup a different

    logging level • Setup a custom WorkerFactory (for DI)
  78. WorkManager configuration - Disable the default initialization AndroidManifest.xml <provider android:name="androidx.work.impl.WorkManagerInitializer"

    android:authorities="${applicationId}.workmanager-init" tools:node="remove" />
  79. 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() } }
  80. 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() } }
  81. 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() } }
  82. 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() } }
  83. Timeline WorkManager Initialize App Starts WorkManager executes WorkRequests

  84. 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") } } }
  85. 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() }
  86. 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() }
  87. 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)
  88. Dagger?

  89. I’m not a Dagger expert! - Different requirements - Different

    architectures - Different setups
  90. Plaid

  91. Plaid Modules StoryComponent CoreComponent Dribbble Component Home Component About Component

    bit.ly/plaid-dagger
  92. 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() } } }
  93. 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") } } }
  94. 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
  95. @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)
  96. Things to keep in mind - Register your WorkerFactory -

    Renaming a Worker requires to cancel their work requests - WorkManager and on demand Delivery...
  97. Conclusion

  98. • 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..
  99. 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
  100. 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
  101. Q&A Pietro Maggi @pfmaggi

  102. Thank you! Pietro Maggi @pfmaggi