Uma corrotina é um padrão de projeto de simultaneidade que você pode usar no Android para simplificar o código que é executado de forma assíncrona. Corrotinas foram adicionados ao Kotlin na versão 1.3 e são baseados em modelos conceitos de outras linguagens.
No Android, as corrotinas ajudam a gerenciar tarefas de longa duração que podem bloquear a linha de execução principal e fazer com que seu app pare de responder. Mais de 50% dos desenvolvedores profissionais que usam corrotinas notaram um aumento na produtividade. Este tópico descreve como você pode usar corrotinas do Kotlin para resolver esses problemas, permitindo criar um código de app mais simples e conciso.
Recursos
As corrotinas são nossa solução recomendada para programação assíncrona no Android. Os recursos notáveis incluem o seguinte:
- Leve: é possível executar muitas corrotinas em uma única linha de execução devido ao suporte à suspensão (link em inglês), que não bloqueia a linha de execução em que a corrotina está sendo executada. A suspensão economiza memória em vez de bloquear, oferecendo compatibilidade com muitas operações simultâneas.
- Menos vazamentos de memória: use simultaneidade estruturada para executar operações dentro de um escopo.
- Suporte para cancelamento integrado: Cancelamento é propagado automaticamente pela hierarquia de corrotinas em execução.
- Integração com o Jetpack: muitas bibliotecas do Jetpack incluem extensões que oferecem compatibilidade total com corrotinas. Algumas bibliotecas também fornecem o próprio escopo de corrotina que pode ser usado para simultaneidade estruturada.
Visão geral dos exemplos
Com base no Guia para a arquitetura do app, os exemplos neste tópico fazem uma solicitação de rede e retornam o resultado para a linha de execução principal, onde o app pode exibir o resultado para o usuário.
Especificamente, o componente de arquitetura ViewModel
chama a camada repositória na linha de execução principal para
acionar a solicitação de rede. Este guia itera por várias soluções
que usam corrotinas para manter a linha de execução principal desbloqueada.
ViewModel
inclui um conjunto de extensões KTX que funcionam diretamente com
corrotinas. Essas extensões são
uma biblioteca lifecycle-viewmodel-ktx
e são usadas
neste guia.
Informações de dependência
Para usar corrotinas no seu projeto Android, adicione a seguinte dependência
ao arquivo build.gradle
do app:
Groovy
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' }
Kotlin
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") }
Executar em uma linha de execução em segundo plano
Fazer uma solicitação de rede na linha de execução principal faz com que ela aguarde ou fique bloqueada, até receber uma resposta. Como a linha de execução está bloqueada, o SO não pode chamar onDraw()
, o que faz com que seu aplicativo congele e potencialmente leve a uma caixa de diálogo "O app não está respondendo" (ANR, na sigla em inglês). Para uma melhor experiência do usuário, vamos executar essa operação em uma linha de execução em segundo plano.
Primeiro, confira a classe Repository
e veja como ela está fazendo a solicitação de rede:
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
class LoginRepository(private val responseParser: LoginResponseParser) {
private const val loginUrl = "https://example.com/login"
// Function that makes the network request, blocking the current thread
fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
val url = URL(loginUrl)
(url.openConnection() as? HttpURLConnection)?.run {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/json; utf-8")
setRequestProperty("Accept", "application/json")
doOutput = true
outputStream.write(jsonBody.toByteArray())
return Result.Success(responseParser.parse(inputStream))
}
return Result.Error(Exception("Cannot open HttpURLConnection"))
}
}
makeLoginRequest
é síncrono e bloqueia a linha de execução de chamada. Para modelar a resposta da solicitação de rede, temos nossa própria classe Result
.
O ViewModel
aciona a solicitação de rede quando o usuário clica, por exemplo, em um botão:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
Com o código anterior, LoginViewModel
está bloqueando a linha de execução de interface ao fazer a solicitação de rede. A solução mais simples para retirar a execução da linha de execução principal é criar uma nova corrotina e executar a solicitação de rede em uma linha de execução de E/S:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine to move the execution off the UI thread
viewModelScope.launch(Dispatchers.IO) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
}
Vamos separar o código de corrotinas na função login
:
viewModelScope
é umCoroutineScope
predefinido que está incluído nas extensões KTXViewModel
. Todas as corrotinas precisam ser executadas em um escopo. UmCoroutineScope
gerencia uma ou mais corrotinas relacionadas.launch
é uma função que cria uma corrotina e envia a execução do corpo funcional para o agente correspondente.Dispatchers.IO
indica que essa corrotina deve ser executada em uma linha de execução reservada para operações de E/S.
A função login
é executada da seguinte maneira:
- O aplicativo chama a função
login
da camadaView
na linha de execução principal. launch
cria uma nova corrotina, e a solicitação de rede é feita independentemente em uma linha de execução reservada para operações de E/S.- Enquanto a corrotina está em execução, a função
login
continua a execução e retorna, possivelmente antes que a solicitação de rede seja concluída. Para simplificar, a resposta da rede é ignorada por enquanto.
Como essa corrotina é iniciada com viewModelScope
, ela é executada no escopo do ViewModel
. Se o ViewModel
for destruído porque o usuário está navegando para fora da tela, viewModelScope
será cancelado automaticamente, e todas as corrotinas em execução também serão canceladas.
Um problema com o exemplo anterior é que qualquer coisa que chame makeLoginRequest
precisa se lembrar de mover explicitamente a execução para fora da linha de execução principal. Vamos ver como podemos modificar o Repository
para resolver esse problema.
Usar corrotinas para a segurança principal
Consideramos uma função muito segura quando ela não bloqueia atualizações da IU na linha de execução principal. A função makeLoginRequest
não é muito segura, porque chamar makeLoginRequest
da linha de execução principal bloqueia a IU. Use a função withContext()
da biblioteca de corrotinas para mover a execução de uma corrotina para uma linha de execução diferente:
class LoginRepository(...) {
...
suspend fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
// Move the execution of the coroutine to the I/O dispatcher
return withContext(Dispatchers.IO) {
// Blocking network request code
}
}
}
withContext(Dispatchers.IO)
move a execução da corrotina para uma linha de execução de E/S, tornando nossa função de chamada muito segura e permitindo que a IU seja atualizada conforme necessário.
makeLoginRequest
também é marcado com a palavra-chave suspend
. Essa palavra-chave é a maneira do Kotlin impor uma função a ser chamada de dentro de uma corrotina.
No exemplo a seguir, a corrotina é criada no LoginViewModel
.
À medida que makeLoginRequest
move a execução para fora da linha de execução principal, a corrotina na função login
agora pode ser executada na linha de execução principal:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine on the UI thread
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
// Make the network call and suspend execution until it finishes
val result = loginRepository.makeLoginRequest(jsonBody)
// Display result of the network request to the user
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
Observe que a corrotina ainda é necessária aqui, já que makeLoginRequest
é uma função suspend
, e todas as funções suspend
precisam ser executadas em uma corrotina.
Esse código é diferente do exemplo login
anterior de algumas maneiras:
launch
não usa um parâmetroDispatchers.IO
. Quando você não transmite umDispatcher
paralaunch
, todas as corrotinas iniciadas emviewModelScope
são executadas na linha de execução principal.- O resultado da solicitação de rede agora é processado para exibir a IU de sucesso ou falha.
A função de login agora é executada da seguinte maneira:
- O aplicativo chama a função
login()
da camadaView
na linha de execução principal. launch
cria uma nova corrotina na linha de execução principal e ela inicia a execução.- Dentro da corrotina, a chamada para
loginRepository.makeLoginRequest()
agora suspende a execução futura da corrotina até que o blocowithContext
emmakeLoginRequest()
seja concluído. - Quando o bloco
withContext
termina, a corrotina emlogin()
retoma a execução na linha de execução principal com o resultado da solicitação de rede.
Como processar exceções
Para processar exceções que a camada Repository
pode gerar, use o
suporte integrado para exceções do Kotlin (link em inglês).
No exemplo a seguir, usamos um bloco try-catch
:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
val result = try {
loginRepository.makeLoginRequest(jsonBody)
} catch(e: Exception) {
Result.Error(Exception("Network request failed"))
}
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
Neste exemplo, qualquer exceção inesperada gerada pela chamada makeLoginRequest()
é tratada como um erro na IU.
Outros recursos de corrotinas
Para ter uma visão mais detalhada das corrotinas no Android, consulte Melhorar o desempenho do app com corrotinas de Kotlin.
Para ver mais recursos de corrotinas, consulte os seguintes links (em inglês):
- Visão geral de corrotinas (JetBrains) (link em inglês)
- Guia de corrotinas (JetBrains) (link em inglês)
- Outros recursos para corrotinas e fluxos do Kotlin