Kotlin coroutines no Android

Um coroutine é um padrão de design concomitante que você pode usar no Android para simplificar o código que executa assincronamente.Coroutines foram adicionados ao Kotlin na versão 1.3 e são baseados em conceitos estabelecidos de outras linguagens.

No Android, coroutines ajudam a gerenciar tarefas de longa duração que bloqueiam a linha principal e fazem com que seu aplicativo não responda.Mais de 50% dos desenvolvedores profissionais que usam coroutines relataram ter visto um aumento na produtividade. Este tópico descreve como você pode usar o Kotlin coroutines para resolver esses problemas, permitindo que você escreva um código de aplicação mais limpo e conciso.

Faatures

Coroutines é nossa solução recomendada para programação assíncrona no Android. Entre as funcionalidades notáveis estão as seguintes:

  • Leve: Você pode executar muitos coroutinos em um único fio devido ao suporte à suspensão, que não bloqueia o fio onde o coroutine está rodando. Suspensões economizam memória sobre bloqueio enquanto suporta muitas operações simultâneas.
  • Menos vazamentos de memória: Utiliza simultaneidade estruturada para executar operações dentro de um escopo.
  • Suporte de cancelamento embutido:O cancelamento é propagado automaticamente através da hierarquia de coroutina em execução.
  • Integração Jetpack: Muitas bibliotecas Jetpack incluem extensões que fornecem suporte completo a coroutinas. Somelibraries também fornecem seu próprio escopocoroutine que você pode usar para concorrência estruturada.

Síntese de exemplos

Baseado no Guia para arquitetura do aplicativo, os exemplos neste tópico fazem uma requisição de rede e retornam o resultado para o tópico principal, onde o aplicativo pode então exibir o resultado para o usuário.

Especificamente, o componente ViewModelArquitetura chama a camada de repositório no tópico principal totrigger a requisição de rede. Este guia itera através de várias soluções que utilizam coroutinas mantêm a thread principal desbloqueada.

ViewModel inclui um conjunto de extensões KTX que funcionam diretamente com coroutinas. Estas extensões sãolifecycle-viewmodel-ktx biblioteca e são usadas neste guia.

Informação de dependência

Para usar coroutinos no seu projeto Android, adicione a seguinte dependência ao seu aplicativo build.gradle arquivo:

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

Executar em um thread de fundo

Fazer uma solicitação de rede no thread principal faz com que ele espere, ou bloqueie, até que receba uma resposta. Como a thread está bloqueada, a tabela do SO é chamada para onDraw(), o que faz com que a sua aplicação congele e potencialmente leva a um diálogo de Não Resposta de Aplicação (ANR). Para uma melhor experiência de uso, vamos executar esta operação em uma thread de fundo.

Primeiro, vamos dar uma olhada na nossa classe Repository e ver como ela está fazendo a requisiçã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íncrona e bloqueia a thread de chamada. Para modelar a resposta da requisição de rede, temos a nossa própria Result class.

O ViewModel aciona a requisição de rede quando o usuário clica, forexample, 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 thread UI ao fazer a requisição de rede. A solução mais simples para mover a execução do thread principal é criar um novo coroutine e executar a requisição de rede em um thread 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 dissecar o código dos coroutines na função login:

  • viewModelScope é um pré-definido CoroutineScope que está incluído com as extensões ViewModel KTX. Note que todos os coroutinos devem ser executados no ascope. A CoroutineScope gere um ou mais coroutinos relacionados.
  • launch é uma função que cria um coroutine e envia a execução do seu corpo de funções para o despachante correspondente.
  • Dispatchers.IO indica que este coroutine deve ser executado em um tópico reservado para operações de E/S.

A função login é executada da seguinte forma:

  • A aplicação chama a função login a partir da camada View na thread principal.
  • launch cria um novo coroutine, e a requisição de rede é feita independentemente de uma thread reservada para operações de E/S.
  • Enquanto o coroutine está em execução, a função login continua a execução e retorna, possivelmente antes da requisição de rede ser finalizada. Note que para simplificar, a resposta da rede é ignorada por enquanto.

Desde que este coroutine seja iniciado com viewModelScope, ele é executado no escopo do ViewModel. Se o ViewModel for destruído porque o usuário está navegando longe da tela, viewModelScope é automaticamente cancelado, e todos os coroutinos em execução também são cancelados.

Um problema com o exemplo anterior é que qualquer coisa chamando makeLoginRequest precisa lembrar explicitamente de mover a execução para fora do tópico principal. Vamos ver como podemos modificar o Repository para resolver este problema para nós.

Utilizar coroutinos para segurança principal

Consideramos uma função de segurança principal quando ela não bloqueia atualizações de IU na thread principal. A função makeLoginRequest não é de segurança principal, pois a chamadamakeLoginRequest a partir do fio principal bloqueia a IU. Use a funçãowithContext() da biblioteca de corotinas para mover a execução de uma corotina para uma thread 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 corotina para uma thread de I/O, tornando a nossa função de chamada principal-safe e habilitando a data de toque da IU conforme necessário.

makeLoginRequest também é marcada com a palavra-chave suspend. Esta palavra-chave é a forma do Kotlin de impor uma função a ser chamada de dentro de um coroutine.

No exemplo a seguir, o coroutine é criado em LoginViewModel.Como makeLoginRequest move a execução para fora do fio principal, o coroutine na função login pode agora ser executado no fio 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 } } }}

Nota que o coroutine ainda é necessário aqui, já que makeLoginRequest isa suspend função, e todas as funções suspend devem ser executadas em coroutine.

Este código difere do anterior login exemplo de algumas maneiras:

  • launch não aceita um parâmetro Dispatchers.IO. Quando não se ultrapassa um Dispatcher a launch, qualquer coroutino lançado deviewModelScope corre na linha principal.
  • O resultado do pedido de rede é agora tratado para mostrar a IU de falha do sucessor.

A função de login agora é executada da seguinte forma:

  • A aplicação chama a função login() da camada View na thread principal.
  • launch cria um novo coroutine para fazer a requisição de rede na thread principal, e o coroutine inicia a execução.
  • Com o coroutine, a chamada para loginRepository.makeLoginRequest() suspende a execução do coroutine até que o withContextblock em makeLoginRequest() termine a execução.
  • Após o withContextbloco terminar, o coroutine em login() retoma a execução na thread principal com o resultado da requisição de rede.

Exceções de manuseio

Para lidar com exceções que a camada Repository pode lançar, use o suporte Kotlin’sbuilt-in para exceções. No exemplo a seguir, usamos um bloco:try-catch

class LoginViewModel( private val loginRepository: LoginRepository): ViewModel() { fun makeLoginRequest(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 lançada pela camada makeLoginRequest()chamada é tratada como um erro na UI.

Leave a Reply