A AsyncTask ficou deprecated a partir da API 30, o Android 11. Como alternativa, a documentação sugere o uso do pacote ou ... Considerando esse detalhe, neste artigo, vamos aprender a converter uma implementação com utilizando Coroutines do Kotlin.
Autor(a)
Alex Felipe
Alex é instrutor e desenvolvedor e possui experiência em Java, Kotlin, Android. Atualmente cria conteúdo no canal https://www.youtube.com/@AlexFelipeDev.
Inscreva-se em nossa Newsletter
Fique por dentro de conteúdos, insights e oportunidades do universo tech. Receba novidades e lançamentos direto no seu e-mail.
Como demonstração, vamos utilizar o projeto Todo, um App que lista e cria tarefas. Em sua implementação, foi utilizado o Room para armazenar as informações. Por padrão, as operações do Room precisam ser feitas em uma thread diferente da UI, portanto, a listagem e inserção de tarefas foram feitas com AsyncTask.
Em outras palavras, o nosso objetivo é avaliar as duas amostras e converter para o uso de Coroutines.
Adicionando Coroutines no projeto Android
Para instalar a Coroutine no projeto Android, precisamos adicionar a dependência:
Então é só sincronizar que temos acesso às Coroutines. Com tudo configurado, podemos começar a conversão do nosso primeiro exemplo, a inserção de notas.
Convertendo a task de salvar nota
No código de inserção temos a seguinte AsyncTask:
classSalvaNotaTask(
privateval dao: NotaDao,
privateval nota: Nota,
val quandoNotaSalva: () -> Unit,
) : AsyncTask<Unit, Unit, Unit>() {
overridefundoInBackground(vararg p0: Unit?) {
dao.salva(nota)
}
overridefunonPostExecute(result: Unit?) {
quandoNotaSalva()
}
}
Então, temos a seguinte chamada dentro do FormularioNotaFragment que permite salvar a nota:
classFormularioNotaFragment : Fragment() {
// restante do códigooverridefunonViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val botao = binding.formularioNotaBotaoSalvarFragment
botao.setOnClickListener {
salvaNota()
}
}
privatefunsalvaNota() {
val campoTitulo = binding.formularioNotaTituloFragment
val titulo = campoTitulo.text.toString()
val campoDescricao = binding.formularioNotaDescricaoFragment
val descricao = campoDescricao.text.toString()
val nota = Nota(
titulo = titulo,
descricao = descricao
)
SalvaNotaTask(dao, nota) {
activity?.onBackPressed()
}.execute()
}
}
Basicamente, ao clicar no botão salvar, chamamos o salvaNota() que cria uma Nota e executa a SalvaNotaTask() enviando o NotaDao e a Nota criada. Ao finalizar, a expressão lambda é executada e chama o activity?.onBackPressed() que volta para a lista de notas.
Com Coroutines, executamos a mesma tarefa com o seguinte código:
Observe que não precisamos criar uma classe nova! Além disso, não há a necessidade de implementar métodos de callbacks para separar a execução assíncrona da thread principal.
Porém, quando escrevemos um código mais enxuto que faz a mesma coisa de um código maior, é natural surgir dúvidas, como por exemplo, o que é o CoroutineScope, Dispatchers.IO e etc...
De uma maneira resumida, ambos significam o seguinte:
CoroutinesScope: determina o escopo de execução da coroutine. Ao criar uma instância, somos obrigados a determinar um CoroutineContext que define onde a Coroutine será executada, no caso do Dispatchers.IO, indicamos que é uma execução que precisa ser feita em paralela à thread principal, ou seja, destinada a execuções como leitura e escrita, requisições e etc.
launch: função de extensão de uma CoroutineScope, a partir dela executamos a coroutine sem bloquear a thread principal.
withContext: suspend function que permite trocar o escopo de uma Coroutine. Seguindo o nosso exemplo, trocamos de uma thread IO para uma Main que representa a execução da UI Thread.
Dispartchers: um grupo de implementações de CoroutinesDispatchers. A partir dele temos acesso a implementações comuns, como a execução em threads destinadas a IO ou a main thread.
Suspend functions, são funções que podem ser chamadas apenas por Coroutines ou por outras funções de suspensão. Caso tenha interesse em saber mais detalhes sobre Coroutines, fique à vontade em consultar a documentação.
Para deixar mais clara toda teoria, podemos visualizar a execução do nosso código com os seguintes comentários:
CoroutineScope(Dispatchers.IO).launch {
// executa paralelo à UI thread
dao.salva(nota)
withContext(Dispatchers.Main) {
// executa na UI thread
activity?.onBackPressed()
}
}
Apenas com esse exemplo, podemos aplicar a mesma técnica para buscar as notas!
Observe que o resultado é bastante similar ao nosso primeiro exemplo, a diferença é que na busca de notas esperamos o retorno da função buscaTodas().
Um detalhe que podemos ressaltar, é que Coroutines permite a escrita de código assíncrono de maneira sequencial, ou seja, sem a necessidade de callbacks!
Escopo de lifecycle owner
Além de criar o nosso próprio escopo, no ambiente Android, temos acesso aos escopos de lifecycle owner, como Activities ou Fragments. Isso significa que as Coroutines executadas com esses escopos são sincronizadas com o ciclo de vida do componente.
Em outras palavras, a execução da Coroutine nesse escopo é cancelada automaticamente se o ciclo de vida chegar no estado de destruição!
Para uma demonstração do comportamento, podemos executar o código com alguns logs:
Observe que podemos definir também o CoroutinesContext na função launch.
Ao abrir e fechar o App, não somos capazes de notar a sincronia mencionada, pois é uma execução rápida! Para que isso seja possível durante o teste, podemos aplicar um atraso de execução com a suspend function delay():
Veja que a busca não é finalizada! Dessa forma, concluímos a existência da sincroniza do escopo da Coroutine de lifecycle owner com o ciclo de vida do componente.
Conclusão
Mesmo que a AsyncTask esteja obsoleta, a Coroutine é uma ótima alternativa. Além do que vimos, as Coroutines estão cada vez mais comuns dentro do ambiente Android, seja como alternativa para AsyncTask, como também, no uso de muitas bibliotecas famosas do Android que estão aderindo ao uso, como por exemplo, as bibliotecas do Jetpack entre outras.