$30 off During Our Annual Pro Sale. View Details »

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.

Pietro F. Maggi

July 03, 2019
Tweet

More Decks by Pietro F. Maggi

Other Decks in Programming

Transcript

  1. Embrace WorkManager
    Pietro Maggi
    @pfmaggi

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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.

    View Slide

  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

    View Slide

  9. I need to run a task

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  15. ● Asynchronous one-off and
    periodic tasks
    WorkManager Benefits
    Compress

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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...

    View Slide

  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

    View Slide

  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+

    View Slide

  23. WorkManager’s Worker classes

    View Slide

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

    View Slide

  25. 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
    }
    }

    View Slide

  26. 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
    }
    }

    View Slide

  27. 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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  33. 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() };
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  39. WorkManager’s Status

    View Slide

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

    View Slide

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

    View Slide

  42. 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)

    View Slide

  43. 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)

    View Slide

  44. 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)

    View Slide

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

    View Slide

  46. Cancel Work

    View Slide

  47. 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

    View Slide

  48. 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)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  54. Unique Work

    View Slide

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

    View Slide

  56. 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)
    }
    }

    View Slide

  57. 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)
    }
    }

    View Slide

  58. 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")

    View Slide

  59. 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")

    View Slide

  60. Periodic Work

    View Slide

  61. PeriodicWorkRequest
    Few differences from OneTimeWorkRequest:
    ● No InitialDelay

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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)

    View Slide

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

    View Slide

  67. 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)

    View Slide

  68. Testing WorkManager

    View Slide

  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

    View Slide

  70. 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

    View Slide

  71. 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

    View Slide

  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(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

    View Slide

  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(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

    View Slide

  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(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

    View Slide

  75. WorkManager Configuration

    View Slide

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

    View Slide

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

    View Slide

  78. WorkManager configuration - Disable the default initialization
    AndroidManifest.xml
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:authorities="${applicationId}.workmanager-init"
    tools:node="remove" />

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  83. Timeline
    WorkManager
    Initialize
    App Starts
    WorkManager
    executes
    WorkRequests

    View Slide

  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")
    }
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  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)

    View Slide

  88. Dagger?

    View Slide

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

    View Slide

  90. Plaid

    View Slide

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

    View Slide

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

    View Slide

  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")
    }
    }
    }

    View Slide

  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

    View Slide

  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)

    View Slide

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

    View Slide

  97. Conclusion

    View Slide

  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..

    View Slide

  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

    View Slide

  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

    View Slide

  101. Q&A
    Pietro Maggi
    @pfmaggi

    View Slide

  102. Thank you!
    Pietro Maggi
    @pfmaggi

    View Slide