Fonctionnalités avancées de WorkManager

Cet atelier de programmation traite des concepts avancés de WorkManager. Il s'appuie sur les concepts de base traités dans l'atelier de programmation Travail en arrière-plan avec WorkManager.

Voici quelques autres ressources pour vous familiariser avec WorkManager :

Objectifs de l'atelier

Dans cet atelier de programmation, vous allez utiliser Blur-O-Matic, une application qui permet de flouter des photos et des images, et d'enregistrer le résultat dans un fichier. Si vous avez déjà terminé l'atelier de programmation Travail en arrière-plan avec WorkManager, vous allez retrouver ici le même exemple d'application. Dans cet atelier, vous allez ajouter des fonctionnalités au code :

  1. Configuration personnalisée
  2. Utilisation de l'API Progress pour mettre à jour l'interface utilisateur pendant que le travail s'exécute
  3. Test de vos nœuds de calcul

Prérequis

Pour cet atelier de programmation, vous devez disposer de la dernière version stable d'Android Studio.

Vous devez également connaître les classes LiveData, ViewModel et View Binding. Si vous ne les connaissez pas encore, consultez l'atelier de programmation sur les composants du cycle de vie d'Android (en particulier pour ViewModel et LiveData) ou l'atelier de programmation sur Room (présentation des composants de l'architecture).

Si vous êtes bloqué

Si vous vous retrouvez bloqué au cours de cet atelier de programmation ou si vous souhaitez voir le code final, vous pouvez télécharger le code final de Blur-o-Matic.

Ou, si vous préférez, vous pouvez cloner l'atelier de programmation WorkManager complet depuis GitHub :

$ git clone -b advanced https://github.com/googlecodelabs/android-workmanager

Étape 1 : Téléchargez le code

Cliquez sur le lien suivant pour télécharger la version du code nécessaire pour suivre cet atelier de programmation :

Télécharger le code de départ

Ou, si vous préférez, vous pouvez cloner l'atelier de programmation depuis GitHub :

$ git clone https://github.com/googlecodelabs/android-workmanager

Étape 2 : Exécutez l'application

Exécutez l'application. Les écrans suivants devraient s'afficher. Veillez bien à autoriser l'application à accéder à vos photos lorsque vous y êtes invité.

Vous pouvez sélectionner une image et passer à l'écran suivant, qui contient des cases d'option pour sélectionner le flou de l'image. Appuyez sur le bouton Go (Appliquer) pour flouter et enregistrer l'image. Lors du floutage, l'application affiche le bouton Cancel (Annuler) pour que vous puissiez interrompre l'opération.

d6b8946f437ec4e1.png

Le code de départ contient les éléments suivants :

  • WorkerUtils**:** cette classe contient le code du floutage, ainsi que des méthodes pratiques que vous utiliserez plus tard pour afficher Notifications et ralentir l'application.
  • BlurApplication***:** classe d'application avec une méthode onCreate() simple pour initialiser le système de journalisation Timber pour les versions de débogage.
  • BlurActivity***:** activité qui permet d'afficher l'image, et qui inclut des cases d'option pour sélectionner le niveau de flou.
  • BlurViewModel***:** ce modèle de vue stocke toutes les données nécessaires à l'affichage de BlurActivity. C'est également dans cette classe que vous démarrez le travail en arrière-plan à l'aide de WorkManager.
  • Workers/CleanupWorker**:** ce nœud de calcul supprime toujours les fichiers temporaires existants.
  • Workers/BlurWorker***:** ce nœud de calcul floute l'image transmise en tant que données d'entrée avec un URI et renvoie l'URI du fichier temporaire.
  • Workers/SaveImageToFileWorker**:** ce nœud de calcul utilise l'URI de l'image temporaire et renvoie l'URI du fichier final.
  • Constants**:** classe statique comprenant quelques constantes que vous utiliserez dans cet atelier de programmation.
  • SelectImageActivity**:** première activité vous permettant de sélectionner une image.
  • res/activity_blur.xml et res/activity_select.xml : fichiers de mise en page pour chaque activité.

***** Fichiers dans lesquels vous écrirez le code.

WorkManager nécessite la dépendance Gradle ci-dessous. Ils ont déjà été inclus dans les fichiers :

app/build.gradle

dependencies {
    implementation "androidx.work:work-runtime-ktx:$versions.work"
}

Vous devez télécharger la dernière version de work-runtime depuis la page de téléchargement des versions de WorkManager et utiliser la dernière version stable, ou utiliser la version suivante :

build.gradle

versions.work = "2.4.0"

Veillez à cliquer sur Sync Now (Synchroniser maintenant) pour synchroniser votre projet avec les fichiers Gradle modifiés.

Dans cette étape, vous allez ajouter à l'application une configuration personnalisée afin de modifier le niveau de journalisation de WorkManager pour les versions de débogage.

Étape 1 : Désactivez l'initialisation par défaut

Comme décrit dans la documentation Custom WorkManager configuration and initialization (Configuration personnalisée et initialisation de WorkManager), vous devez désactiver l'initialisation par défaut dans le fichier AndroidManifest.xml en supprimant le nœud qui est automatiquement fusionné par défaut depuis la bibliothèque WorkManager.

Pour supprimer ce nœud, vous pouvez ajouter un nouveau nœud de fournisseur à AndroidManifest.xml, comme indiqué ci-dessous :

AndroidManifest.xml

<application

...

    <provider
        android:name="androidx.work.impl.WorkManagerInitializer"
        android:authorities="${applicationId}.workmanager-init"
        tools:node="remove" />
</application>

Vous devez également ajouter l'espace de noms des outils au fichier manifeste. Le fichier complet contenant ces modifications est le suivant :

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 Google LLC.
   SPDX-License-Identifier: Apache-2.0 -->

<manifest package="com.example.background"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:name=".BlurApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".SelectImageActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".BlurActivity" />

        <!-- ADD THE FOLLOWING NODE -->
        <provider
            android:name="androidx.work.impl.WorkManagerInitializer"
            android:authorities="${applicationId}.workmanager-init"
            tools:node="remove" />
    </application>
</manifest>

Étape 2 : Ajoutez Configuration.Provider à la classe Application

Vous pouvez utiliser une initialisation à la demande en implémentant l'interface Configuration.Provider de WorkManager dans la classe Application. La première fois que votre application récupère l'instance WorkManager à l'aide de getInstance(context), WorkManager s'initialise en utilisant la configuration renvoyée par getWorkManagerConfiguration().

BlurApplication.kt

class BlurApplication : Application(), Configuration.Provider {

    override fun getWorkManagerConfiguration(): Configuration =

        Configuration.Builder()
                     .setMinimumLoggingLevel(android.util.Log.DEBUG)
                     .build()
...
}

Avec ce changement, WorkManager s'exécute avec la journalisation définie sur DEBUG.

Il est préférable de configurer WorkManager de cette manière uniquement pour les versions de débogage de votre application, en utilisant par exemple :

BlurApplication.kt

class BlurApplication() : Application(), Configuration.Provider {

    override fun getWorkManagerConfiguration(): Configuration {
        return if (BuildConfig.DEBUG) {
            Configuration.Builder()
                    .setMinimumLoggingLevel(android.util.Log.DEBUG)
                    .build()
        } else {
            Configuration.Builder()
                    .setMinimumLoggingLevel(android.util.Log.ERROR)
                    .build()
        }
    }

...
}

BlurApplication.kt devient alors :

BlurApplication.kt

/* Copyright 2020 Google LLC.
   SPDX-License-Identifier: Apache-2.0 */

package com.example.background

import android.app.Application
import androidx.work.Configuration
import timber.log.Timber
import timber.log.Timber.DebugTree

class BlurApplication() : Application(), Configuration.Provider {

    override fun getWorkManagerConfiguration(): Configuration {
        return if (BuildConfig.DEBUG) {
            Configuration.Builder()
                    .setMinimumLoggingLevel(android.util.Log.DEBUG)
                    .build()
        } else {
            Configuration.Builder()
                    .setMinimumLoggingLevel(android.util.Log.ERROR)
                    .build()
        }
    }

    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            Timber.plant(DebugTree())
        }
    }
}

Étape 3 : Exécutez l'application en mode de débogage

WorkManager est à présent configuré de manière que vos versions de débogage consignent tous les messages provenant de la bibliothèque.

Si vous exécutez l'application, vous pouvez voir que les journaux s'affichent dans l'onglet logcat d'Android Studio :

5f3522812d1bfb18.png

Étape 4 : Quels sont les paramètres configurables ?

La liste complète des paramètres est disponible dans le guide de référence de WorkManager pour Configuration.Builder. Faites attention à deux paramètres supplémentaires :

  • WorkerFactory
  • Plage JobId

La modification de la valeur WorkerFactory permet d'ajouter des paramètres au constructeur de votre nœud de calcul. Pour en savoir plus sur l'implémentation d'un WorkerFactory personnalisé, consultez l'article Customizing WorkManager (Personnaliser WorkManager). Si vous utilisez WorkManager ainsi que l'API JobScheduler dans votre application, nous vous recommandons de personnaliser la plage JobId pour éviter que la même plage JobId soit utilisée par les deux API. Une règle lint, introduite dans la version 2.4.0, traite également de ce cas.

WorkManager v2.3 intègre une nouvelle fonctionnalité permettant de partager avec votre application les informations sur l'avancement du nœud de calcul à l'aide de setProgressAsync() (ou de setProgress() si vous partez d'un CoroutineWorker). Ces informations sont accessibles via WorkInfo. Elles sont destinées à fournir à l'utilisateur des indications dans l'interface. Les données de progression sont ensuite annulées lorsque le nœud de calcul atteint un état final SUCCEEDED, FAILED ou CANCELLED (RÉUSSITE, ÉCHEC ou ANNULÉ). Pour en savoir plus sur la publication et l'écoute de la progression, consultez l'article Observing intermediate Worker progress (Observer la progression d'un nœud de calcul intermédiaire).

Vous allez maintenant ajouter une barre de progression dans l'interface afin que l'utilisateur puisse voir la progression du floutage lorsque l'application se trouve au premier plan. Le résultat final ressemble à ceci :

3ca52d773a4d0e8f.png

Étape 1 : Modifiez ProgressBar

Pour modifier ProgressBar dans la mise en page, vous devez supprimer le paramètre android:indeterminate="true", ajouter le style style="@android:style/Widget.ProgressBar.Horizontal", et définir une valeur initiale avec android:progress="0" Vous devez aussi définir l'orientation LinearLayout sur "vertical" :

app/src/main/res/layout/activity_blur.xml

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <ProgressBar
        android:id="@+id/progress_bar"
        style="@android:style/Widget.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:progress="0"
        android:visibility="gone"
        android:layout_gravity="center_horizontal"
        />

    <Button
        android:id="@+id/cancel_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/cancel_work"
        android:visibility="gone"
        />
</LinearLayout>

L'autre modification nécessaire consiste à s'assurer que ProgressBar redémarre à la position initiale. Pour ce faire, mettez à jour la fonction showWorkFinished() dans le fichier BlurActivity.kt :

app/src/main/java/com/example/background/BlurActivity.kt

/**
 * Shows and hides views for when the Activity is done processing an image
 */
private fun showWorkFinished() {
    with(binding) {
        progressBar.visibility = View.GONE
        cancelButton.visibility = View.GONE
        goButton.visibility = View.VISIBLE
        progressBar.progress = 0 // <-- ADD THIS LINE
    }
}

Étape 2 : Observez les informations sur la progression dans ViewModel

Le fichier BlurViewModel comporte déjà un observateur qui vérifie quand votre chaîne est terminée. Ajoutez-en un autre pour observer la progression publiée par BlurWorker.

Tout d'abord, ajoutez des constantes pour en effectuer le suivi à la fin du fichier Constants.kt :

app/src/main/java/com/example/background/Constants.kt

// Progress Data Key
const val PROGRESS = "PROGRESS"
const val TAG_PROGRESS = "TAG_PROGRESS"

L'étape suivante consiste à ajouter ce tag au WorkRequest de BlurWorker dans le fichier BlurViewModel.kt afin de pouvoir récupérer WorkInfo. Depuis WorkInfo, vous pouvez récupérer les informations sur la progression du nœud de calcul :

app/src/main/java/com/example/background/BlurViewModel.kt

// Add WorkRequests to blur the image the number of times requested
for (i in 0 until blurLevel) {
    val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()

    // Input the Uri if this is the first blur operation
    // After the first blur operation the input will be the output of previous
    // blur operations.
    if (i == 0) {
        blurBuilder.setInputData(createInputDataForUri())
    }

    blurBuilder.addTag(TAG_PROGRESS) // <-- ADD THIS
    continuation = continuation.then(blurBuilder.build())
}

Ajoutez un nouveau LiveData au fichier BlurViewModel.kt qui effectue le suivi de WorkRequest, puis initialisez LiveData dans le bloc init :

app/src/main/java/com/example/background/BlurViewModel.kt

class BlurViewModel(application: Application) : AndroidViewModel(application) {

    internal var imageUri: Uri? = null
    internal var outputUri: Uri? = null
    internal val outputWorkInfoItems: LiveData<List<WorkInfo>>
    internal val progressWorkInfoItems: LiveData<List<WorkInfo>> // <-- ADD THIS
    private val workManager: WorkManager = WorkManager.getInstance(application)

    init {
        // This transformation makes sure that whenever the current work Id changes the WorkStatus
        // the UI is listening to changes
        outputWorkInfoItems = workManager.getWorkInfosByTagLiveData(TAG_OUTPUT)
        progressWorkInfoItems = workManager.getWorkInfosByTagLiveData(TAG_PROGRESS) // <-- ADD THIS
    }

...
}

Étape 3 : Observez LiveData dans l'activité

Vous pouvez désormais utiliser LiveData dans BlurActivity pour observer l'intégralité de la progression publiée. Enregistrez tout d'abord un nouvel observateur LiveData à la fin de la méthode onCreate() :

app/src/main/java/com/example/background/BlurActivity.kt

// Show work status
viewModel.outputWorkInfoItems.observe(this, outputObserver())

// ADD THE FOLLOWING LINES
// Show work progress
viewModel.progressWorkInfoItems.observe(this, progressObserver())

Vous pouvez maintenant vérifier le WorkInfo reçu dans l'observateur pour voir s'il existe des informations sur la progression, puis mettre à jour ProgressBar en conséquence :

app/src/main/java/com/example/background/BlurActivity.kt

private fun progressObserver(): Observer<List<WorkInfo>> {
    return Observer { listOfWorkInfo ->
        if (listOfWorkInfo.isNullOrEmpty()) {
            return@Observer
        }

        listOfWorkInfo.forEach { workInfo ->
            if (WorkInfo.State.RUNNING == workInfo.state) {
                val progress = workInfo.progress.getInt(PROGRESS, 0)
                binding.progressBar.progress = progress
            }
        }

    }
}

Étape 4 : Publiez la progression depuis BlurWorker

Tous les éléments nécessaires à l'affichage des informations sur la progression sont désormais en place. Il est temps d'ajouter la publication réelle des informations sur la progression dans BlurWorker.

Cet exemple simule simplement un long processus dans notre fonction doWork() afin de pouvoir publier des informations sur la progression sur une période définie.

La modification ici consiste à remplacer un seul retard par 10 petits retards, afin de définir une nouvelle progression à chaque itération :

app/src/main/java/com/example/background/workers/BlurWorker.kt

override fun doWork(): Result {
    val appContext = applicationContext

    val resourceUri = inputData.getString(KEY_IMAGE_URI)

    makeStatusNotification("Blurring image", appContext)
    // sleep()
    (0..100 step 10).forEach {
        setProgressAsync(workDataOf(PROGRESS to it))
        sleep()
    }

...
}

Comme le retard initial était de trois secondes, il est probablement judicieux de le réduire également d'un facteur 10, à 0,3 seconde :

app/src/main/java/com/example/background/Constants.kt

// const val DELAY_TIME_MILLIS: Long = 3000
const val DELAY_TIME_MILLIS: Long = 300

Étape 5 : Exécutez l'application

Si vous exécutez l'application à ce stade, la barre de progression devrait s'afficher avec les messages provenant de BlurWorker.

Le test constitue une phase essentielle pour chaque application. Lorsque vous introduisez une bibliothèque telle que WorkManager, il est important de fournir les outils permettant de tester facilement votre code.

Avec WorkManager, nous avons aussi mis à disposition quelques assistants pour tester facilement vos nœuds de calcul. Pour en savoir plus sur la création de tests pour vos nœuds de calcul, consultez la documentation de WorkManager sur les tests.

Dans cette section de l'atelier de programmation, nous allons effectuer des tests pour les classes de nœud de calcul, pour quelques cas d'utilisation courants.

Tout d'abord, nous souhaitons fournir un moyen simple de configurer les tests. Pour ce faire, nous pouvons créer une règle TestRule qui configure WorkManager :

  • Ajouter des dépendances
  • Créer WorkManagerTestRule et TestUtils
  • Créer un test pour CleanupWorker
  • Créer un test pour BlurWorker

Nous supposons que vous avez déjà créé le dossier AndroidTest dans votre projet. Vous devez alors ajouter des dépendances à utiliser dans les tests :

app/build.gradle

androidTestImplementation "androidx.arch.core:core-testing:2.1.0"
androidTestImplementation "androidx.test.ext:junit:1.1.1"
androidTestImplementation "androidx.test:rules:1.2.0"
androidTestImplementation "androidx.test:runner:1.2.0"
androidTestImplementation "androidx.work:work-testing:$versions.work"

Nous pouvons maintenant commencer à assembler les pièces du puzzle avec une règle TestRule, que nous pouvons utiliser dans les tests :

app/src/androidTest/java/com/example/background/workers/WorkManagerTestRule.kt

/* Copyright 2020 Google LLC.
   SPDX-License-Identifier: Apache-2.0 */

package com.example.background.workers

import android.content.Context
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import androidx.work.Configuration
import androidx.work.WorkManager
import androidx.work.testing.SynchronousExecutor
import androidx.work.testing.WorkManagerTestInitHelper
import org.junit.rules.TestWatcher
import org.junit.runner.Description

class WorkManagerTestRule : TestWatcher() {
    lateinit var targetContext: Context
    lateinit var testContext: Context
    lateinit var configuration: Configuration
    lateinit var workManager: WorkManager

    override fun starting(description: Description?) {
        targetContext = InstrumentationRegistry.getInstrumentation().targetContext
        testContext = InstrumentationRegistry.getInstrumentation().context
        configuration = Configuration.Builder()
                // Set log level to Log.DEBUG to make it easier to debug
                .setMinimumLoggingLevel(Log.DEBUG)
                // Use a SynchronousExecutor here to make it easier to write tests
                .setExecutor(SynchronousExecutor())
                .build()

        // Initialize WorkManager for instrumentation tests.
        WorkManagerTestInitHelper.initializeTestWorkManager(targetContext, configuration)
        workManager = WorkManager.getInstance(targetContext)
    }
}

Puisque nous avons besoin de cette image de test sur l'appareil (où les tests seront exécutés), nous allons créer des fonctions d'assistance à utiliser dans les tests :

app/src/androidTest/java/com/example/background/workers/TestUtils.kt

/* Copyright 2020 Google LLC.
   SPDX-License-Identifier: Apache-2.0 */

package com.example.background.workers

import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import com.example.background.OUTPUT_PATH
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.util.UUID

/**
 * Copy a file from the asset folder in the testContext to the OUTPUT_PATH in the target context.
 * @param testCtx android test context
 * @param targetCtx target context
 * @param filename source asset file
 * @return Uri for temp file
 */
@Throws(Exception::class)
fun copyFileFromTestToTargetCtx(testCtx: Context, targetCtx: Context, filename: String): Uri {
    // Create test image
    val destinationFilename = String.format("blur-test-%s.png", UUID.randomUUID().toString())
    val outputDir = File(targetCtx.filesDir, OUTPUT_PATH)
    if (!outputDir.exists()) {
        outputDir.mkdirs()
    }
    val outputFile = File(outputDir, destinationFilename)

    val bis = BufferedInputStream(testCtx.assets.open(filename))
    val bos = BufferedOutputStream(FileOutputStream(outputFile))
    val buf = ByteArray(1024)
    bis.read(buf)
    do {
        bos.write(buf)
    } while (bis.read(buf) != -1)
    bis.close()
    bos.close()

    return Uri.fromFile(outputFile)
}

/**
 * Check if a file exists in the given context.
 * @param testCtx android test context
 * @param uri for the file
 * @return true if file exist, false if the file does not exist of the Uri is not valid
 */
fun uriFileExists(targetCtx: Context, uri: String?): Boolean {
    if (uri.isNullOrEmpty()) {
        return false
    }

    val resolver = targetCtx.contentResolver

    // Create a bitmap
    try {
        BitmapFactory.decodeStream(
                resolver.openInputStream(Uri.parse(uri)))
    } catch (e: FileNotFoundException) {
        return false
    }
    return true
}

Une fois que c'est fait, nous pouvons commencer à écrire les tests.

Nous allons commencer par tester CleanupWorker, afin de vérifier qu'il supprime bien nos fichiers. Pour ce faire, copiez l'image de test sur l'appareil dans le test, puis vérifiez si elle est là après que CleanupWorker a été exécuté :

app/src/androidTest/java/com/example/background/workers/CleanupWorkerTest.kt

/* Copyright 2020 Google LLC.
   SPDX-License-Identifier: Apache-2.0 */

package com.example.background.workers

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import org.hamcrest.CoreMatchers.`is`
import org.junit.Assert.assertThat
import org.junit.Rule
import org.junit.Test

class CleanupWorkerTest {

    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()
    @get:Rule
    var wmRule = WorkManagerTestRule()

    @Test
    fun testCleanupWork() {
        val testUri = copyFileFromTestToTargetCtx(
                wmRule.testContext, wmRule.targetContext, "test_image.png")
        assertThat(uriFileExists(wmRule.targetContext, testUri.toString()), `is`(true))

        // Create request
        val request = OneTimeWorkRequestBuilder<CleanupWorker>().build()

        // Enqueue and wait for result. This also runs the Worker synchronously
        // because we are using a SynchronousExecutor.
        wmRule.workManager.enqueue(request).result.get()
        // Get WorkInfo
        val workInfo = wmRule.workManager.getWorkInfoById(request.id).get()

        // Assert
        assertThat(uriFileExists(wmRule.targetContext, testUri.toString()), `is`(false))
        assertThat(workInfo.state, `is`(WorkInfo.State.SUCCEEDED))
    }
}

Vous pouvez à présent exécuter ce test depuis le menu "Exécuter" d'Android Studio ou en utilisant le rectangle vert à gauche de votre classe de test :

be955a84b5b00400.png

Vous pouvez aussi exécuter vos tests depuis une ligne de commande, en utilisant la commande ./gradlew cAT depuis le dossier racine de votre projet.

Les tests devraient s'exécuter correctement.

Nous pouvons ensuite tester BlurWorker. Ce nœud de calcul attend une donnée d'entrée avec l'URI de l'image à traiter. Nous pouvons donc créer deux tests : un qui vérifie que le nœud de calcul échoue en l'absence d'URI d'entrée et un autre qui traite l'image d'entrée.

app/src/androidTest/java/com/example/background/workers/BlurWorkerTest.kt

/* Copyright 2020 Google LLC.
   SPDX-License-Identifier: Apache-2.0 */

package com.example.background.workers

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.workDataOf
import org.hamcrest.CoreMatchers.`is`
import org.junit.Assert.assertThat
import org.junit.Rule
import com.example.background.KEY_IMAGE_URI
import org.junit.Test

class BlurWorkerTest {

    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()
    @get:Rule
    var wmRule = WorkManagerTestRule()

    @Test
    fun testFailsIfNoInput() {
        // Define input data

        // Create request
        val request = OneTimeWorkRequestBuilder<BlurWorker>().build()

        // Enqueue and wait for result. This also runs the Worker synchronously
        // because we are using a SynchronousExecutor.
        wmRule.workManager.enqueue(request).result.get()
        // Get WorkInfo
        val workInfo = wmRule.workManager.getWorkInfoById(request.id).get()

        // Assert
        assertThat(workInfo.state, `is`(WorkInfo.State.FAILED))
    }

    @Test
    @Throws(Exception::class)
    fun testAppliesBlur() {
        // Define input data
        val inputDataUri = copyFileFromTestToTargetCtx(
                wmRule.testContext,
                wmRule.targetContext,
                "test_image.png")
        val inputData = workDataOf(KEY_IMAGE_URI to inputDataUri.toString())

        // Create request
        val request = OneTimeWorkRequestBuilder<BlurWorker>()
                .setInputData(inputData)
                .build()

        // Enqueue and wait for result. This also runs the Worker synchronously
        // because we are using a SynchronousExecutor.
        wmRule.workManager.enqueue(request).result.get()
        // Get WorkInfo
        val workInfo = wmRule.workManager.getWorkInfoById(request.id).get()
        val outputUri = workInfo.outputData.getString(KEY_IMAGE_URI)

        // Assert
        assertThat(uriFileExists(wmRule.targetContext, outputUri), `is`(true))
        assertThat(workInfo.state, `is`(WorkInfo.State.SUCCEEDED))
    }
}

Si vous exécutez ces tests, les deux devraient réussir.

Félicitations ! Vous venez de terminer l'application Blur-O-Matic. Vous savez maintenant :

  • créer une configuration personnalisée ;
  • publier la progression à partir du nœud de calcul ;
  • afficher la progression du travail dans l'interface utilisateur ;
  • écrire des tests pour vos nœuds de calcul.

Beau travail ! Pour voir l'état final du code et toutes les modifications, consultez la page :

Code final de Blur-o-Matic

Ou, si vous préférez, vous pouvez cloner l'atelier de programmation de WorkManager à partir de GitHub :

$ git clone -b advanced https://github.com/googlecodelabs/android-workmanager

Workmanager est compatible avec bien d'autres fonctionnalités que nous n'avons pu traiter dans cet atelier de programmation. Pour en savoir plus, consultez la documentation de WorkManager.