A AsyncTask ficou deprecated a partir da API 30, o Android 11. Como alternativa, a documentação sugere o uso do pacote java.util.concurrent ou Coroutines do Kotlin... Considerando esse detalhe, neste artigo, vamos aprender a converter uma implementação com AsyncTask
utilizando Coroutines do Kotlin.
AsyncTask
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.
Para instalar a Coroutine no projeto Android, precisamos adicionar a dependência:
dependencies {
// dependências
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
}
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.
No código de inserção temos a seguinte AsyncTask
:
class SalvaNotaTask(
private val dao: NotaDao,
private val nota: Nota,
val quandoNotaSalva: () -> Unit,
) : AsyncTask<Unit, Unit, Unit>() {
override fun doInBackground(vararg p0: Unit?) {
dao.salva(nota)
}
override fun onPostExecute(result: Unit?) {
quandoNotaSalva()
}
}
Então, temos a seguinte chamada dentro do FormularioNotaFragment
que permite salvar a nota:
class FormularioNotaFragment : Fragment() {
// restante do código
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val botao = binding.formularioNotaBotaoSalvarFragment
botao.setOnClickListener {
salvaNota()
}
}
private fun salvaNota() {
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:
private fun salvaNota() {
// restante do código
CoroutineScope(Dispatchers.IO).launch {
dao.salva(nota)
withContext(Dispatchers.Main) {
activity?.onBackPressed()
}
}
}
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!
Na busca com AsyncTask
temos o seguinte código:
class BuscaNotasTask(
private val dao: NotaDao,
private val quandoNotasEncontradas: (notas: List<Nota>) -> Unit
) : AsyncTask<Unit, Unit, List<Nota>>() {
override fun doInBackground(vararg p0: Unit?): List<Nota> {
return dao.buscaTodas()
}
override fun onPostExecute(resultado: List<Nota>?) {
resultado?.let { notas ->
quandoNotasEncontradas(notas)
}
}
}
Então na ListaNotasActivity
chamamos da seguinte maneira:
class ListaNotasActivity : AppCompatActivity() {
// restante do código
override fun onResume() {
super.onResume()
BuscaNotasTask(dao) { notas ->
adapter.atualiza(notas)
}.execute()
}
}
Com Coroutines, seguindo a mesma técnica, temos o seguinte código:
override fun onResume() {
super.onResume()
CoroutineScope(IO).launch {
val notas = dao.buscaTodas()
withContext(Main){
adapter.atualiza(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!
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:
override fun onResume() {
super.onResume()
lifecycleScope.launch(IO) {
Log.i(TAG, "onResume: buscando notas")
val notas = dao.buscaTodas()
withContext(Main){
adapter.atualiza(notas)
}
Log.i(TAG, "onResume: buscando finalizada")
}
}
override fun onDestroy() {
super.onDestroy()
Log.i(TAG, "onDestroy: activity destruída")
}
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()
:
lifecycleScope.launch(IO) {
Log.i(TAG, "onResume: buscando notas")
delay(5000)
val notas = dao.buscaTodas()
withContext(Main){
adapter.atualiza(notas)
}
Log.i(TAG, "onResume: buscando finalizada")
}
Fazendo a mesma simulação de abrir e fechar o App, temos o seguinte resultado:
I/ListaNotasActivity: onResume: buscando notas
I/ListaNotasActivity: onDestroy: activity destruída
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.
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.
Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.
Certificado de que assistiu o curso e finalizou as atividades
Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets
Cursos de introdução a tecnologia através de games, apps e ciência
Reforço online de inglês e espanhol para aprimorar seu conhecimento
Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.
Certificado de que assistiu o curso e finalizou as atividades
Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets
Cursos de introdução a tecnologia através de games, apps e ciência
Reforço online de inglês e espanhol para aprimorar seu conhecimento
Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.
Certificado de que assistiu o curso e finalizou as atividades
Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets
Cursos de introdução a tecnologia através de games, apps e ciência
Reforço online de inglês e espanhol para aprimorar seu conhecimento
Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.
Certificado de que assistiu o curso e finalizou as atividades
Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets
Cursos de introdução a tecnologia através de games, apps e ciência
Reforço online de inglês e espanhol para aprimorar seu conhecimento
Acesso por 1 ano
Estude 24h/dia onde e quando quiser
Novos cursos todas as semanas