0% acharam este documento útil (0 voto)
96 visualizações29 páginas

1-Usando Kotlin Coroutines No Seu Aplicativo Android

1. Este documento introduz o uso de Kotlin Coroutines em aplicativos Android para simplificar código assíncrono, convertendo retornos de chamada para código sequencial. 2. Coroutines permitem que funções assíncronas sejam chamadas de forma seqüencial, suspendendo a execução até que os resultados estejam disponíveis. 3. Este codelab ensinará como usar coroutines para carregar dados da rede e banco de dados em um aplicativo existente que usa retornos de chamada.

Enviado por

Gelson Stoduto
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato ODT, PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
96 visualizações29 páginas

1-Usando Kotlin Coroutines No Seu Aplicativo Android

1. Este documento introduz o uso de Kotlin Coroutines em aplicativos Android para simplificar código assíncrono, convertendo retornos de chamada para código sequencial. 2. Coroutines permitem que funções assíncronas sejam chamadas de forma seqüencial, suspendendo a execução até que os resultados estejam disponíveis. 3. Este codelab ensinará como usar coroutines para carregar dados da rede e banco de dados em um aplicativo existente que usa retornos de chamada.

Enviado por

Gelson Stoduto
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato ODT, PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 29

1.

Introdução
Neste codelab, você aprenderá como usar as Kotlin Coroutines em um aplicativo Android - uma
nova maneira de gerenciar threads em segundo plano que podem simplificar o código, reduzindo
a necessidade de retornos de chamada. As corotinas são um recurso do Kotlin que converte
retornos de chamada assíncronos para tarefas de longa execução, como acesso a banco de dados
ou rede, em código sequencial.

Aqui está um trecho de código para lhe dar uma idéia do que você fará.

// Async callbacks
networkRequest { result ->
// Successful network request
databaseSave(result) { rows ->
// Result saved
}
}

O código baseado em retorno de chamada será convertido em código seqüencial usando


corotinas.

// The same code with coroutines


val result = networkRequest()
// Successful network request
databaseSave(result)
// Result saved

Você começará com um aplicativo existente, criado com o Architecture Components, que usa um
estilo de retorno de chamada para tarefas de longa duração.

No final deste codelab, você terá experiência suficiente para usar corotinas em seu aplicativo para
carregar dados da rede e poderá integrar corotinas em um aplicativo. Você também estará
familiarizado com as práticas recomendadas para corotinas e como escrever um teste em relação
ao código que usa corotinas.

O que você aprenderá


Como chamar código escrito com corotinas e obter resultados.
Como usar as funções de suspensão para tornar o código assíncrono seqüencial.
Como usar launch e runBlocking para controlar como o código é executado.
Técnicas para converter APIs existentes em corotinas usando o suspendCoroutine.
Como usar corotinas com componentes de arquitetura.
Práticas recomendadas para testar corotinas.
Pré-requisitos
Familiarizado com os componentes de arquitetura ViewModel, LiveData, Repository e Room.
Experiente na sintaxe do Kotlin, incluindo funções de extensão e lambdas.
Um entendimento básico do uso de threads no Android, incluindo o thread principal, threads de
segundo plano e retornos de chamada.
Para obter uma introdução à sala, consulte Acessando dados usando os DAOs da sala.
Para uma introdução aos outros componentes da arquitetura usados neste codelab, consulte o
Guia de arquitetura de aplicativos.
Para uma introdução à sintaxe do Kotlin, consulte Kotlin Bootcamp for Programmers.
Para obter uma introdução aos conceitos básicos de encadeamento no Android, consulte o Guia
de processamento em segundo plano.
O que você precisará
Android Studio 3.5 (o codelab pode funcionar com outras versões, mas algumas coisas podem
estar faltando ou parecer diferentes).

2. Como configurar
Faça o download do código
Clique no link a seguir para baixar todo o código deste codelab:

... ou clone o repositório GitHub da linha de comando usando o seguinte comando:

$ git clone https://github.com/googlecodelabs/kotlin-coroutines.git


O repositório kotlin-coroutines contém o código para dois codelabs. Este codelab usa o projeto no
diretório coroutines-codelab. Existem dois módulos de aplicativos nesse projeto:

android_studio_folder.pngstart - Aplicativo simples que usa componentes da arquitetura Android


aos quais você adicionará corotinas
android_studio_folder.pngfinished_code - O projeto com corotinas já adicionadas

3. Execute o aplicativo de amostra inicial


Primeiro, vamos ver como é o aplicativo de amostra inicial. Siga estas instruções para abrir o
aplicativo de exemplo no Android Studio.

Se você baixou o arquivo zip kotlin-coroutines, descompacte o arquivo.


Abra o projeto coroutines-codelab no Android Studio.
Selecione o módulo de aplicativo inicial.
Clique no botão execute.pngRun e escolha um emulador ou conecte seu dispositivo Android, que
deve ser capaz de executar o Android Lollipop (o SDK mínimo suportado é 21). A tela Kotlin
Coroutines deve aparecer:

Este aplicativo inicial usa threads para aumentar a contagem um pequeno atraso depois que você
pressiona a tela. Ele também buscará um novo título da rede e o exibirá na tela. Experimente
agora e você verá a contagem e a mensagem mudarem após um pequeno atraso. Neste codelab,
você converterá esse aplicativo para usar corotinas.

Este aplicativo usa componentes de arquitetura para separar o código da interface do usuário em
MainActivity da lógica do aplicativo em MainViewModel. Reserve um momento para se familiarizar
com a estrutura do projeto.

1-MainActivity exibe a interface do usuário, registra ouvintes de clique e pode exibir uma
Snackbar. Ele passa eventos para MainViewModel e atualiza a tela com base no LiveData no
MainViewModel.
2-MainViewModel manipula eventos em onMainViewClicked e se comunicará com MainActivity
usando LiveData.

3-Executors define BACKGROUND, que pode executar coisas em um thread de segundo plano.

4-O TitleRepository busca os resultados da rede e os salva no banco de dados.


Adicionando corotinas a um projeto

Para usar corotinas no Kotlin, você deve incluir a biblioteca coroutines-core no arquivo
build.gradle (Module: app) do seu projeto. Os projetos do codelab já fizeram isso por você,
portanto você não precisa fazer isso para concluir o codelab.

As corotinas no Android estão disponíveis como uma biblioteca principal e extensões específicas
do Android:

kotlinx-corountines-core - Interface principal para usar coroutines no Kotlin


kotlinx-coroutines-android - Suporte para o thread principal do Android em coroutines
O aplicativo inicial já inclui as dependências no build.gradle. Ao criar um novo projeto de
aplicativo, você precisa abrir o build.gradle (Módulo: app) e adicionar as dependências das
corotinas ao projeto.

dependencies {
...
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:x.x.x"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:x.x.x"
}

Coroutines e RxJava

Se você estiver usando o RxJava na sua base de código atual, poderá integrar-se às corotinas
usando uma biblioteca kotlin-coroutines-rx.

4. Corotinas em Kotlin
No Android, é essencial evitar o bloqueio do thread principal. O segmento principal é um
segmento único que lida com todas as atualizações da interface do usuário. É também o
segmento que chama todos os manipuladores de clique e outros retornos de chamada da
interface do usuário. Como tal, ele deve funcionar sem problemas para garantir uma ótima
experiência do usuário.

Para que seu aplicativo seja exibido ao usuário sem pausas visíveis, o segmento principal precisa
atualizar a tela a cada 16ms ou mais frequentemente, ou seja, cerca de 60 quadros por segundo.
Muitas tarefas comuns levam mais tempo que isso, como analisar grandes conjuntos de dados
JSON, gravar dados em um banco de dados ou buscar dados da rede. Portanto, chamar um código
como esse do thread principal pode fazer com que o aplicativo pause, gagueje ou até congele. E se
você bloquear o thread principal por muito tempo, o aplicativo poderá até falhar e apresentar
uma caixa de diálogo O aplicativo não está respondendo.
Assista ao vídeo abaixo para obter uma introdução sobre como as corotinas resolvem esse
problema para nós no Android, introduzindo a segurança principal.

*** VIDEO

O padrão de retorno de chamada


Um padrão para executar tarefas de longa execução sem bloquear o encadeamento principal é o
retorno de chamada. Usando retornos de chamada, você pode iniciar tarefas de longa execução
em um encadeamento em segundo plano. Quando a tarefa é concluída, o retorno de chamada é
chamado para informá-lo do resultado no encadeamento principal.

Veja um exemplo do padrão de retorno de chamada.


// Slow request with callbacks
@UiThread
fun makeNetworkRequest() {
// The slow network request runs on another thread
slowFetch { result ->
// When the result is ready, this callback will get the result
show(result)
}
// makeNetworkRequest() exits after calling slowFetch without waiting
for the result
}

Como esse código é anotado com @UiThread, ele deve ser executado rápido o suficiente para ser
executado no encadeamento principal. Isso significa que ele precisa retornar muito rapidamente,
para que a próxima atualização da tela não seja atrasada. No entanto, como o slowFetch levará
segundos ou até minutos para concluir, o thread principal não pode esperar pelo resultado. O
retorno de chamada show (result) permite que slowFetch seja executado em um thread de
segundo plano e retorne o resultado quando estiver pronto.

Usando corotinas para remover retornos de chamada


Os retornos de chamada são um ótimo padrão, porém têm alguns inconvenientes. O código que
usa fortemente retornos de chamada pode se tornar difícil de ler e mais difícil de raciocinar. Além
disso, os retornos de chamada não permitem o uso de alguns recursos de idioma, como exceções.

As corotinas Kotlin permitem converter o código baseado em retorno de chamada em código


seqüencial. O código escrito sequencialmente geralmente é mais fácil de ler e pode até usar
recursos de idioma, como exceções.

No final, eles fazem exatamente a mesma coisa: espere até que um resultado esteja disponível em
uma tarefa de longa execução e continue a execução. No entanto, no código, eles parecem muito
diferentes.

A palavra-chave suspend é a maneira do Kotlin de marcar uma função, ou tipo de função,


disponível para as rotinas. Quando uma corotina chama uma função marcada como suspensa, em
vez de bloquear até que a função retorne como uma chamada de função normal, ela suspende a
execução até que o resultado esteja pronto e, em seguida, retoma de onde parou com o
resultado. Enquanto está suspenso aguardando um resultado, desbloqueia o encadeamento em
execução para que outras funções ou corotinas possam ser executadas.

Por exemplo, no código abaixo, makeNetworkRequest () e slowFetch () são funções de suspensão.

// Slow request with coroutines


@UiThread
suspend fun makeNetworkRequest() {
// slowFetch is another suspend function so instead of
// blocking the main thread makeNetworkRequest will `suspend` until the
result is
// ready
val result = slowFetch()
// continue to execute after the result is ready
show(result)
}

// slowFetch is main-safe using coroutines


suspend fun slowFetch(): SlowResult { ... }

Assim como na versão de retorno de chamada, o makeNetworkRequest deve retornar do


encadeamento principal imediatamente porque está marcado como @UiThread. Isso significa que
geralmente não era possível chamar métodos de bloqueio como slowFetch. É aqui que a palavra-
chave suspend funciona sua mágica.

Importante: A palavra-chave suspend não especifica a execução do código do encadeamento. As


funções de suspensão podem ser executadas em um thread de segundo plano ou no thread
principal.

Comparado ao código baseado em retorno de chamada, o código da corotina obtém o mesmo


resultado ao desbloquear o encadeamento atual com menos código. Devido ao seu estilo
seqüencial, é fácil encadear várias tarefas de longa execução sem criar vários retornos de
chamada. Por exemplo, o código que busca um resultado de dois pontos de extremidade da rede
e o salva no banco de dados pode ser gravado como uma função em corotinas sem retorno de
chamada. Igual a:

// Request data from network and save it to database with coroutines

// Because of the @WorkerThread, this function cannot be called on the


// main thread without causing an error.
@WorkerThread
suspend fun makeNetworkRequest() {
// slowFetch and anotherFetch are suspend functions
val slow = slowFetch()
val another = anotherFetch()
// save is a regular function and will block this thread
database.save(slow, another)
}

// slowFetch is main-safe using coroutines


suspend fun slowFetch(): SlowResult { ... }
// anotherFetch is main-safe using coroutines
suspend fun anotherFetch(): AnotherResult { ... }

O padrão de assíncrono e espera em outros idiomas é baseado em corotinas. Se você conhece


esse padrão, a palavra-chave suspend é semelhante a assíncrona. No entanto, no Kotlin, wait ()
está implícito ao chamar uma função de suspensão.

O Kotlin possui um método Deferred.await () usado para aguardar o resultado de uma corotina
iniciada com o construtor assíncrono.

Você apresentará corotinas ao aplicativo de amostra na próxima seção.

5. Controlando a interface do usuário com corotinas


Neste exercício, você escreverá uma rotina para exibir uma mensagem após um atraso. Para
começar, verifique se o módulo é aberto no Android Studio.

Noções básicas sobre o CoroutineScope


No Kotlin, todas as corotinas são executadas dentro de um CoroutineScope. Um escopo controla a
vida útil das corotinas através de seu trabalho. Quando você cancela o trabalho de um escopo, ele
cancela todas as corotinas iniciadas nesse escopo. No Android, você pode usar um escopo para
cancelar todas as corotinas em execução quando, por exemplo, o usuário sair de uma Atividade
ou Fragmento. Os escopos também permitem especificar um expedidor padrão. Um despachante
controla qual thread executa uma rotina.

Para corotinas iniciadas pela interface do usuário, normalmente é correto iniciá-las no


Dispatchers.Main, que é o principal encadeamento no Android. Uma corotina iniciada em
Dispatchers.Main não bloqueará o thread principal enquanto estiver suspenso. Como uma
corotina do ViewModel quase sempre atualiza a interface do usuário no encadeamento principal,
iniciar corotinas no encadeamento principal economiza opções adicionais de encadeamento. Uma
corotina iniciada no encadeamento principal pode alternar entre despachantes a qualquer
momento após o início. Por exemplo, ele pode usar outro expedidor para analisar um resultado
JSON grande do encadeamento principal.

As corotinas oferecem segurança principal

Como as corotinas podem alternar facilmente os encadeamentos a qualquer momento e passar


os resultados de volta ao encadeamento original, é uma boa idéia iniciar as corotinas relacionadas
à interface do usuário no encadeamento principal.
Bibliotecas como Room e Retrofit oferecem segurança principal imediata ao usar corotinas, para
que você não precise gerenciar threads para fazer chamadas de rede ou de banco de dados. Isso
geralmente pode levar a um código substancialmente mais simples.

No entanto, bloquear códigos como classificar uma lista ou ler um arquivo ainda requer código
explícito para criar segurança principal, mesmo ao usar corotinas. Isso também é verdade se você
estiver usando uma biblioteca de rede ou banco de dados que ainda não suporta corotinas.5.
Controlando a interface do usuário com corotinas
Neste exercício, você escreverá uma rotina para exibir uma mensagem após um atraso. Para
começar, verifique se o módulo é aberto no Android Studio.

Noções básicas sobre o CoroutineScope


No Kotlin, todas as corotinas são executadas dentro de um CoroutineScope. Um escopo controla a
vida útil das corotinas através de seu trabalho. Quando você cancela o trabalho de um escopo, ele
cancela todas as corotinas iniciadas nesse escopo. No Android, você pode usar um escopo para
cancelar todas as corotinas em execução quando, por exemplo, o usuário sair de uma Atividade
ou Fragmento. Os escopos também permitem especificar um expedidor padrão. Um despachante
controla qual thread executa uma rotina.

Para corotinas iniciadas pela interface do usuário, normalmente é correto iniciá-las no


Dispatchers.Main, que é o principal encadeamento no Android. Uma corotina iniciada em
Dispatchers.Main não bloqueará o thread principal enquanto estiver suspenso. Como uma
corotina do ViewModel quase sempre atualiza a interface do usuário no encadeamento principal,
iniciar corotinas no encadeamento principal economiza opções adicionais de encadeamento. Uma
corotina iniciada no encadeamento principal pode alternar entre despachantes a qualquer
momento após o início. Por exemplo, ele pode usar outro expedidor para analisar um resultado
JSON grande do encadeamento principal.

As corotinas oferecem segurança principal

Como as corotinas podem alternar facilmente os encadeamentos a qualquer momento e passar


os resultados de volta ao encadeamento original, é uma boa idéia iniciar as corotinas relacionadas
à interface do usuário no encadeamento principal.

Bibliotecas como Room e Retrofit oferecem segurança principal imediata ao usar corotinas, para
que você não precise gerenciar threads para fazer chamadas de rede ou de banco de dados. Isso
geralmente pode levar a um código substancialmente mais simples.

No entanto, bloquear códigos como classificar uma lista ou ler um arquivo ainda requer código
explícito para criar segurança principal, mesmo ao usar corotinas. Isso também é verdade se você
estiver usando uma biblioteca de rede ou banco de dados que ainda não suporta corotinas.

Usando viewModelScope
A biblioteca AndroidX lifecycle-viewmodel-ktx adiciona um CoroutineScope ao ViewModels
configurado para iniciar corotinas relacionadas à interface do usuário. Para usar esta biblioteca,
você deve incluí-la no arquivo build.gradle (Module: app) do seu projeto. Essa etapa já está
concluída nos projetos do codelab.

Usando viewModelScope
A biblioteca AndroidX lifecycle-viewmodel-ktx adiciona um CoroutineScope ao ViewModels
configurado para iniciar corotinas relacionadas à interface do usuário. Para usar esta biblioteca,
você deve incluí-la no arquivo build.gradle (Module: app) do seu projeto. Essa etapa já está
concluída nos projetos do codelab.

dependencies {
...
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:x.x.x"
}

A biblioteca adiciona um viewModelScope como uma função de extensão da classe ViewModel.


Esse escopo é vinculado a Dispatchers.Main e será automaticamente cancelado quando o
ViewModel for limpo.

Mudar de threads para corotinas


Em MainViewModel.kt, encontre o próximo TODO junto com este código:

MainViewModel.kt
/**
* Wait one second then update the tap count.
*/
private fun updateTaps() {
// TODO: Convert updateTaps to use coroutines
tapCount++
BACKGROUND.submit {
Thread.sleep(1_000)
_taps.postValue("$tapCount taps")
}
}

Esse código usa o BACKGROUND ExecutorService (definido em util / Executor.kt) para executar em
um encadeamento em segundo plano. Como o sono bloqueia o encadeamento atual, ele
congelará a interface do usuário se for chamado no encadeamento principal. Um segundo depois
que o usuário clica na visualização principal, ele solicita uma snackbar.

Você pode ver isso acontecendo removendo o BACKGROUND do código e executando-o


novamente. O botão giratório de carregamento não será exibido e tudo "saltará" para o estado
final um segundo depois.

MainViewModel.kt
/**
* Wait one second then update the tap count.
*/
private fun updateTaps() {
// TODO: Convert updateTaps to use coroutines
tapCount++
Thread.sleep(1_000)
_taps.postValue("$tapCount taps")
}

Substitua updateTaps por esse código baseado em rotina que faz a mesma coisa. Você precisará
importar o lançamento e o atraso.

MainViewModel.kt
/**
* Wait one second then display a snackbar.
*/
fun updateTaps() {
// launch a coroutine in viewModelScope
viewModelScope.launch {
tapCount++
// suspend this coroutine for one second
delay(1_000)
// resume in the main dispatcher
// _snackbar.value can be called directly from main thread
_taps.postValue("$tapCount taps")
}
}

Esse código faz a mesma coisa, aguardando um segundo antes de mostrar uma snackbar. No
entanto, existem algumas diferenças importantes:

O viewModelScope.launch iniciará uma corrotina no viewModelScope. Isso significa que, quando o


trabalho que passamos para o viewModelScope for cancelado, todas as rotinas neste trabalho /
escopo serão canceladas. Se o usuário deixou a Atividade antes que o atraso retornasse, essa
rotina será automaticamente cancelada quando onCleared for chamado após a destruição do
ViewModel.
Como o viewModelScope possui um despachante padrão de Dispatchers.Main, essa rotina será
iniciada no encadeamento principal. Veremos mais adiante como usar diferentes threads.
O atraso da função é uma função de suspensão. Isso é mostrado no Android Studio pelo ícone na
calha esquerda. Mesmo que essa corrotina seja executada no thread principal, o atraso não
bloqueará o thread por um segundo. Em vez disso, o expedidor agendará a rotina para retomar
em um segundo na próxima declaração.
Vá em frente e corra. Ao clicar na tela principal, você verá uma snackbar um segundo depois.

Na próxima seção, consideraremos como testar esta função.

6. Testando corotinas através do comportamento


Neste exercício, você escreverá um teste para o código que acabou de escrever. Este exercício
mostra como testar corotinas em execução em Dispatchers. Principalmente usando a biblioteca
kotlinx-coroutines-test. Posteriormente neste codelab, você implementará um teste que interage
diretamente com as corotinas.

A biblioteca de testes kotlinx-coroutines usada nesta seção está marcada como experimental e
pode ter alterações posteriores antes do lançamento.

Revise o código existente


Abra MainViewModelTest.kt na pasta androidTest.

MainViewModelTest.kt
class MainViewModelTest {
@get:Rule
val coroutineScope = MainCoroutineScopeRule()
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()

lateinit var subject: MainViewModel

@Before
fun setup() {
subject = MainViewModel(
TitleRepository(
MainNetworkFake("OK"),
TitleDaoFake("initial")
))
}
}

Uma regra é uma maneira de executar código antes e depois da execução de um teste no JUnit.
Duas regras são usadas para permitir testar o MainViewModel em um teste fora do dispositivo:

InstantTaskExecutorRule é uma regra JUnit que configura o LiveData para executar cada tarefa de
forma síncrona
MainCoroutineScopeRule é uma regra personalizada nesta base de código que configura
Dispatchers.Main para usar um TestCoroutineDispatcher de kotlinx-coroutines-test. Isso permite
que os testes avancem um relógio virtual para testes e permite que o código use Dispatchers.Main
em testes de unidade.
No método de configuração, uma nova instância do MainViewModel é criada usando testes falsos
- essas são implementações falsas da rede e do banco de dados fornecidos no código inicial para
ajudar a escrever testes sem usar a rede ou o banco de dados real.

Para este teste, as falsificações são necessárias apenas para satisfazer as dependências do
MainViewModel. Posteriormente neste laboratório de código, você atualizará as falsificações para
dar suporte às corotinas.
Escreva um teste que controla corotinas
Adicione um novo teste que garanta que os toques sejam atualizados um segundo após o clique
na visualização principal:

MainViewModelTest.kt
@Test
fun whenMainClicked_updatesTaps() {
subject.onMainViewClicked()
Truth.assertThat(subject.taps.getValueForTest()).isEqualTo("0 taps")
coroutineScope.advanceTimeBy(1000)
Truth.assertThat(subject.taps.getValueForTest()).isEqualTo("1 taps")
}

Ao chamar onMainViewClicked, a corotina que acabamos de criar será lançada. Este teste verifica
se o texto dos toques permanece "0 toques" logo após a chamada onMainViewClicked e, 1
segundo depois, é atualizado para "1 toques".

Esse teste usa o tempo virtual para controlar a execução da corotina iniciada pelo
onMainViewClicked. O MainCoroutineScopeRule permite pausar, retomar ou controlar a execução
de corotinas iniciadas no Dispatchers.Main. Aqui estamos chamando advanceTimeBy (1_000), que
fará com que o expedidor principal execute imediatamente as rotinas que estão agendadas para
serem retomadas 1 segundo depois.

Este teste é totalmente determinístico, o que significa que sempre será executado da mesma
maneira. E, porque ele tem controle total sobre a execução de corotinas lançadas nos Dispatchers.
Principalmente, não é necessário esperar um segundo para que o valor seja definido.

Execute o teste existente


Clique com o botão direito do mouse no nome da classe MainViewModelTest no seu editor para
abrir um menu de contexto.
No menu de contexto, escolha execute.pngRun 'MainViewModelTest'
Para execuções futuras, você pode selecionar esta configuração de teste nas configurações
próximas ao botão execute.png na barra de ferramentas. Por padrão, a configuração será
chamada MainViewModelTest.
Você deve ver o teste passar! E deve demorar um pouco menos de um segundo para ser
executado.

No próximo exercício, você aprenderá como converter de APIs de retorno de chamada existentes
para usar corotinas.

7. Movendo de retornos de chamada para corotinas


Nesta etapa, você começará a converter um repositório para usar corotinas. Para fazer isso,
adicionaremos corotinas ao ViewModel, Repository, Room e Retrofit.

É uma boa idéia entender o que cada parte da arquitetura é responsável antes de passar para o
uso de corotinas.

MainDatabase implementa um banco de dados usando o Room que salva e carrega um Título.
MainNetwork implementa uma API de rede que busca um novo título. Ele usa o Retrofit para
buscar títulos. O retrofit é configurado para retornar erros aleatoriamente ou simular dados, mas
se comporta como se estivesse fazendo solicitações de rede reais.
O TitleRepository implementa uma única API para buscar ou atualizar o título combinando dados
da rede e do banco de dados.
MainViewModel representa o estado da tela e lida com eventos. Ele instruirá o repositório a
atualizar o título quando o usuário tocar na tela.
Como a solicitação de rede é direcionada por eventos da interface do usuário e queremos iniciar
uma corotina com base neles, o local natural para começar a usar corotinas é no ViewModel.

A versão de retorno de chamada


Abra MainViewModel.kt para ver a declaração de refreshTitle.

MainViewModel.kt
/**
* Update title text via this LiveData
*/
val title = repository.title

// ... other code ...

/**
* Refresh the title, showing a loading spinner while it refreshes and errors
via snackbar.
*/
fun refreshTitle() {
// TODO: Convert refreshTitle to use coroutines
_spinner.value = true
repository.refreshTitleWithCallbacks(object: TitleRefreshCallback {
override fun onCompleted() {
_spinner.postValue(false)
}

override fun onError(cause: Throwable) {


_snackBar.postValue(cause.message)
_spinner.postValue(false)
}
})
}

Essa função é chamada toda vez que o usuário clica na tela - e fará com que o repositório atualize
o título e grave o novo título no banco de dados.
Esta implementação usa um retorno de chamada para fazer algumas coisas:

Antes de iniciar uma consulta, ele exibe um controle giratório de carregamento com
_spinner.value = true
Quando obtém um resultado, limpa o controle giratório de carregamento com _spinner.value =
false
Se ocorrer um erro, ele diz para uma lanchonete exibir e limpa o botão rotativo
Observe que o retorno de chamada onCompleted não recebe o título. Como escrevemos todos os
títulos no banco de dados da sala, a interface do usuário é atualizada para o título atual,
observando um LiveData atualizado pela sala.

Na atualização para as rotinas, manteremos exatamente o mesmo comportamento. É um bom


padrão usar uma fonte de dados observável como um banco de dados de salas para manter
automaticamente a interface do usuário atualizada.

O que significa object: TitleRefreshCallback?

Esta é a maneira de criar uma classe anônima no Kotlin. Ele cria um novo objeto que implementa
TitleRefreshCallback.

A versão das corotinas


Vamos reescrever refreshTitle com coroutines!

Como precisaremos imediatamente, vamos criar uma função de suspensão vazia em nosso
repositório (TitleRespository.kt). Defina uma nova função que use o operador de suspensão para
informar ao Kotlin que ele funciona com corotinas.

TitleRepository.kt
suspend fun refreshTitle() {
// TODO: Refresh from network and write to database
delay(500)
}

Quando terminar este codelab, você o atualizará para usar Retrofit e Room para buscar um novo
título e gravá-lo no banco de dados usando corotinas. Por enquanto, gastará apenas 500
milissegundos fingindo fazer o trabalho e continuando.

No MainViewModel, substitua a versão de retorno de chamada de refreshTitle por uma que inicie
uma nova corotina:

MainViewModel.kt
/**
* Refresh the title, showing a loading spinner while it refreshes and errors
via snackbar.
*/
fun refreshTitle() {
viewModelScope.launch {
try {
_spinner.value = true
repository.refreshTitle()
} catch (error: TitleRefreshError) {
_snackBar.value = error.message
} finally {
_spinner.value = false
}
}
}

Vamos percorrer esta função:


viewModelScope.launch {

Assim como a corotina para atualizar a contagem de toques, comece lançando


uma nova corotina no viewModelScope. Isso usará Dispatchers.Main, que está
OK. Embora o refreshTitle faça uma solicitação de rede e uma consulta ao
banco de dados, ele pode usar corotinas para expor uma interface principal
segura. Isso significa que será seguro chamá-lo a partir do thread
principal.

Como estamos usando o viewModelScope, quando o usuário se afasta dessa tela,


o trabalho iniciado por essa rotina é automaticamente cancelado. Isso
significa que ele não fará solicitações extras de rede ou consultas ao banco
de dados.

Ao criar uma corotina a partir de uma não corotina, inicie com o lançamento.

Dessa forma, se eles lançarem uma exceção não capturada, ela será propagada
automaticamente para manipuladores de exceção não capturados (que, por
padrão, travam o aplicativo). Uma corotina iniciada com async não emitirá
uma exceção para o chamador até você ligar em espera. No entanto, você só
pode ligar em espera de dentro de uma corotina, pois é uma função de
suspensão.

Uma vez dentro de uma corotina, você pode usar o launch ou assíncrono para
iniciar corotinas filho. Use o lançamento para quando você não tiver um
resultado para retornar e assíncrono quando o fizer.

As próximas linhas de código chamam refreshTitle no repositório.


try {
_spinner.value = true
repository.refreshTitle()
}

Antes dessa corotina fazer qualquer coisa, ele inicia o controle giratório
de carregamento - então chama refreshTitle como uma função regular. No
entanto, como refreshTitle é uma função de suspensão, ele é executado de
maneira diferente da função normal.
Não precisamos passar um retorno de chamada. A corotina será suspensa até
ser retomada por refreshTitle. Embora pareça uma chamada de função de
bloqueio regular, ela aguardará automaticamente até que a consulta à rede e
ao banco de dados seja concluída antes de continuar sem bloquear o segmento
principal.

} catch (error: TitleRefreshError) {


_snackBar.value = error.message
} finally {
_spinner.value = false
}

Exceções nas funções de suspensão funcionam como erros nas funções


regulares. Se você lançar um erro em uma função de suspensão, ela será
lançada para o chamador. Portanto, mesmo que eles sejam executados de
maneira bastante diferente, você pode usar blocos try / catch regulares para
lidar com eles. Isso é útil porque permite confiar no suporte ao idioma
interno para tratamento de erros, em vez de criar tratamento personalizado
para cada retorno de chamada.

E, se você lançar uma exceção para fora de uma corotina - essa corotina
cancelará seu pai por padrão. Isso significa que é fácil cancelar várias
tarefas relacionadas juntas.

E então, em um bloco final, podemos garantir que o botão giratório esteja


sempre desligado após a execução da consulta.

O que acontece com exceções não capturadas

Exceções não capturadas em uma corotina são semelhantes às exceções não


capturadas no código que não é de corotina. Por padrão, eles cancelam o
trabalho da corotina e notificam as corotinas dos pais que elas devem se
cancelar. Se nenhuma corotina manipular a exceção, ela será passada para um
manipulador de exceção não capturado no CoroutineScope.

Por padrão, as exceções não capturadas serão enviadas ao manipulador de


exceções não capturadas do encadeamento na JVM. Você pode personalizar esse
comportamento fornecendo um CoroutineExceptionHandler.

Execute o aplicativo novamente selecionando a configuração inicial e


pressionandoexecute.png, você verá um botão giratório de carregamento ao
tocar em qualquer lugar. O título permanecerá o mesmo, porque ainda não
conectamos nossa rede ou banco de dados.

No próximo exercício, você atualizará o repositório para realmente


funcionar.

8. Fazendo funções de segurança principal a partir do código de bloqueio


Neste exercício, você aprenderá como alternar o segmento em que uma corotina é executada
para implementar uma versão funcional do TitleRepository.
Revise o código de retorno de chamada existente em refreshTitle
Abra TitleRepository.kt e revise a implementação existente baseada em retorno de chamada.

TitleRepository.kt
// TitleRepository.kt

fun refreshTitleWithCallbacks(titleRefreshCallback: TitleRefreshCallback) {


// This request will be run on a background thread by retrofit
BACKGROUND.submit {
try {
// Make network request using a blocking call
val result = network.fetchNextTitle().execute()
if (result.isSuccessful) {
// Save it to database
titleDao.insertTitle(Title(result.body()!!))
// Inform the caller the refresh is completed
titleRefreshCallback.onCompleted()
} else {
// If it's not successful, inform the callback of the error
titleRefreshCallback.onError(
TitleRefreshError("Unable to refresh title", null))
}
} catch (cause: Throwable) {
// If anything throws an exception, inform the caller
titleRefreshCallback.onError(
TitleRefreshError("Unable to refresh title", cause))
}
}
}

No TitleRepository.kt, o método refreshTitleWithCallbacks é implementado com um retorno de


chamada para comunicar o estado de carregamento e erro ao chamador.

Essa função faz algumas coisas para implementar a atualização.

Alterne para outro thread com BACKGROUND ExecutorService


Execute a solicitação de rede fetchNewTitle usando o método de bloqueio execute (). Isso
executará a solicitação de rede no encadeamento atual, nesse caso, um dos encadeamentos em
BACKGROUND.
Se o resultado for bem-sucedido, salve-o no banco de dados com insertTitle e chame o método
onCompleted ().
Se o resultado não tiver sido bem-sucedido ou houver uma exceção, chame o método onError
para informar ao chamador sobre a falha na atualização.
Essa implementação baseada em retorno de chamada é segura principalmente porque não
bloqueia o thread principal. Porém, ele deve usar um retorno de chamada para informar o
chamador quando o trabalho for concluído. Ele também chama os retornos de chamada no
segmento BACKGROUND que ele também comutou.

Como fazer chamadas de bloqueio de corotinas


Sem introduzir corotinas na rede ou no banco de dados, podemos tornar esse código seguro
principal usando corotinas. Isso nos livrará do retorno de chamada e passará o resultado de volta
ao thread que o chamou inicialmente.

Você pode usar esse padrão a qualquer momento que precisar executar trabalhos intensivos de
bloqueio ou CPU de dentro de uma rotina, como classificar e filtrar uma lista grande ou ler no
disco.

Esse padrão deve ser usado para integrar APIs de bloqueio no seu código ou para executar um
trabalho intensivo da CPU. Quando possível, é melhor usar funções de suspensão regulares de
bibliotecas como Room ou Retrofit.

Para alternar entre qualquer expedidor, as corotinas usam withContext. Chamar withContext
alterna para o outro distribuidor apenas para o lambda e depois retorna ao distribuidor que o
chamou com o resultado desse lambda.

Por padrão, as rotinas da Kotlin fornecem três Dispatchers: Main, IO e Default. O distribuidor de
E / S é otimizado para o trabalho de E / S, como a leitura da rede ou do disco, enquanto o
distribuidor de E / S é otimizado para tarefas intensivas da CPU.

TitleRepository.kt
suspend fun refreshTitle() {
// interact with *blocking* network and IO calls from a coroutine
withContext(Dispatchers.IO) {
val result = try {
// Make network request using a blocking call
network.fetchNextTitle().execute()
} catch (cause: Throwable) {
// If the network throws an exception, inform the caller
throw TitleRefreshError("Unable to refresh title", cause)
}

if (result.isSuccessful) {
// Save it to database
titleDao.insertTitle(Title(result.body()!!))
} else {
// If it's not successful, inform the callback of the error
throw TitleRefreshError("Unable to refresh title", null)
}
}
}

Essa implementação usa chamadas de bloqueio para a rede e o banco de dados - mas ainda é um
pouco mais simples que a versão de retorno de chamada.

Esse código ainda usa chamadas de bloqueio. A chamada de execute () e insertTitle (...) bloqueará
o encadeamento em que esta rotina está sendo executada. No entanto, alternando para
Dispatchers.IO usando withContext, estamos bloqueando um dos encadeamentos no dispatcher
de E / S. A corotina que chamou isso, possivelmente executando em Dispatchers.Main, será
suspensa até que o lambda withContext seja concluído.

Comparado à versão de retorno de chamada, há duas diferenças importantes:

withContext retorna o resultado para o Dispatcher que o chamou, neste caso Dispatchers.Main. A
versão de retorno de chamada chamou os retornos de chamada em um encadeamento no serviço
do executor BACKGROUND.
O chamador não precisa passar um retorno de chamada para esta função. Eles podem confiar em
suspender e continuar para obter o resultado ou erro.
Dica avançada

Este código não suporta cancelamento de rotina, mas pode! O cancelamento da corotina é
cooperativo. Isso significa que seu código precisa verificar o cancelamento explicitamente, o que
acontece sempre que você chama as funções em kotlinx-coroutines.

Como esse bloco withContext chama apenas chamadas de bloqueio, ele não será cancelado até
que retorne do withContext.

Para consertar isso, você pode chamar o rendimento regularmente para dar uma chance às
outras corotinas e verificar o cancelamento. Aqui você adicionaria uma chamada para gerar entre
a solicitação de rede e a consulta ao banco de dados. Então, se a corotina for cancelada durante a
solicitação de rede, ela não salvará o resultado no banco de dados.

Também é possível verificar o cancelamento explicitamente, o que você deve fazer ao criar
interfaces de corotina de baixo nível.

Execute o aplicativo novamente


Se você executar o aplicativo novamente, verá que a nova implementação baseada em corotinas
está carregando resultados da rede!

Na próxima etapa, você integrará as corotinas no Room and Retrofit.

9. Corotinas na Sala e Retrofit


Para continuar a integração das corotinas, usaremos o suporte para funções de suspensão na
versão estável do Room and Retrofit e simplificaremos o código que acabamos de escrever
substancialmente usando as funções de suspensão.

Corotinas no quarto
Primeiro abra MainDatabase.kt e torne insertTitle uma função de suspensão:
MainDatabase.kt
// add the suspend modifier to the existing insertTitle

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTitle(title: Title)

Quando você fizer isso, o Room tornará sua consulta segura principal e executará
automaticamente em um encadeamento em segundo plano. No entanto, isso também significa
que você só pode chamar essa consulta de dentro de uma rotina.

E - é tudo o que você precisa fazer para usar as corotinas no quarto. Muito bacana.

Corotinas em Retrofit
A seguir, vamos ver como integrar corotinas com o Retrofit. Abra MainNetwork.kt e altere
fetchNextTitle para uma função de suspensão.

O suporte à função de suspensão requer o Retrofit 2.6.0 ou superior.

MainNetwork.kt
// add suspend modifier to the existing fetchNextTitle
// change return type from Call<String> to String

interface MainNetwork {
@GET("next_title.json")
suspend fun fetchNextTitle(): String
}

Para usar as funções de suspensão com o Retrofit, você deve fazer duas coisas:

Adicione um modificador de suspensão à função


Remova o wrapper de chamada do tipo de retorno. Aqui estamos retornando String, mas você
também pode retornar um tipo complexo suportado por json. Se você ainda deseja fornecer
acesso ao resultado completo da atualização, poderá retornar o resultado <> em vez de String da
função de suspensão.
O retrofit tornará automaticamente as funções de suspensão seguras para que você possa
chamá-las diretamente do Dispatchers.Main.

O Room e o Retrofit tornam as funções de suspensão seguras.

É seguro chamar esses divertimentos de suspensão de Dispatchers.Main, mesmo que eles


busquem na rede e gravem no banco de dados.

O Room e o Retrofit usam um despachante personalizado e não usam Dispatchers.IO.

A sala executará corotinas usando a consulta e transação padrão Executor configuradas.


O retrofit criará um novo objeto Call sob o capô e o enfileirará para enviar a solicitação de forma
assíncrona.

Usando Sala e Retrofit


Agora que o Room e o Retrofit suportam funções de suspensão, podemos usá-las em nosso
repositório. Abra o TitleRepository.kt e veja como o uso de funções de suspensão simplifica muito
a lógica, mesmo em comparação com a versão de bloqueio:

TitleRepository.kt
suspend fun refreshTitle() {
try {
// Make network request using a blocking call
val result = network.fetchNextTitle()
titleDao.insertTitle(Title(result))
} catch (cause: Throwable) {
// If anything throws an exception, inform the caller
throw TitleRefreshError("Unable to refresh title", cause)
}
}

Uau, isso é muito mais curto. O que aconteceu? Acontece que confiar em suspender e continuar
permite que o código seja muito menor. O Retrofit nos permite usar tipos de retorno como String
ou um objeto Usuário aqui, em vez de uma Chamada. Isso é seguro, porque, dentro da função de
suspensão, o Retrofit pode executar a solicitação de rede em um encadeamento em segundo
plano e retomar a corotina quando a chamada é concluída.

Melhor ainda, nos livramos do withContext. Como o Room e o Retrofit fornecem funções de
suspensão com segurança principal, é seguro orquestrar esse trabalho assíncrono no
Dispatchers.Main.

Você não precisa usar o withContext para chamar funções de suspensão com segurança principal.

Por convenção, você deve garantir que as funções de suspensão escritas em seu aplicativo sejam
de segurança principal. Dessa forma, é seguro chamá-los de qualquer despachante, até mesmo
Despachantes.

Corrigindo erros do compilador


Mover para corotinas envolve alterar a assinatura de funções, pois você não pode chamar uma
função de suspensão de uma função regular. Quando você adicionou o modificador de suspensão
nesta etapa, foram gerados alguns erros do compilador que mostram o que aconteceria se você
alterasse uma função para suspender em um projeto real.

Percorra o projeto e corrija os erros do compilador que alteram a função a suspender criada. Aqui
estão as resoluções rápidas para cada um:

TestingFakes.kt
Atualize as falsificações de teste para suportar os novos modificadores de suspensão.
TítuloDaoFake
Pressione alt-enter e adicione modificadores de suspensão a todas as funções na hierarquia
MainNetworkFake
Pressione alt-enter e adicione modificadores de suspensão a todas as funções na hierarquia
Substitua fetchTitle por esta função
fun fetchTitle() = result

MainNetworkCompletableFake
Pressione alt-enter e adicione modificadores de suspensão a todas as funções
na hierarquia
Substitua fetchTitle por esta função

fun fetchTitle() = completable.await()

TitleRepository.kt
Exclua a função refreshTitleWithCallbacks, pois ela não é mais usada.
Execute o aplicativo
Execute o aplicativo novamente, uma vez compilado, você verá que está
carregando dados usando corotinas por todo o caminho, do ViewModel ao Room e
Retrofit!

Parabéns, você mudou completamente este aplicativo para o uso de corotinas!


Para finalizar, falaremos um pouco sobre como testar o que acabamos de
fazer.

10. Teste de corotinas diretamente


Neste exercício, você escreverá um teste que chama uma função de suspensão
diretamente.

Como refreshTitle é exposto como uma API pública, ele será testado
diretamente, mostrando como chamar funções de corotinas a partir de testes.

Aqui está a função refreshTitle que você implementou no último exercício:

TitleRepository.kt
suspend fun refreshTitle() {
try {
// Make network request using a blocking call
val result = network.fetchNextTitle()
titleDao.insertTitle(Title(result))
} catch (cause: Throwable) {
// If anything throws an exception, inform the caller
throw TitleRefreshError("Unable to refresh title", cause)
}
}

Escreva um teste que chama uma função de suspensão


Abra TitleRepositoryTest.kt na pasta de teste que possui dois TODOS.

Tente chamar refreshTitle a partir do primeiro teste


quandoRefreshTitleSuccess_insertsRows.
@Test
fun whenRefreshTitleSuccess_insertsRows() {
val subject = TitleRepository(
MainNetworkFake("OK"),
TitleDaoFake("title")
)

subject.refreshTitle()
}

Como refreshTitle é uma função de suspensão, o Kotlin não sabe como chamá-
lo, exceto a partir de uma corotina ou outra função de suspensão, e você
receberá um erro do compilador como "Função de suspensão refreshTitle deve
ser chamado apenas de uma corotina ou outra função de suspensão".

O executor de teste não sabe nada sobre corotinas, portanto não podemos
tornar esse teste uma função de suspensão. Poderíamos iniciar uma corotina
usando um CoroutineScope como em um ViewModel, no entanto, os testes
precisam executar corotinas até a conclusão antes de retornarem. Quando uma
função de teste retorna, o teste termina. As corotinas iniciadas com o
lançamento são código assíncrono, que pode ser concluído em algum momento no
futuro. Portanto, para testar esse código assíncrono, é necessário que você
diga ao teste que aguarde até a conclusão da sua rotina. Como o lançamento é
uma chamada sem bloqueio, isso significa que ele retorna imediatamente e
pode continuar executando uma corotina após o retorno da função - não pode
ser usado em testes. Por exemplo:
@Test
fun whenRefreshTitleSuccess_insertsRows() {
val subject = TitleRepository(
MainNetworkFake("OK"),
TitleDaoFake("title")
)

// launch starts a coroutine then immediately returns


GlobalScope.launch {
// since this is asynchronous code, this may be called *after* the
test completes
subject.refreshTitle()
}
// test function returns immediately, and
// doesn't see the results of refreshTitle
}
A biblioteca kotlinx-coroutines-test possui a função runBlockingTest que bloqueia enquanto
chama funções de suspensão. Quando o runBlockingTest chama uma função de suspensão ou
inicia uma nova rotina, ele a executa imediatamente por padrão. Você pode pensar nisso como
uma maneira de converter funções de suspensão e corotinas em chamadas de função normais.

Além disso, runBlockingTest retornará exceções não capturadas para você. Isso facilita o teste
quando uma corotina está lançando uma exceção.

Importante: A função runBlockingTest sempre bloqueará o chamador, assim como uma chamada
de função regular. A corotina será executada de forma síncrona no mesmo encadeamento. Você
deve evitar runBlocking e runBlockingTest no código do aplicativo e preferir o lançamento que
retorna imediatamente.

O runBlockingTest deve ser usado apenas em testes, pois executa corotinas de maneira
controlada por testes, enquanto runBlocking pode ser usado para fornecer interfaces de bloqueio
para corotinas.

Implementar um teste com uma corotina


Encerre a chamada para refreshTitle com runBlockingTest e conclua o teste como este

TitleRepositoryTest.kt
@Test
fun whenRefreshTitleSuccess_insertsRows() = runBlockingTest {
val titleDao = TitleDaoFake("title")
val subject = TitleRepository(
MainNetworkFake("OK"),
titleDao
)

subject.refreshTitle()
Truth.assertThat(titleDao.nextInsertedOrNull()).isEqualTo("OK")
}

Este teste usa as falsificações fornecidas para verificar se "OK" está inserido no banco de dados
pelo refreshTitle.

Quando o teste chama runBlockingTest, ele será bloqueado até a conclusão da rotina iniciada por
runBlockingTest. Depois, quando chamamos refreshTitle, ele usa o mecanismo regular de
suspensão e retomada para aguardar a adição da linha do banco de dados à nossa falsificação.

Após a conclusão da rotina de teste, runBlockingTest retorna.

Escreva um teste de tempo limite


Queremos adicionar um tempo limite curto à solicitação de rede. Vamos escrever o teste primeiro
e depois implementar o tempo limite. Crie um novo teste:

TitleRepositoryTest.kt
@Test(expected = TitleRefreshError::class)
fun whenRefreshTitleTimeout_throws() = runBlockingTest {
val network = MainNetworkCompletableFake()
val subject = TitleRepository(
network,
TitleDaoFake("title")
)

launch {
subject.refreshTitle()
}

advanceTimeBy(5_000)
}

Este teste usa o MainNetworkCompletableFake falso fornecido, que é um falso de rede projetado
para suspender os chamadores até que o teste os continue. Quando refreshTitle tenta fazer uma
solicitação de rede, ela fica suspensa para sempre, porque queremos testar o tempo limite.

Em seguida, ele lança uma rotina separada para chamar refreshTitle. Essa é uma parte essencial
do tempo limite do teste; o tempo limite deve ocorrer em uma rotina diferente da que o
runBlockingTest cria. Ao fazer isso, podemos chamar a próxima linha, advanceTimeBy (5_000), que
avançará o tempo em 5 segundos e fará com que a outra coroutine atinja o tempo limite.

Este é um teste de tempo limite completo e passará assim que implementarmos o tempo limite.

Execute-o agora e veja o que acontece:


Caused by: kotlinx.coroutines.test.UncompletedCoroutinesError: Test finished
with active jobs: ["...]

Um dos recursos do runBlockingTest é que ele não permite vazar corotinas após a conclusão do
teste. Se houver alguma corotina inacabada, como a nossa corotina de lançamento, no final do
teste, ela falhará no teste.

Adicionar tempo limite


Abra o TitleRepository e adicione um tempo limite de cinco segundos à busca na rede. Você pode
fazer isso usando a função withTimeout:

TitleRepository.kt
suspend fun refreshTitle() {
try {
// Make network request using a blocking call
val result = withTimeout(5_000) {
network.fetchNextTitle()
}
titleDao.insertTitle(Title(result))
} catch (cause: Throwable) {
// If anything throws an exception, inform the caller
throw TitleRefreshError("Unable to refresh title", cause)
}
}

Execute o teste. Ao executar os testes, você deverá ver todos os testes aprovados!

No próximo exercício, você aprenderá a escrever funções de ordem superior usando corotinas.

runBlockingTest depende de TestCoroutineDispatcher para controlar corotinas.

Como resultado, é uma boa idéia injetar um TestCoroutineDispatcher ou TestCoroutineScope ao


usar o runBlockingTest. Isso tem o efeito de tornar as corotinas únicas e oferece a capacidade de
controlar explicitamente todas as corotinas nos testes.

Se você não deseja alterar o comportamento das corotinas - por exemplo, em um teste de
integração -, use o runBlocking com as implementações padrão de todos os despachantes.

runBlockingTest é experimental e atualmente possui um erro que faz com que falhe no teste se
uma corotina alternar para um expedidor que executa uma corotina em outro encadeamento.
Não é esperado que o estábulo final tenha esse bug.
11. Usando corotinas em funções de ordem superior
Neste exercício, você refatorará refreshTitle no MainViewModel para usar uma função geral de
carregamento de dados. Isso ensinará como criar funções de ordem superior que usam corotinas.

A implementação atual do refreshTitle funciona, mas podemos criar uma rotina geral de
carregamento de dados que sempre mostra o botão giratório. Isso pode ser útil em uma base de
código que carrega dados em resposta a vários eventos e deseja garantir que o controle giratório
de carregamento seja exibido consistentemente.

Revisando a implementação atual a cada linha, exceto repository.refreshTitle (), é um clichê para
mostrar o girador e exibir erros.
// MainViewModel.kt

fun refreshTitle() {
viewModelScope.launch {
try {
_spinner.value = true
// this is the only part that changes between sources
repository.refreshTitle()
} catch (error: TitleRefreshError) {
_snackBar.value = error.message
} finally {
_spinner.value = false
}
}
}

Importante: Embora apenas usemos o viewModelScope neste codelab, geralmente é bom


adicionar um escopo em qualquer lugar que faça sentido. Não se esqueça de cancelá-lo se não for
mais necessário.

Por exemplo, você pode declarar um em um adaptador RecyclerView para executar operações
DiffUtil.

Usando corotinas em funções de ordem superior


Adicione este código ao MainViewModel.kt

MainViewModel.kt
private fun launchDataLoad(block: suspend () -> Unit): Job {
return viewModelScope.launch {
try {
_spinner.value = true
block()
} catch (error: TitleRefreshError) {
_snackBar.value = error.message
} finally {
_spinner.value = false
}
}
}

Agora refatorre refreshTitle () para usar esta função de ordem superior.

MainViewModel.kt
fun refreshTitle() {
launchDataLoad {
repository.refreshTitle()
}
}

Abstraindo a lógica de mostrar um girador de carregamento e mostrar erros, simplificamos nosso


código real necessário para carregar dados. Mostrar um botão giratório ou exibir um erro é algo
fácil de generalizar para qualquer carregamento de dados, enquanto a fonte e o destino reais dos
dados precisam ser especificados sempre.
Para construir essa abstração, launchDataLoad usa um bloco de argumento que é um lambda de
suspensão. Uma suspensão lambda permite chamar funções de suspensão. É assim que o Kotlin
implementa os construtores de corotina que lançam e executam o bloco que usamos neste
codelab.

// suspend lambda

block: suspend () -> Unit

Para criar um lambda de suspensão, comece com a palavra-chave suspend. A seta de função e o
tipo de retorno Unidade completam a declaração.

Você não precisa declarar frequentemente suas próprias lambdas de suspensão, mas elas podem
ser úteis para criar abstrações como essa que encapsulam a lógica repetida!

12. Usando corotinas com o WorkManager


Neste exercício, você aprenderá como usar o código baseado em corotina no WorkManager.

O que é o WorkManager

Existem muitas opções no Android para trabalhos em segundo plano adiados. Este exercício
mostra como integrar o WorkManager às corotinas. O WorkManager é uma biblioteca compatível,
flexível e simples para trabalhos em segundo plano adiados. O WorkManager é a solução
recomendada para esses casos de uso no Android.

O WorkManager faz parte do Android Jetpack e um componente de arquitetura para trabalhos em


segundo plano que precisam de uma combinação de execução oportunista e garantida. A
execução oportunista significa que o WorkManager fará seu trabalho em segundo plano o mais
rápido possível. A execução garantida significa que o WorkManager cuidará da lógica para iniciar
seu trabalho em uma variedade de situações, mesmo se você sair do aplicativo.

Por esse motivo, o WorkManager é uma boa opção para tarefas que devem ser concluídas
eventualmente.

Alguns exemplos de tarefas que são um bom uso do WorkManager:

Upload de logs
Aplicando filtros às imagens e salvando a imagem
Sincronizando periodicamente dados locais com a rede
Para saber mais sobre o WorkManager, consulte a documentação.

Usando corotinas com o WorkManager


O WorkManager fornece diferentes implementações de sua classe ListanableWorker básica para
diferentes casos de uso.

A classe Worker mais simples nos permite executar alguma operação síncrona pelo
WorkManager. No entanto, tendo trabalhado até agora para converter nossa base de código para
usar corotinas e suspender funções, a melhor maneira de usar o WorkManager é através da
classe CoroutineWorker que permite definir nossa função doWork () como uma função de
suspensão.
Para começar, abra RefreshMainDataWork. Ele já estende o CoroutineWorker e você precisa
implementar o doWork.

Dentro da função suspend doWork, chame refreshTitle () do repositório e retorne o resultado


apropriado!

Depois de concluir o TODO, o código ficará assim:


override suspend fun doWork(): Result {
val database = getDatabase(applicationContext)
val repository = TitleRepository(network, database.titleDao)

return try {
repository.refreshTitle()
Result.success()
} catch (error: TitleRefreshError) {
Result.failure()
}
}

bserve que CoroutineWorker.doWork () é uma função de suspensão. Diferente da classe Worker


mais simples, esse código NÃO é executado no Executor especificado na configuração do
WorkManager, mas, em vez disso, usa o dispatcher no membro coroutineContext (por padrão
Dispatchers.Default).

Testando nosso CoroutineWorker


Nenhuma base de código deve estar completa sem teste.

O WorkManager disponibiliza algumas maneiras diferentes de testar suas classes Worker. Para
saber mais sobre a infraestrutura de teste original, você pode ler a documentação.

O WorkManager v2.1 apresenta um novo conjunto de APIs para oferecer suporte a uma maneira
mais simples de testar as classes ListenableWorker e, como conseqüência, o CoroutineWorker. Em
nosso código, usaremos uma dessas novas API: TestListenableWorkerBuilder.

Para adicionar nosso novo teste, crie um novo arquivo de origem kotlin com o nome
RefreshMainDataWorkTest na pasta androidTest.

O conteúdo do arquivo é:
package com.example.android.kotlincoroutines.main

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.work.ListenableWorker.Result
@Test
fun testRefreshMainDataWork() {
val fakeNetwork = MainNetworkFake("OK")

val context = ApplicationProvider.getApplicationContext<Context>()


val worker = TestListenableWorkerBuilder<RefreshMainDataWork>(context)
.setWorkerFactory(RefreshMainDataWork.Factory(fakeNetwork))
.build()

// Start the work synchronously


val result = worker.startWork().get()

assertThat(result).isEqualTo(Result.success())
}

Antes de começarmos o teste, informamos o WorkManager sobre a fábrica para injetar a rede
falsa.

O teste em si usa o TestListenableWorkerBuilder para criar nosso trabalhador que podemos


executar chamando o método startWork ().

O WorkManager é apenas um exemplo de como as corotinas podem ser usadas para simplificar o
design das APIs.

13. Onde aprender mais


Neste codelab, abordamos o básico que você precisará para começar a usar corotinas em seu
aplicativo!

Abordamos como integrar corotinas aos aplicativos Android a partir dos trabalhos de interface do
usuário e WorkManager para simplificar a programação assíncrona. E como usar corotinas dentro
de um ViewModel para buscar dados da rede e salvá-los em um banco de dados sem bloquear o
encadeamento principal, além de como cancelar todas as corotinas quando o ViewModel
terminar.

Para testar o código baseado em rotina, abordamos tanto o comportamento do teste quanto a
chamada direta de funções de suspensão dos testes.

Confira o codelab "Corotinas avançadas com Kotlin Flow e LiveData" para saber mais sobre o uso
avançado de corotinas no Android.

As corotinas Kotlin têm muitos recursos que não foram cobertos por este codelab. Se você estiver
interessado em aprender mais sobre as corotinas Kotlin, leia os guias de corotinas publicados pela
JetBrains. Confira também "Melhorar o desempenho do aplicativo com as corotinas Kotlin" para
obter mais padrões de uso de corotinas no Android.

Você também pode gostar

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy