Como substituir AsyncTask pelas corrotinas do Kotlin

Você ainda usa AsyncTask em seus aplicativos Android? Você provavelmente não deveria mais estar. Veja como substituí-los pelas corrotinas de Kotlin.

Por muito tempo no Android, se você precisasse fazer algo de forma assíncrona ao criar um aplicativo, provavelmente usaria AsyncTask. AsyncTask é uma API na estrutura do Android que facilita (ish) a execução de operações em segundo plano e o retorno de valores quando concluído. E isso faz sentido. Ao contrário das corrotinas de Kotlin, o AsyncTask já existe há algum tempo e está integrado.

No entanto, tanto a filosofia de design quanto a implementação do AsyncTask tornaram-se um tanto desatualizadas ao longo dos anos. Por causa disso, o Google descontinuado a API AsyncTask. Você ainda pode usá-lo se quiser, mas o Google não recomenda fazer isso. Felizmente, existem várias alternativas ao AsyncTask, incluindo um recurso da linguagem Kotlin – corrotinas.

A API de corrotinas do Kotlin é uma estrutura incrivelmente poderosa que permite fazer um monte de coisas. Este artigo irá apenas arranhar a superfície do que é possível. Veremos os princípios básicos necessários para migrar de AsyncTask para corrotinas.

Adicionando suporte a corrotinas

Antes de começar a usar corrotinas, você precisa adicioná-las ao seu projeto.

Adicionando suporte Kotlin

Se você já implementou o Kotlin, vá para a próxima seção. Caso contrário, você precisará adicionar suporte Kotlin ao seu projeto. Confira meu tutorial sobre como adicionar Kotlin a um projeto existente para obter mais detalhes.

Adicionando bibliotecas de corrotinas

No nível do seu módulo build.gradle, inclua as dependências a seguir.

dependencies {
...
implementation 'org.jetbrains.kotlinx: kotlinx-coroutines-core: 1.5.0'
implementation 'org.jetbrains.kotlinx: kotlinx-coroutines-android: 1.5.0'
}

Sincronize seu projeto e as corrotinas do Kotlin estarão agora disponíveis para uso.

Usando corrotinas

Implementando um CoroutineScope

Para usar corrotinas, você precisará ter uma instância CoroutineScope disponível. Uma maneira fácil de fazer isso é simplesmente implementá-lo na classe que o contém.

Por exemplo, para implementar um CoroutineScope em uma Activity:

classSomeActivity : AppCompatActivity, CoroutineScope by MainScope() {
...

override fun onDestroy(){
super.onDestroy()

cancel()
}
}

Isso fará com que SomeActivity implemente a interface CoroutineScope por meio da classe MainScope. MainScope lidará com toda a lógica de implementação do CoroutineScope, permitindo que você use os métodos CoroutineScope. Chamando cancel() em onDestroy() garante que nenhuma lógica assíncrona continue em execução após o encerramento da atividade.

Substituindo AsyncTask por Corrotinas

Digamos que você tenha um AsyncTask dentro de uma Activity que executa uma operação de longa duração em segundo plano e eventualmente retorna uma String. Algo como o seguinte.

private inner classSomeTask : AsyncTask() {
override fun doInBackground(vararg params: Void): String {
try {
//Pretend this is an actual operation that takes 10 seconds and not just sleeping.
Thread.sleep(10000);
} catch (e: InterruptedException) {}

return"SomeString";
}

override fun onPostExecute(result: String) {
val someTextView = findViewById(R.id.some_text_view)
someTextView.text = result
}
}

Substituir isso por uma corrotina é fácil. Basta usar o async() método. Kotlin async() é executado em qualquer Thread em que foi iniciado, mas de forma assíncrona. Isso significa que você pode atualizar Views e outros sem precisar se preocupar em usar o Thread correto.

classSomeActivity : AppCompatActivity(), CoroutineScope by MainScope() {
...

private fun doOperation(){
async {
//Inside coroutine scopes (like inside async here), delay is used instead of Thread.sleep.
delay(10000)

val someTextView = findViewById(R.id.some_text_view)
someTextView.text = "SomeString"
}
}
}

Como você pode ver, usar corrotinas pode ser muito mais simples do que usar AsyncTask. Você não precisa apenas ligar async() e deixe-o fazer o seu trabalho, no entanto. Você pode manter uma referência a ele e até esperar que termine.

val asyncJob = async {
//Some operation
}
//Pause here until the async block is finished.
asyncJob.await()

//This won't run until asyncJob finishes, but other operations started before the job, or started from another method, can still run.
doSomethingElse()

Retornando valores com assíncrono

Você pode até retornar um valor de async() se você quiser. Portanto, o exemplo original poderia se tornar algo assim.

classSomeActivity : AppCompatActivity(), CoroutineScope by MainScope() {
...
private fun doOperation(){
val asyncJob = async {
//Inside coroutine scopes (like inside async here), delay is used instead of Thread.sleep.
delay(10000)

//Whatever the type is of the last line is what async() eventually returns.
"SomeString"
}

val result = asyncJob.await()

val someTextView = findViewById(R.id.some_text_view)
someTextView.text = result
}
}

Usando withContext

Por conveniência, Kotlin fornece withContext(). Isso incorpora todo await() coisa e apenas retorna o valor para você.

classSomeActivity : AppCompatActivity(), CoroutineScope by MainScope() {
...
private fun doOperation(){
//Run asynchronously on the main Thread.
val result = withContext(Dispatchers.Main) {
delay(10000)

"SomeResult"
}

val someTextView = findViewById(R.id.some_text_view)
someTextView.text = result
}
}

Conclusão

Os exemplos acima são apenas alguns usos básicos das corrotinas do Kotlin para você começar. Você não precisa limitar as corrotinas às atividades ou mesmo a qualquer coisa com um ciclo de vida adequado. Você pode executá-los basicamente em qualquer lugar. Existem também operações mais avançadas, como escolher qual Thread deve executar a lógica assíncrona. Este guia serve principalmente para mostrar como substituir um AsyncTask simples por uma corrotina simples.

Para obter mais detalhes sobre como as corrotinas funcionam e como você pode usar seus recursos mais avançados, confira o documentação oficial do Kotlin.