1. Antes de começar
Este codelab abrange o WorkManager, uma biblioteca compatível com versões anteriores, flexível e simples para trabalhos em segundo plano adiáveis. O WorkManager é o programador de tarefas recomendado para Android para trabalhos adiáveis, com garantia de execução.
Pré-requisitos
- Conhecimento sobre StateFlow e ViewModel. Se você ainda não conhece essas classes, confira o codelab ViewModel e estado no Compose (especificamente para ViewModel e estados) ou Ler e atualizar dados com o Room (especificamente para fluxos e StateFlow).
- Conhecimento sobre repositórios e injeção de dependências. Para relembrar, confira Adicionar repositório e DI manual.
- Saber implementar corrotinas no app.
O que você vai aprender
- Como adicionar o WorkManager ao projeto.
- Como agendar uma tarefa simples.
- Como configurar parâmetros de entrada e saída para workers.
- Como encadear workers.
O que você vai fazer
- Modificar um app inicial para usar o WorkManager.
- Implementar uma solicitação de trabalho para desfocar uma imagem.
- Implementar um grupo em série de trabalhos encadeados.
- Transmitir dados para dentro e para fora do trabalho que está sendo agendado.
O que é necessário
- A versão estável mais recente do Android Studio.
- Uma conexão com a Internet.
2. Visão geral do app
Atualmente, os smartphones são muito bons para tirar fotos. Tirar fotos desfocadas de algo misterioso é coisa do passado.
Neste codelab, você vai trabalhar com o Blur-O-Matic, um app que desfoca fotos e salva os resultados em um arquivo. Aquilo era o monstro do Lago Ness ou um submarino de brinquedo (link em inglês)? Com o Blur-O-Matic, ninguém vai saber!
| 
 | 
 | 
A tela tem botões de opção em que você pode selecionar o nível de desfoque da imagem. Clicar no botão Start (começar) desfoca e salva a imagem.
No momento, o app não aplica desfoque nem salva a imagem final.
O foco deste codelab é adicionar o WorkManager ao app, criar workers para limpar arquivos temporários criados para desfocar uma imagem, desfocar a imagem e salvar uma cópia final dela, que pode ser visualizada ao clicar no botão See File (mostrar arquivo). Você também vai aprender a monitorar o status do trabalho em segundo plano e a atualizar a interface do app.
3. Conhecer o app inicial Blur-O-Matic
Acessar o código inicial
Para começar, faça o download do código inicial:
Outra opção é clonar o repositório do GitHub:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git $ cd basic-android-kotlin-compose-training-workmanager $ git checkout starter
Procure o código do app Blur-o-matic neste repositório do GitHub (em inglês).
Executar o código inicial
Para se familiarizar com o código inicial, siga estas etapas:
- Abra o projeto com o código inicial no Android Studio.
- Execute o app em um dispositivo Android ou em um emulador.

A tela tem botões de opção que permitem selecionar o nível de desfoque da imagem. Quando você clica no botão Start, o app desfoca e salva a imagem.
No momento, o app não aplica desfoque quando você clica no botão Start.
Tutorial do código inicial
Nesta tarefa, você vai conhecer a estrutura do projeto. Confira abaixo explicações sobre os arquivos e as pastas mais importantes desse projeto.
- WorkerUtils: métodos práticos que vão ser usados mais tarde para mostrar- Notificationse o código para salvar um bitmap no arquivo.
- BlurViewModel: esse modelo de visualização armazena o estado do app e interage com o repositório.
- WorkManagerBluromaticRepository: a classe em que você inicia o trabalho em segundo plano com o WorkManager.
- Constants: uma classe estática com algumas constantes usadas durante o codelab.
- BluromaticScreen: contém funções combináveis para a interface e interage com o- BlurViewModel. As funções combináveis mostram a imagem e incluem botões de opção para selecionar o nível de desfoque desejado.
4. O que é o WorkManager?
O WorkManager faz parte do Android Jetpack e de um componente de arquitetura para trabalho em segundo plano que requer uma combinação de execução oportunista e garantida. Execução oportunista significa que o WorkManager faz o trabalho em segundo plano o quanto antes. Já a execução garantida se refere à lógica para iniciar o trabalho em diversas situações, mesmo se você sair do app.
O WorkManager é uma biblioteca incrivelmente flexível, que oferece diversos outros benefícios. Alguns desses benefícios incluem:
- Suporte a tarefas únicas e periódicas assíncronas.
- Suporte a restrições, por exemplo, condições de rede, espaço de armazenamento e status de carregamento.
- Encadeamento de solicitações de trabalho complexas, por exemplo, a execução de trabalhos em paralelo.
- Usar a saída de uma solicitação de trabalho como entrada para a próxima.
- Suporte a níveis anteriores da API até o 14 (confira a observação).
- Trabalhar com ou sem o Google Play Services.
- Segue as práticas recomendadas de integridade do sistema.
- Oferece suporte à exibição facilitada do estado das solicitações de trabalho na interface do app.
5. Quando usar o WorkManager?
O WorkManager é uma boa opção para tarefas que precisam ser concluídas. A execução dessas tarefas não depende que o app continue em execução depois que o trabalho é colocado na fila. As tarefas são executadas mesmo se o app for fechado ou se o usuário retornar à tela inicial.
Alguns exemplos de tarefas que fazem um bom uso do WorkManager:
- Consultar periodicamente as últimas notícias.
- Aplicar filtros a uma imagem e salvá-la.
- Sincronização periódica de dados locais com a rede.
O WorkManager é uma opção para executar uma tarefa fora da linha de execução principal, mas não é abrangente o suficiente para executar todos os tipos de tarefa fora da linha de execução principal. Corrotinas são uma outra opção discutida nos codelabs anteriores.
Para saber mais sobre quando usar o WorkManager, confira o Guia para o processamento em segundo plano.
6. Adicionar o WorkManager ao app
O WorkManager requer a dependência do Gradle mostrada abaixo. Ela já está incluída no arquivo de build.
app/build.gradle.kts
dependencies {
    // WorkManager dependency
    implementation("androidx.work:work-runtime-ktx:2.8.1")
}
É necessário usar a versão mais recente da versão estável do work-runtime-ktx no app.
Se você mudar a versão, clique em Sync Now para sincronizar o projeto com os arquivos atualizados do Gradle.
7. Noções básicas do WorkManager
Há algumas classes do WorkManager que você precisa conhecer:
- Worker/- CoroutineWorker: Worker é uma classe que executa o trabalho de forma síncrona em uma linha de execução em segundo plano. Como temos interesse no trabalho assíncrono, podemos usar a classe CoroutineWorker, que tem interoperabilidade com as corrotinas do Kotlin. Neste app, você estende a classe CoroutineWorker e substitui o método- doWork(), em que você coloca o código do trabalho real que quer realizar em segundo plano.
- WorkRequest: representa uma solicitação para realizar algum trabalho. Uma- WorkRequesté o local onde você define se o worker precisa ser executado uma vez ou periodicamente. Também é possível impor restrições na- WorkRequestque exigem determinadas condições antes da execução do trabalho. Por exemplo, que o dispositivo esteja carregando antes de iniciar o trabalho solicitado. Você transmite o- CoroutineWorkercomo parte da criação da- WorkRequest.
- WorkManager: essa classe agenda e executa a- WorkRequest. Ela programa uma- WorkRequestde modo a distribuir a carga nos recursos do sistema, respeitando as restrições especificadas.
No seu caso, você define uma nova classe BlurWorker, que contém o código para desfocar uma imagem. Quando você clica no botão Start, o WorkManager cria e depois enfileira um objeto WorkRequest.
8. Criar o BlurWorker
Nesta etapa, você vai usar uma imagem na pasta res/drawable chamada android_cupcake.png e executar algumas funções nela em segundo plano. Essas funções desfocam a imagem.
- Clique com o botão direito do mouse no pacote com.example.bluromatic.workersno painel do projeto Android e selecione New -> Kotlin Class/File.
- Dê o nome BlurWorkerà nova classe Kotlin. Estenda-a doCoroutineWorkercom os parâmetros obrigatórios do construtor.
workers/BlurWorker.kt
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import android.content.Context
class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
}
A classe do BlurWorker estende a classe do CoroutineWorker, em vez da classe mais geral Worker. A implementação da classe CoroutineWorker do doWork() é uma função de suspensão, que permite executar um código assíncrono, o que não pode ser realizado por um Worker. Como detalhado no guia Linhas de execução no WorkManager, "CoroutineWorker é a implementação recomendada para usuários do Kotlin".
Nesse ponto, o Android Studio mostra uma linha ondulada vermelha em class BlurWorker, que indica um erro.

Se você colocar o cursor sobre o texto class BlurWorker, o ambiente de desenvolvimento integrado vai mostrar um pop-up com mais informações sobre o erro.

A mensagem de erro indica que você não substituiu o método doWork() conforme necessário.
No método doWork(), crie o código para desfocar a imagem mostrada do cupcake.
Siga estas etapas para corrigir o erro e implementar o método doWork():
- Posicione o cursor dentro do código da classe, clicando no texto "BlurWorker".
- No menu do Android Studio, selecione Code > Override Methods...
- No pop-up Override Members, selecione doWork().
- Clique em OK.

- Imediatamente antes da declaração de classe, crie uma variável com o nome TAGe atribua a ela o valorBlurWorker. Essa variável não está relacionada especificamente ao métododoWork(), mas é usada mais tarde em chamadas paraLog().
workers/BlurWorker.kt
private const val TAG = "BlurWorker"
class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
... 
- Para ver melhor quando o trabalho é executado, você precisa usar a função makeStatusNotification()doWorkerUtil. Essa função permite que você mostre facilmente um banner de notificação na parte de cima da tela.
No método doWork(), use a função makeStatusNotification() para exibir uma notificação de status e informar ao usuário que o worker de desfoque foi iniciado e está desfocando a imagem.
workers/BlurWorker.kt
import com.example.bluromatic.R
...
override suspend fun doWork(): Result {
    makeStatusNotification(
        applicationContext.resources.getString(R.string.blurring_image),
        applicationContext
    )
...
- Adicione um bloco de código return try...catch, em que o trabalho real da imagem de desfoque é realizado.
workers/BlurWorker.kt
...
        makeStatusNotification(
            applicationContext.resources.getString(R.string.blurring_image),
            applicationContext
        )
        return try {
        } catch (throwable: Throwable) {
        }
...
- No bloco try, adicione uma chamada paraResult.success().
- No bloco catch, adicione uma chamada paraResult.failure().
workers/BlurWorker.kt
...
        makeStatusNotification(
            applicationContext.resources.getString(R.string.blurring_image),
            applicationContext
        )
        return try {
            Result.success()
        } catch (throwable: Throwable) {
            Result.failure()
        }
...
- No bloco try, crie uma nova variável chamadapicturee a preencha com o bitmap retornado da chamada do métodoBitmapFactory.decodeResource()e da transmissão do pacote de recursos do aplicativo e o ID do recurso da imagem do cupcake.
workers/BlurWorker.kt
...
        return try {
            val picture = BitmapFactory.decodeResource(
                applicationContext.resources,
                R.drawable.android_cupcake
            )
            Result.success()
...
- Desfoque o bitmap chamando a função blurBitmap()e transmita a variávelpicturee um valor de1(um) para o parâmetroblurLevel.
- Salve o resultado em uma nova variável com o nome output.
workers/BlurWorker.kt
...
            val picture = BitmapFactory.decodeResource(
                applicationContext.resources,
                R.drawable.android_cupcake
            )
            val output = blurBitmap(picture, 1)
            Result.success()
...
- Crie uma nova variável outputUrie a preencha com uma chamada para a funçãowriteBitmapToFile().
- Na chamada para writeBitmapToFile(), transmita o contexto do aplicativo e a variáveloutputcomo argumentos.
workers/BlurWorker.kt
...
            val output = blurBitmap(picture, 1)
            // Write bitmap to a temp file
            val outputUri = writeBitmapToFile(applicationContext, output)
            Result.success()
...
- Adicione o código para mostrar uma mensagem de notificação ao usuário com a variável outputUri.
workers/BlurWorker.kt
...
            val outputUri = writeBitmapToFile(applicationContext, output)
            makeStatusNotification(
                "Output is $outputUri",
                applicationContext
            )
            Result.success()
...
- No bloco catch, registre uma mensagem indicando que ocorreu um erro ao tentar desfocar a imagem. A chamada paraLog.e()transmite a variávelTAGdefinida anteriormente, uma mensagem adequada e a exceção que está sendo gerada.
workers/BlurWorker.kt
...
        } catch (throwable: Throwable) {
            Log.e(
                TAG,
                applicationContext.resources.getString(R.string.error_applying_blur),
                throwable
            )
            Result.failure()
        }
...
Por padrão, um CoroutineWorker, é executado como Dispatchers.Default, mas pode ser mudado chamando withContext() e transmitindo o agente desejado.
- Crie um bloco withContext().
- Na chamada para withContext(), transmitaDispatchers.IOpara que a função lambda seja executada em um pool de linhas de execução especial para possivelmente bloquear operações de E/S.
- Mova o código return try...catchcriado anteriormente para esse bloco.
...
        return withContext(Dispatchers.IO) {
            return try {
                // ...
            } catch (throwable: Throwable) {
                // ...
            }
        }
...
O Android Studio mostra o erro abaixo porque não é possível chamar return em uma função lambda.

Para corrigir esse erro, adicione um rótulo, conforme mostrado no pop-up.
...
            //return try {
            return@withContext try {
...
Como esse worker é executado muito rapidamente, é recomendável adicionar um atraso no código para emular um trabalho mais lento.
- No lambda withContext(), adicione uma chamada para a função utilitáriadelay()e transmita a constanteDELAY_TIME_MILLIS. Essa chamada é apenas para que o codelab forneça um atraso entre as mensagens de notificação.
import com.example.bluromatic.DELAY_TIME_MILLIS
import kotlinx.coroutines.delay
...
        return withContext(Dispatchers.IO) {
            // This is an utility function added to emulate slower work.
            delay(DELAY_TIME_MILLIS)
                val picture = BitmapFactory.decodeResource(
...
9. Atualizar o WorkManagerBluromaticRepository
O repositório processa todas as interações com o WorkManager. Essa estrutura está de acordo com o princípio de design de separação de conceitos e é um padrão recomendado de arquitetura do Android.
- No arquivo data/WorkManagerBluromaticRepository.kt, dentro da classeWorkManagerBluromaticRepository, crie uma variável particular com o nomeworkManagere armazene uma instância deWorkManagernela chamandoWorkManager.getInstance(context).
data/WorkManagerBluromaticRepository.kt
import androidx.work.WorkManager
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {
    // New code
    private val workManager = WorkManager.getInstance(context)
...
Criar e enfileirar a WorkRequest no WorkManager
Muito bem. Agora vamos fazer uma WorkRequest e pedir para o WorkManager executá-la. Há dois tipos de WorkRequests:
- OneTimeWorkRequest: uma- WorkRequestque é executada apenas uma vez.
- PeriodicWorkRequest: uma- WorkRequestque é executada repetidas vezes em um ciclo.
É importante que a imagem seja desfocada uma vez, quando o usuário clicar no botão Start.
Esse trabalho ocorre no método applyBlur(), que é chamado quando você clica no botão Start.
As etapas abaixo são concluídas no método applyBlur().
- Para preencher uma nova variável com o nome blurBuilder, crie umaOneTimeWorkRequestpara o worker de desfoque e chame a função de extensãoOneTimeWorkRequestBuilderdo WorkManager KTX.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
    // Create WorkRequest to blur the image
    val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
}
- Inicie o trabalho chamando o método enqueue()no objetoworkManager.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
    // Create WorkRequest to blur the image
    val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
    // Start the work
    workManager.enqueue(blurBuilder.build())
}
- Execute o app e confira a notificação ao clicar no botão Start.
No momento, a imagem é desfocada no mesmo nível, independente da opção selecionada. Nas próximas etapas, o nível de desfoque vai mudar com base na opção selecionada.

Para confirmar se a imagem está sendo desfocada corretamente, abra o Device Explorer no Android Studio:

Depois navegue até data > data > com.example.bluromatic > files > blur_filter_outputs > <URI> e confirme se a imagem do cupcake está sendo desfocada:

10. Dados de entrada e saída
Desfocar o recurso de imagem no diretório de recursos funciona, mas, para que o Blur-O-Matic seja revolucionário como está destinado a ser, você precisa permitir que o usuário desfoque a imagem exibida na tela e depois mostre o resultado desfocado.
Para fazer isso, fornecemos o URI da imagem mostrada do cupcake como entrada para a WorkRequest e usamos a saída da WorkRequest para exibir a imagem desfocada final.

A entrada e a saída são transmitidas para um worker por objetos Data. Objetos Data são contêineres leves para pares de chave-valor. Eles armazenam uma pequena quantidade de dados que podem entrar e sair de um worker pela WorkRequest.
Na próxima etapa, você vai transmitir o URI para o BlurWorker criando um objeto de dados de entrada.
Criar um objeto de dados de entrada
- No arquivo data/WorkManagerBluromaticRepository.kt, dentro da classeWorkManagerBluromaticRepository, crie uma nova variável particular com o nomeimageUri.
- Preencha a variável com o URI da imagem, chamando o método de contexto getImageUri().
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.getImageUri
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {
    private var imageUri: Uri = context.getImageUri() // <- Add this
    private val workManager = WorkManager.getInstance(context)
...
O código do app contém a função auxiliar createInputDataForWorkRequest() para criar objetos de dados de entrada.
data/WorkManagerBluromaticRepository.kt
// For reference - already exists in the app
private fun createInputDataForWorkRequest(blurLevel: Int, imageUri: Uri): Data {
    val builder = Data.Builder()
    builder.putString(KEY_IMAGE_URI, imageUri.toString()).putInt(BLUR_LEVEL, blurLevel)
    return builder.build()
}
Primeiro, a função auxiliar cria um objeto Data.Builder. Em seguida, coloca imageUri e blurLevel nelas como pares de chave-valor. Um objeto de dados é criado e retornado quando chama return builder.build().
- Para definir o objeto de dados de entrada para a WorkRequest, chame o método blurBuilder.setInputData(). É possível criar e transmitir o objeto de dados em uma etapa chamando a função auxiliarcreateInputDataForWorkRequest()como argumento. Para chamar a funçãocreateInputDataForWorkRequest(), transmita as variáveisblurLeveleimageUri.
data/WorkManagerBluromaticRepository.kt
override fun applyBlur(blurLevel: Int) {
     // Create WorkRequest to blur the image
    val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
    // New code for input data object
    blurBuilder.setInputData(createInputDataForWorkRequest(blurLevel, imageUri))
    workManager.enqueue(blurBuilder.build())
}
Acessar o objeto de dados de entrada
Agora, vamos atualizar o método doWork() na classe BlurWorker para acessar o URI e o nível de desfoque transmitido pelo objeto de dados de entrada. Se um valor de blurLevel não tiver sido fornecido, o padrão vai ser 1.
No método doWork():
- Crie uma nova variável com o nome resourceUrie a preencha chamandoinputData.getString()e transmitindo a constanteKEY_IMAGE_URIusada como chave ao criar o objeto de dados de entrada.
val resourceUri = inputData.getString(KEY_IMAGE_URI)
- Crie uma nova variável com o nome blurLevel. Preencha a variável chamandoinputData.getInt()e transmitindo a constanteBLUR_LEVELque foi usada como chave ao criar o objeto de dados de entrada. Caso esse par de chave-valor não tenha sido criado, forneça um valor padrão de1(um).
workers/BlurWorker.kt
import com.example.bluromatic.KEY_BLUR_LEVEL
import com.example.bluromatic.KEY_IMAGE_URI
...
override fun doWork(): Result {
    // ADD THESE LINES
    val resourceUri = inputData.getString(KEY_IMAGE_URI)
    val blurLevel = inputData.getInt(KEY_BLUR_LEVEL, 1)
    // ... rest of doWork()
}
Com o URI, agora vamos desfocar a imagem do cupcake na tela.
- Verifique se a variável resourceUriestá preenchida. Se não estiver, uma exceção será gerada. O código abaixo usa a instruçãorequire()(link em inglês), que gera umaIllegalArgumentExceptionquando o primeiro argumento é falso.
workers/BlurWorker.kt
return@withContext try {
    // NEW code
    require(!resourceUri.isNullOrBlank()) {
        val errorMessage =
            applicationContext.resources.getString(R.string.invalid_input_uri)
            Log.e(TAG, errorMessage)
            errorMessage
    }
Como a origem da imagem é transmitida como um URI, precisamos de um objeto ContentResolver para ler o conteúdo apontado pelo URI.
- Adicione um objeto contentResolverao valorapplicationContext.
workers/BlurWorker.kt
...
    require(!resourceUri.isNullOrBlank()) {
        // ...
    }
    val resolver = applicationContext.contentResolver
...
- Como a origem da imagem agora é a transmitida em URI, use BitmapFactory.decodeStream()em vez deBitmapFactory.decodeResource()para criar o objeto Bitmap.
workers/BlurWorker.kt
import android.net.Uri
...
//     val picture = BitmapFactory.decodeResource(
//         applicationContext.resources,
//         R.drawable.android_cupcake
//     )
    val resolver = applicationContext.contentResolver
    val picture = BitmapFactory.decodeStream(
        resolver.openInputStream(Uri.parse(resourceUri))
    )
- Transmita a variável blurLevelna chamada da funçãoblurBitmap().
workers/BlurWorker.kt
//val output = blurBitmap(picture, 1)
val output = blurBitmap(picture, blurLevel)
Criar o objeto de dados de saída
Você terminou esse worker e pode retornar o URI de saída como um objeto de dados de saída em Result.success(). Fornecer o URI de saída como um objeto de dados de saída facilita o acesso a outros workers para mais operações. Essa abordagem vai ser útil na próxima seção durante a criação de uma cadeia de workers.
Para isso, siga as etapas abaixo:
- Antes do código Result.success(), crie uma nova variável com o nomeoutputData.
- Para preencher essa variável, chame a função workDataOf()e use a constanteKEY_IMAGE_URIpara a chave e a variáveloutputUricomo o valor. A funçãoworkDataOf()cria um objeto de dados do par de chave-valor transmitido.
workers/BlurWorker.kt
import androidx.work.workDataOf
// ...
val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())
- Atualize o código Result.success()para usar esse novo objeto de dados como argumento.
workers/BlurWorker.kt
//Result.success()
Result.success(outputData)
- Remova o código que mostra a notificação, já que ela não é mais necessária, porque o objeto de dados de saída agora usa o URI.
workers/BlurWorker.kt
// REMOVE the following notification code
//makeStatusNotification(
//    "Output is $outputUri",
//    applicationContext
//)
Executar seu app
Nesse ponto, ao executar o app, você espera que ele seja compilado. A imagem desfocada aparece no Device Explorer, mas ainda não é mostrada na tela.
Talvez seja necessário usar a função Synchronize para mostrar as imagens:

Bom trabalho! Você desfocou uma imagem de entrada usando o WorkManager.
11. Encadear seu trabalho
No momento, você está realizando uma única tarefa: desfocar a imagem. Essa tarefa é uma ótima etapa inicial, mas o app ainda não tem algumas funcionalidades importantes:
- O app não limpa arquivos temporários.
- O app não salva a imagem em um arquivo permanente.
- O app sempre desfoca a imagem no mesmo nível.
Você pode usar uma cadeia de trabalho do WorkManager para adicionar essa funcionalidade. O WorkManager permite que você crie WorkerRequests separadas que são executadas em ordem ou em paralelo.
Nesta seção, você vai criar uma cadeia de trabalho parecida com esta:

As caixas representam as WorkRequests.
Outro recurso de encadeamento é a capacidade de aceitar entrada e produzir saída. A saída de uma WorkRequest se torna a entrada da próxima WorkRequest na cadeia.
Você já tem um CoroutineWorker para desfocar uma imagem, mas também precisa de um CoroutineWorker para limpar arquivos temporários e um CoroutineWorker para salvar a imagem de forma permanente.
Criar o CleanupWorker
O CleanupWorker exclui os arquivos temporários, se houver.
- Clique com o botão direito do mouse no pacote com.example.bluromatic.workersno painel do projeto Android e selecione New -> Kotlin Class/File.
- Dê o nome CleanupWorkerà nova classe Kotlin.
- Copie o código do CleanupWorker.kt, conforme mostrado no exemplo de código abaixo.
Como a manipulação de arquivos está fora do escopo deste codelab, você pode copiar o código abaixo para o CleanupWorker.
workers/CleanupWorker.kt
package com.example.bluromatic.workers
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.OUTPUT_PATH
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.io.File
/**
 * Cleans up temporary files generated during blurring process
 */
private const val TAG = "CleanupWorker"
class CleanupWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
    override suspend fun doWork(): Result {
        /** Makes a notification when the work starts and slows down the work so that it's easier
         * to see each WorkRequest start, even on emulated devices
         */
        makeStatusNotification(
            applicationContext.resources.getString(R.string.cleaning_up_files),
            applicationContext
        )
        return withContext(Dispatchers.IO) {
            delay(DELAY_TIME_MILLIS)
            return@withContext try {
                val outputDirectory = File(applicationContext.filesDir, OUTPUT_PATH)
                if (outputDirectory.exists()) {
                    val entries = outputDirectory.listFiles()
                    if (entries != null) {
                        for (entry in entries) {
                            val name = entry.name
                            if (name.isNotEmpty() && name.endsWith(".png")) {
                                val deleted = entry.delete()
                                Log.i(TAG, "Deleted $name - $deleted")
                            }
                        }
                    }
                }
                Result.success()
            } catch (exception: Exception) {
                Log.e(
                    TAG,
                    applicationContext.resources.getString(R.string.error_cleaning_file),
                    exception
                )
                Result.failure()
            }
        }
    }
}
Criar o SaveImageToFileWorker
A classe SaveImageToFileWorker salva o arquivo temporário em um permanente.
O SaveImageToFileWorker recebe a entrada e saída. A entrada é uma String do URI de imagem temporariamente desfocado, armazenada com a chave KEY_IMAGE_URI. A saída é uma String do URI da imagem desfocada salvo, armazenado com a chave KEY_IMAGE_URI.

- Clique com o botão direito do mouse no pacote com.example.bluromatic.workersno painel do projeto Android e selecione New -> Kotlin Class/File.
- Dê o nome SaveImageToFileWorkerà nova classe Kotlin.
- Copie o código do SaveImageToFileWorker.kt conforme mostrado no código de exemplo abaixo.
Como a manipulação de arquivos está fora do escopo deste codelab, você pode copiar o código abaixo para o SaveImageToFileWorker. No código fornecido, observe como os valores resourceUri e output são extraídos e armazenados com a chave KEY_IMAGE_URI. Esse processo é muito semelhante ao código criado anteriormente para os objetos de dados de entrada e saída.
workers/SaveImageToFileWorker.kt
package com.example.bluromatic.workers
import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.KEY_IMAGE_URI
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.Date
/**
 * Saves the image to a permanent file
 */
private const val TAG = "SaveImageToFileWorker"
class SaveImageToFileWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
    private val title = "Blurred Image"
    private val dateFormatter = SimpleDateFormat(
        "yyyy.MM.dd 'at' HH:mm:ss z",
        Locale.getDefault()
    )
    override suspend fun doWork(): Result {
        // Makes a notification when the work starts and slows down the work so that
        // it's easier to see each WorkRequest start, even on emulated devices
        makeStatusNotification(
            applicationContext.resources.getString(R.string.saving_image),
            applicationContext
        )
        return withContext(Dispatchers.IO) {
            delay(DELAY_TIME_MILLIS)
            val resolver = applicationContext.contentResolver
            return@withContext try {
                val resourceUri = inputData.getString(KEY_IMAGE_URI)
                val bitmap = BitmapFactory.decodeStream(
                    resolver.openInputStream(Uri.parse(resourceUri))
                )
                val imageUrl = MediaStore.Images.Media.insertImage(
                    resolver, bitmap, title, dateFormatter.format(Date())
                )
                if (!imageUrl.isNullOrEmpty()) {
                    val output = workDataOf(KEY_IMAGE_URI to imageUrl)
                    Result.success(output)
                } else {
                    Log.e(
                        TAG,
                        applicationContext.resources.getString(R.string.writing_to_mediaStore_failed)
                    )
                    Result.failure()
                }
            } catch (exception: Exception) {
                Log.e(
                    TAG,
                    applicationContext.resources.getString(R.string.error_saving_image),
                    exception
                )
                Result.failure()
            }
        }
    }
}
Criar uma cadeia de trabalho
Atualmente, o código cria e executa apenas uma WorkRequest.
Nesta etapa, você vai modificar o código para criar e executar uma cadeia de WorkRequests, em vez de apenas uma solicitação de imagem de desfoque.
Na cadeia de WorkRequests, sua primeira solicitação de trabalho é limpar os arquivos temporários.
- Em vez de chamar OneTimeWorkRequestBuilder, chameworkManager.beginWith().
Chamar o método beginWith() retorna um objeto WorkContinuation e cria o ponto de partida para uma cadeia de WorkRequests com a primeira solicitação de trabalho.
data/WorkManagerBluromaticRepository.kt
import androidx.work.OneTimeWorkRequest
import com.example.bluromatic.workers.CleanupWorker
// ...
    override fun applyBlur(blurLevel: Int) {
        // Add WorkRequest to Cleanup temporary images
        var continuation = workManager.beginWith(OneTimeWorkRequest.from(CleanupWorker::class.java))
        // Add WorkRequest to blur the image
        val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
...
É possível adicionar mais solicitações de trabalho a essa cadeia chamando o método then() e transmitindo um objeto WorkRequest.
- Remova a chamada workManager.enqueue(blurBuilder.build()), que colocava apenas uma solicitação de trabalho na fila.
- Adicione a próxima solicitação de trabalho à cadeia, chamando o método .then().
data/WorkManagerBluromaticRepository.kt
...
//workManager.enqueue(blurBuilder.build())
// Add the blur work request to the chain
continuation = continuation.then(blurBuilder.build())
...
- Crie uma solicitação de trabalho para salvar a imagem e a adicionar à cadeia.
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.SaveImageToFileWorker
...
continuation = continuation.then(blurBuilder.build())
// Add WorkRequest to save the image to the filesystem
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
    .build()
continuation = continuation.then(save)
...
- Para iniciar o trabalho, chame o método enqueue()no objeto de continuação.
data/WorkManagerBluromaticRepository.kt
...
continuation = continuation.then(save)
// Start the work
continuation.enqueue()
...
Esse código produz e executa esta cadeia de WorkRequests: uma WorkRequest do CleanupWorker seguida por uma WorkRequest do BlurWorker seguida por uma WorkRequest do SaveImageToFileWorker.
- Execute o app.
Agora você pode clicar em Start e conferir as notificações quando os diferentes workers forem executados. A imagem desfocada ainda está disponível no Device Explorer. Em uma próxima seção, você vai adicionar um botão extra para que os usuários possam conferir a imagem desfocada no dispositivo.
Nas capturas de tela abaixo, observe que a mensagem de notificação mostra qual worker está em execução.



Observe que a pasta de saída contém várias imagens desfocadas, ou seja, imagens que estão em estágios desfocados intermediários, e a imagem final mostra a imagem com o nível de desfoque selecionado.
Ótimo trabalho! Agora você pode limpar os arquivos temporários, desfocar e salvar uma imagem.
12. Acessar o código da solução
Para fazer o download do código do codelab concluído, use estes comandos:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git $ cd basic-android-kotlin-compose-training-workmanager $ git checkout intermediate
Se preferir, você pode fazer o download do repositório como um arquivo ZIP, descompactar e abrir no Android Studio.
Confira o código da solução deste codelab no GitHub (link em inglês).
13. Conclusão
Parabéns! Você concluiu o app Blu-O-Matic e, no processo, aprendeu a:
- adicionar a WorkManager ao projeto;
- programar uma OneTimeWorkRequest;
- usar parâmetros de entrada e saída;
- Encadear trabalhos com WorkRequests.
O WorkManager envolve muito mais do que o conteúdo abordado neste codelab, incluindo trabalho repetitivo, uma biblioteca de suporte para testes, solicitações de trabalho paralelas e mesclagem de entradas.
Para saber mais, acesse a documentação Agendar tarefas com o WorkManager.
 
  