Conceitos básicos do Kotlin para Android 06.2: corrotinas e Room

Este codelab faz parte do curso Conceitos básicos do Kotlin para Android. Você aproveitará mais o curso se fizer os codelabs em sequência. Todos os codelabs do curso estão listados na página de destino dos codelabs do curso Conceitos básicos do Kotlin para Android.

Introdução

Uma das principais prioridades da criação de uma experiência do usuário impecável para o app é garantir que a IU seja sempre responsiva e funcione bem. Uma forma de melhorar o desempenho da IU é mover tarefas de longa duração, como operações de banco de dados, para o segundo plano.

Neste codelab, você vai implementar a parte voltada para o usuário do app TrackMySleepQuality, usando corrotinas Kotlin para realizar operações de banco de dados fora da linha de execução principal.

O que você já precisa saber

Você precisa:

  • Como criar uma interface do usuário (IU) básica usando uma atividade, fragmentos, visualizações e gerenciadores de cliques.
  • Navegar entre fragmentos usando safeArgs para transmitir dados simples entre eles.
  • Visualize modelos, veja fábricas de modelo, transformações e LiveData.
  • Como criar um banco de dados Room, um DAO e definir entidades.
  • Ele é útil quando você conhece os conceitos de linha de execução e multiprocessamento.

O que você vai aprender

  • Como as linhas de execução funcionam no Android.
  • Como usar corrotinas do Kotlin para remover operações do banco de dados da linha de execução principal.
  • Como exibir dados formatados em um TextView.

Atividades do laboratório

  • Estenda o app TrackMySleepQuality para coletar, armazenar e exibir dados no banco de dados e nele.
  • Usar corrotinas para executar operações de banco de dados de longa duração em segundo plano.
  • Use LiveData para acionar a navegação e a exibição de um snackbar.
  • Use LiveData para ativar e desativar botões.

Neste codelab, você criará o modelo de visualização, corrotinas e partes da exibição de dados do app TrackMySleepQuality.

O app tem duas telas, representadas por fragmentos, como mostrado na figura abaixo.

A primeira tela, mostrada à esquerda, tem botões para iniciar e interromper o rastreamento. A tela mostra todos os dados de sono do usuário. O botão Limpar exclui permanentemente todos os dados que o app coletou para o usuário.

A segunda tela, mostrada à direita, serve para selecionar uma classificação de qualidade do sono. No app, a classificação é representada numericamente. Para fins de desenvolvimento, o app mostra os ícones de rostos e os equivalentes numéricos deles.

O fluxo do usuário é o seguinte:

  • O usuário abre o app e vê a tela de monitoramento do sono.
  • O usuário toca no botão Iniciar. Ele registra o horário de início e o exibe. O botão Iniciar está desativado, e o botão Parar está ativado.
  • O usuário toca no botão Parar. Registra o horário de término e abre a tela de qualidade do sono.
  • O usuário seleciona um ícone de qualidade do sono. A tela será fechada, e a tela de monitoramento exibirá a hora de término do sono e a qualidade do sono. O botão Parar está desativado, e o botão Iniciar está ativado. O app está pronto para outra noite.
  • O botão Limpar será ativado sempre que houver dados no banco de dados. Quando o usuário toca no botão Limpar, todos os dados dele são apagados sem fazer o recurso, e não há a mensagem "Você tem certeza?

Esse app usa uma arquitetura simplificada, conforme mostrado abaixo, no contexto da arquitetura completa. O app usa apenas os seguintes componentes:

  • controlador de IU
  • Ver modelo e LiveData
  • Um banco de dados da Room

Nesta tarefa, você usará um TextView para exibir dados formatados de monitoramento do sono. Essa não é a interface final. Você aprenderá de uma maneira melhor em outro codelab.

Você pode continuar com o app TrackMySleepQuality que criou no codelab anterior ou fazer o download do app inicial para este codelab.

Etapa 1: fazer o download e executar o app inicial

  1. Faça o download do app TrackMySleepQuality-Coroutines-Starter no GitHub.
  2. Crie e execute o app. O app mostra a IU para o fragmento SleepTrackerFragment, mas sem dados. Os botões não respondem a toques.

Etapa 2: inspecionar o código

O código inicial deste codelab é igual ao código da solução do codelab 6.1 Criar um banco de dados da Room.

  1. Abra res/layout/activity_main.xml. Esse layout contém o fragmento nav_host_fragment. Observe também a tag <merge>.

    A tag merge pode ser usada para eliminar layouts redundantes ao incluir layouts. É recomendável usá-la. Um exemplo de layout redundante seria o ConstraintLayout > LinearLayout > TextView, em que o sistema pode eliminar o LinearLayout. Esse tipo de otimização pode simplificar a hierarquia de visualização e melhorar o desempenho do app.
  2. Na pasta navigation, abra navigation.xml. É possível ver dois fragmentos e as ações de navegação que os conectam.
  3. Na pasta layout, clique duas vezes no fragmento de rastreador de sono para ver o layout XML. Observe o seguinte:
  • Os dados de layout são agrupados em um elemento <layout> para ativar a vinculação de dados.
  • ConstraintLayout e as outras visualizações são organizadas dentro do elemento <layout>.
  • O arquivo tem uma tag <data> de marcador.

O app inicial também oferece dimensões, cores e estilo para a IU. O app contém um banco de dados Room, um DAO e uma entidade SleepNight. Se você não concluiu o codelab anterior, experimente esses aspectos do código.

Agora que você tem um banco de dados e uma IU, é necessário coletar, adicionar e exibir os dados no banco de dados. Tudo isso é feito no modelo de visualização. Seu modelo de visualização do rastreador de sono gerenciará os cliques nos botões, interagirá com o banco de dados usando o DAO e fornecerá os dados à IU usando a LiveData. Todas as operações do banco de dados precisarão ser executadas na linha de execução de IU principal, e você fará isso usando corrotinas.

Etapa 1: adicionar o SleepTrackerViewModel

  1. No pacote sleeptracker, abra SleepTrackerViewModel.kt.
  2. Inspecione a classe SleepTrackerViewModel, que é fornecida no app inicial e também é mostrada abaixo. A classe estende AndroidViewModel(). Essa classe é igual à ViewModel, mas usa o contexto do aplicativo como um parâmetro e o disponibiliza como uma propriedade. Você precisará dele mais tarde.
class SleepTrackerViewModel(
       val database: SleepDatabaseDao,
       application: Application) : AndroidViewModel(application) {
}

Etapa 2: adicionar SleepTrackerViewModelFactory

  1. No pacote sleeptracker, abra SleepTrackerViewModelFactory.kt.
  2. Examine o código que você recebeu para a fábrica, que é mostrado abaixo:
class SleepTrackerViewModelFactory(
       private val dataSource: SleepDatabaseDao,
       private val application: Application) : ViewModelProvider.Factory {
   @Suppress("unchecked_cast")
   override fun <T : ViewModel?> create(modelClass: Class<T>): T {
       if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
           return SleepTrackerViewModel(dataSource, application) as T
       }
       throw IllegalArgumentException("Unknown ViewModel class")
   }
}

Observações:

  • O SleepTrackerViewModelFactory fornecido usa o mesmo argumento que a ViewModel e estende o ViewModelProvider.Factory.
  • Na fábrica, o código modifica create(), que usa qualquer tipo de classe como argumento e retorna um ViewModel.
  • No corpo de create(), o código verifica se há uma classe SleepTrackerViewModel disponível e, se houver, retorna uma instância dela. Caso contrário, o código vai gerar uma exceção.

Etapa 3: atualizar o SleepTrackerFragment

  1. No SleepTrackerFragment, acesse uma referência ao contexto do aplicativo. Coloque a referência em onCreateView(), abaixo de binding. Você precisa de uma referência ao app a que esse fragmento está anexado para transmitir ao provedor de fábrica de modelo de visualização.

    A função Kotlin requireNotNull gerará uma IllegalArgumentException se o valor for null.
val application = requireNotNull(this.activity).application
  1. Você precisa de uma referência à sua fonte de dados com uma referência ao DAO. Em onCreateView(), antes de return, defina um dataSource. Para acessar uma referência ao DAO do banco de dados, use SleepDatabase.getInstance(application).sleepDatabaseDao.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. No onCreateView(), antes do return, crie uma instância do viewModelFactory. Você precisa transmitir a dataSource e o application.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
  1. Agora que você tem uma fábrica, acesse uma referência ao SleepTrackerViewModel. O parâmetro SleepTrackerViewModel::class.java refere-se à classe Java de tempo de execução desse objeto.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)
  1. O código finalizado ficará assim:
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)

Veja o método onCreateView() até agora:

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        // Get a reference to the binding object and inflate the fragment views.
        val binding: FragmentSleepTrackerBinding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_sleep_tracker, container, false)

        val application = requireNotNull(this.activity).application

        val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao

        val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

        val sleepTrackerViewModel =
                ViewModelProviders.of(
                        this, viewModelFactory).get(SleepTrackerViewModel::class.java)

        return binding.root
    }

Etapa 4: adicionar uma vinculação de dados ao modelo de visualização

Com a ViewModel básica no lugar, você precisa terminar de configurar a vinculação de dados no SleepTrackerFragment para conectar a ViewModel à IU.


No arquivo de layout fragment_sleep_tracker.xml:

  1. No bloco <data>, crie uma <variable> que faça referência à classe SleepTrackerViewModel.
<data>
   <variable
       name="sleepTrackerViewModel"
       type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>

Em SleepTrackerFragment:

  1. Defina a atividade atual como a proprietária do ciclo de vida da vinculação. Adicione este código ao método onCreateView(), antes da instrução return:
binding.setLifecycleOwner(this)
  1. Atribua a variável de vinculação sleepTrackerViewModel à sleepTrackerViewModel. Coloque este código dentro de onCreateView(), abaixo do código que cria o SleepTrackerViewModel:
binding.sleepTrackerViewModel = sleepTrackerViewModel
  1. Você provavelmente verá um erro, porque precisará recriar o objeto de vinculação. Limpe e recrie o projeto para eliminar o erro.
  2. Por fim, como sempre, verifique se o código é compilado e executado sem erros.

No Kotlin, as corrotinas são a maneira de processar tarefas de longa duração de maneira elegante e eficiente. As corrotinas do Kotlin permitem converter códigos baseados em callback em códigos sequenciais. O código escrito de forma sequencial geralmente é mais fácil de ler e pode até mesmo usar recursos de linguagem, como exceções. No fim, corrotinas e callbacks fazem a mesma coisa: aguardam a disponibilidade de um resultado de uma tarefa de longa duração e continuam a execução.

As corrotinas têm as seguintes propriedades:

  • As corrotinas são assíncronas e sem bloqueio.
  • As corrotinas usam funções de suspensão para tornar o código assíncrono sequencial.

As corrotinas são assíncronas.

Uma corrotina é executada independentemente das principais etapas de execução do programa. Isso pode ser paralelo ou em um processador separado. Pode ser que, enquanto o restante do app estiver aguardando a entrada, você demora um pouco para processar. Um dos aspectos importantes do assíncrono é que você não vai esperar que o resultado esteja disponível até esperar explicitamente.

Por exemplo, digamos que você tenha uma pergunta que requer pesquisa e peça a um colega para encontrar a resposta. Eles trabalham e trabalham nisso, o que é como fazer o trabalho "de forma assíncrona" e "em uma conversa separada". Você pode continuar fazendo outro trabalho que não dependa da resposta até que seu colega retorne e diga qual é a resposta.

As corrotinas não causam bloqueios.

Sem bloqueio significa que uma corrotina não bloqueia a linha de execução principal ou de IU. Portanto, com corrotinas, os usuários sempre têm a experiência mais tranquila possível, porque a interação da IU sempre tem prioridade.

As corrotinas usam funções de suspensão para tornar o código assíncrono sequencial.

A palavra-chave suspend é a maneira como o Kotlin marca uma função ou tipo de função como disponível para corrotinas. Quando uma corrotina chama uma função marcada com suspend, em vez de bloquear até que ela retorne como uma chamada de função normal, a corrotina suspende a execução até que o resultado esteja pronto. Em seguida, a corrotina retoma de onde parou, com o resultado.

Enquanto a corrotina está suspensa e aguardando um resultado, ela desbloqueia a linha de execução em que está sendo executada. Dessa forma, outras funções ou corrotinas podem ser executadas.

A palavra-chave suspend não especifica a linha de execução em que o código é executado. Uma função de suspensão pode ser executada em uma linha de execução em segundo plano ou na linha de execução principal.

Para usar corrotinas no Kotlin, você precisa de três itens:

  • Uma vaga
  • Um expedidor
  • Um escopo

Vaga: basicamente, qualquer job pode ser cancelado. Cada corrotina tem um job, e você pode usar esse job para cancelar a corrotina. Os jobs podem ser organizados em hierarquias pai-filho. Cancelar um job pai cancela imediatamente todos os filhos dele, o que é muito mais conveniente do que cancelar manualmente cada corrotina.

Dispatcher: o agente envia corrotinas para serem executadas em várias linhas de execução. Por exemplo, Dispatcher.Main executa tarefas na linha de execução principal, e Dispatcher.IO descarrega tarefas de bloqueio de E/S para um pool compartilhado de linhas de execução.

Escopo: um escopo de corrotina define o contexto em que a corrotina é executada. Um escopo combina informações sobre o job e o agente de uma corrotina. Os escopos rastreiam as corrotinas. Quando você inicia uma corrotina, ela se limita a um escopo, o que significa que você indicou qual escopo rastreia a corrotina.

Você quer que o usuário interaja com os dados de sono das seguintes maneiras:

  • Quando o usuário toca no botão Iniciar, o app cria uma nova noite de sono e armazena a noite no banco de dados.
  • Quando o usuário toca no botão Stop, o app atualiza a noite com um horário de término.
  • Quando o usuário toca no botão Limpar, o app apaga os dados no banco de dados.

Essas operações de banco de dados podem levar muito tempo, então devem ser executadas em uma linha de execução separada.

Etapa 1: configurar corrotinas para operações de banco de dados

Quando o botão Iniciar no app Tracker de sono é tocado, você quer chamar uma função no SleepTrackerViewModel para criar uma nova instância de SleepNight e armazenar a instância no banco de dados.

O toque em qualquer botão aciona uma operação de banco de dados, como a criação ou atualização de um SleepNight. Por esse e outros motivos, você usa corrotinas para implementar gerenciadores de cliques para os botões do app.

  1. Abra o arquivo build.gradle no nível do app e encontre as dependências das corrotinas. Para usar corrotinas, você precisa dessas dependências, que foram adicionadas para você.

    O $coroutine_version é definido no arquivo build.gradle do projeto como coroutine_version = '1.0.0'.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
  1. Abra o arquivo SleepTrackerViewModel.
  2. No corpo da classe, defina viewModelJob e atribua a ela uma instância de Job. Esse viewModelJob permite cancelar todas as corrotinas iniciadas por esse modelo de visualização quando ele não for mais usado e for destruído. Dessa forma, você não terá corrotinas que não retornem a lugar nenhum.
private var viewModelJob = Job()
  1. No final do corpo da classe, substitua onCleared() e cancele todas as corrotinas. Quando o ViewModel é destruído, o onCleared() é chamado.
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}
  1. Logo abaixo da definição de viewModelJob, defina um uiScope para as corrotinas. O escopo determina em qual linha de execução a corrotina será executada, além do escopo que precisa saber sobre o job. Para conseguir um escopo, peça uma instância de CoroutineScope e transmita um agente e um job.

Usar Dispatchers.Main significa que as corrotinas iniciadas em uiScope serão executadas na linha de execução principal. Isso é útil para muitas corrotinas iniciadas por um ViewModel porque, depois que elas fazem algum processamento, resultam na atualização da IU.

private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  1. Abaixo da definição de uiScope, defina uma variável com o nome tonight para armazenar a noite atual. Torne a variável MutableLiveData, porque você precisará observar os dados e alterá-los.
private var tonight = MutableLiveData<SleepNight?>()
  1. Para inicializar a variável tonight o mais rápido possível, crie um bloco init abaixo da definição de tonight e chame initializeTonight(). Você definirá initializeTonight() na próxima etapa.
init {
   initializeTonight()
}
  1. Implemente initializeTonight() abaixo do bloco init. No uiScope, inicie uma corrotina. Dentro dele, receba o valor de tonight do banco de dados chamando getTonightFromDatabase() e atribua o valor a tonight.value. Você definirá getTonightFromDatabase() na próxima etapa.
private fun initializeTonight() {
   uiScope.launch {
       tonight.value = getTonightFromDatabase()
   }
}
  1. implementar getTonightFromDatabase(); Defina-o como uma função private suspend que retorna um SleepNight anulável, se não houver um SleepNight iniciado. Isso causa um erro, já que a função precisa retornar algo.
private suspend fun getTonightFromDatabase(): SleepNight? { }
  1. No corpo da função de getTonightFromDatabase(), retorne o resultado de uma corrotina que é executada no contexto Dispatchers.IO. Use o agente de E/S porque o acesso aos dados do banco de dados é uma operação de E/S e não tem nada a ver com a IU.
  return withContext(Dispatchers.IO) {}
  1. Dentro do bloco de retorno, deixe a corrotina chegar à noite (a noite mais recente) do banco de dados. Se os horários de início e de término não forem iguais, ou seja, se a noite já tiver sido concluída, retorne null. Caso contrário, retorne à noite.
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night

A função de suspensão concluída do getTonightFromDatabase() ficará assim: Não haverá mais erros.

private suspend fun getTonightFromDatabase(): SleepNight? {
   return withContext(Dispatchers.IO) {
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night
   }
}

Etapa 2: adicionar o gerenciador de cliques para o botão "Iniciar"

Agora, você pode implementar onStartTracking(), o gerenciador de cliques do botão Iniciar. É necessário criar um novo SleepNight, inseri-lo no banco de dados e atribuí-lo à tonight. A estrutura da onStartTracking() será muito semelhante à initializeTonight().

  1. Comece com a definição de função para onStartTracking(). É possível colocar os gerenciadores de clique acima de onCleared() no arquivo SleepTrackerViewModel.
fun onStartTracking() {}
  1. No onStartTracking(), inicie uma corrotina no uiScope, porque você precisa desse resultado para continuar e atualizar a IU.
uiScope.launch {}
  1. Dentro da corrotina, crie um novo SleepNight, que capture o horário atual como o horário de início.
        val newNight = SleepNight()
  1. Ainda na inicialização da corrotina, chame insert() para inserir newNight no banco de dados. Você verá um erro, porque ainda não definiu essa função de suspensão insert(). Esta não é a função DAO de mesmo nome.
       insert(newNight)
  1. Também na inicialização da corrotina, atualize a tonight.
       tonight.value = getTonightFromDatabase()
  1. Abaixo de onStartTracking(), defina insert() como uma função private suspend que usa um SleepNight como argumento.
private suspend fun insert(night: SleepNight) {}
  1. Para o corpo de insert(), inicie uma corrotina no contexto de E/S e insira a noite no banco de dados chamando insert() do DAO.
   withContext(Dispatchers.IO) {
       database.insert(night)
   }
  1. No arquivo de layout fragment_sleep_tracker.xml, adicione o gerenciador de cliques da onStartTracking() ao start_button usando a mágica da vinculação de dados configurada anteriormente. A notação de função @{() -> cria uma função lambda que não aceita argumentos e chama o gerenciador de cliques no sleepTrackerViewModel.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
  1. Compile e execute o app. Toque no botão Iniciar. Essa ação cria dados, mas você ainda não pode ver nada. Isso será corrigido em seguida.
fun someWorkNeedsToBeDone {
   uiScope.launch {

        suspendFunction()

   }
}

suspend fun suspendFunction() {
   withContext(Dispatchers.IO) {
       longrunningWork()
   }
}

Etapa 3: exibir os dados

No SleepTrackerViewModel, a variável nights faz referência a LiveData porque getAllNights() no DAO retorna LiveData.

Ele é um recurso Room que sempre que os dados no banco de dados mudam, o LiveData nights é atualizado para mostrar os dados mais recentes. Você nunca precisa definir ou atualizar explicitamente a LiveData. Room atualiza os dados para que correspondam ao banco de dados.

No entanto, se você exibir nights em uma visualização de texto, a referência do objeto será exibida. Para ver o conteúdo do objeto, transforme os dados em uma string formatada. Use um mapa Transformation que é executado sempre que nights recebe novos dados do banco de dados.

  1. Abra o arquivo Util.kt e remova a marca de comentário do código da definição de formatNights() e as instruções import associadas. Para remover a marca de comentário do código no Android Studio, selecione todo o código marcado com // e pressione Cmd+/ ou Control+/.
  2. O formatNights() retorna um tipo Spanned, que é uma string formatada em HTML.
  3. Abra strings.xml. Observe o uso de CDATA para formatar os recursos de string para exibir os dados de sono.
  4. Abra o SleepTrackerViewModel. Na classe SleepTrackerViewModel, abaixo da definição de uiScope, defina uma variável com o nome nights. Receba todas as noites do banco de dados e atribua-as à variável nights.
private val nights = database.getAllNights()
  1. Logo abaixo da definição de nights, adicione o código para transformar a nights em uma nightsString. Use a função formatNights() da Util.kt.

    Transmita nights pela função map() da classe Transformations. Para ter acesso aos recursos da string, defina a função de mapeamento como chamando formatNights(). Forneça nights e um objeto Resources.
val nightsString = Transformations.map(nights) { nights ->
   formatNights(nights, application.resources)
}
  1. Abra o arquivo de layout fragment_sleep_tracker.xml. Em TextView, na propriedade android:text, é possível substituir a string de recursos por uma referência a nightsString.
"@{sleepTrackerViewModel.nightsString}"
  1. Recrie seu código e execute o app. Todos os dados de sono com horários de início serão exibidos agora.
  2. Toque no botão Iniciar mais algumas vezes e você verá mais dados.

Na próxima etapa, ative o recurso do botão Parar.

Etapa 4: adicionar o gerenciador de cliques para o botão "Stop"

Com o mesmo padrão da etapa anterior, implemente o gerenciador de clique do botão Stop no SleepTrackerViewModel..

  1. Adicione onStopTracking() ao ViewModel. Inicie uma corrotina no uiScope. Se o horário de término ainda não tiver sido definido, defina endTimeMilli como o horário atual do sistema e chame update() com os dados noturnos.

    No Kotlin, a sintaxe de return@label especifica a função que retorna essa instrução, entre várias funções aninhadas.
fun onStopTracking() {
   uiScope.launch {
       val oldNight = tonight.value ?: return@launch
       oldNight.endTimeMilli = System.currentTimeMillis()
       update(oldNight)
   }
}
  1. Implemente update() usando o mesmo padrão usado para implementar insert().
private suspend fun update(night: SleepNight) {
   withContext(Dispatchers.IO) {
       database.update(night)
   }
}
  1. Para conectar o gerenciador de cliques à IU, abra o arquivo de layout fragment_sleep_tracker.xml e adicione-o ao stop_button.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
  1. Crie e execute seu aplicativo.
  2. Toque em Iniciar e em Parar. Você verá o horário de início e de término, a qualidade do sono sem valor e o tempo de sono.

Etapa 5: adicionar o gerenciador de cliques para o botão "Limpar"

  1. Da mesma forma, implemente onClear() e clear().
fun onClear() {
   uiScope.launch {
       clear()
       tonight.value = null
   }
}

suspend fun clear() {
   withContext(Dispatchers.IO) {
       database.clear()
   }
}
  1. Para conectar o gerenciador de cliques à IU, abra fragment_sleep_tracker.xml e adicione-o ao clear_button.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
  1. Crie e execute seu aplicativo.
  2. Toque em Limpar para eliminar todos os dados. Em seguida, toque em Iniciar e Parar para criar novos dados.

Projeto do Android Studio: TrackMySleepQualityCoroutines

  • Use ViewModel, ViewModelFactory e a vinculação de dados para configurar a arquitetura da IU para o app.
  • Para manter a IU funcionando sem problemas, use corrotinas para tarefas de longa duração, como todas as operações de banco de dados.
  • As corrotinas são assíncronas e sem bloqueio. Elas usam as funções suspend para tornar o código assíncrono sequencial.
  • Quando uma corrotina chama uma função marcada com suspend, em vez de bloquear até que ela retorne como uma chamada de função normal, ela suspende a execução até que o resultado esteja pronto. Em seguida, ele continua de onde parou com o resultado.
  • A diferença entre bloqueio e suspensão é que, se uma conversa for bloqueada, nenhum outro trabalho acontecerá. Se a linha de execução estiver suspensa, outras tarefas acontecerão até que o resultado seja disponibilizado.

Para iniciar uma corrotina, você precisa de um job, um agente e um escopo:

  • Basicamente, um job é qualquer coisa que pode ser cancelada. Cada corrotina tem um job, e é possível usá-lo para cancelar uma corrotina.
  • O agente envia corrotinas para serem executadas em várias linhas de execução. O Dispatcher.Main executa tarefas na linha de execução principal, e Dispartcher.IO serve para descarregar as tarefas de bloqueio de E/S para um pool compartilhado de linhas de execução.
  • O escopo combina informações, incluindo um job e um agente, para definir o contexto em que a corrotina é executada. Os escopos rastreiam as corrotinas.

Para implementar gerenciadores de cliques que acionam operações do banco de dados, siga este padrão:

  1. Iniciar uma corrotina que é executada na linha de execução principal ou de IU, porque o resultado afeta a IU.
  2. Chame uma função de suspensão para fazer o trabalho de longa duração. Assim, você não bloqueará a linha de execução de IU enquanto aguarda o resultado.
  3. O trabalho de longa duração não tem nada a ver com a IU. Portanto, alterne para o contexto de E/S. Dessa forma, o trabalho pode ser executado em um pool de linhas de execução que seja otimizado e reservado para esses tipos de operações.
  4. Em seguida, chame a função de banco de dados para realizar o trabalho.

Use um mapa Transformations para criar uma string de um objeto LiveData sempre que o objeto mudar.

Curso da Udacity:

Documentação do desenvolvedor Android:

Outros documentos e artigos:

Esta seção lista as possíveis atividades para os alunos que estão trabalhando neste codelab como parte de um curso ministrado por um instrutor. Cabe ao instrutor fazer o seguinte:

  • Se necessário, atribua o dever de casa.
  • Informe aos alunos como enviar o dever de casa.
  • Atribua nota aos trabalhos de casa.

Os professores podem usar essas sugestões o quanto quiserem, e eles devem se sentir à vontade para passar o dever de casa como achar adequado.

Se você estiver fazendo este codelab por conta própria, use essas atividades para testar seu conhecimento.

Responda a estas perguntas.

Pergunta 1

Quais das opções a seguir são vantagens das corrotinas:

  • Elas não causam bloqueios
  • Elas são executadas de forma assíncrona.
  • Elas podem ser executadas em uma linha de execução diferente da principal.
  • Elas sempre tornam os apps mais rápidos.
  • Eles podem usar exceções.
  • Elas podem ser escritas e lidas como código linear.

Pergunta 2

O que é uma função de suspensão?

  • Uma função comum, anotada com a palavra-chave suspend.
  • Uma função que pode ser chamada dentro de corrotinas.
  • Enquanto uma função de suspensão estiver em execução, a linha de execução de chamada será suspensa.
  • As funções de suspensão precisam ser sempre executadas em segundo plano.

Pergunta 3

Qual é a diferença entre bloquear e suspender uma conversa? Marque as afirmações verdadeiras.

  • Quando a execução é bloqueada, nenhum outro trabalho pode ser executado na linha de execução bloqueada.
  • Quando a execução é suspensa, a linha de execução pode fazer outro trabalho enquanto aguarda a conclusão do trabalho transferido.
  • A suspensão é mais eficiente, porque as linhas de execução podem não estar esperando e não fazem nada.
  • Seja bloqueada ou suspensa, a execução ainda aguardará o resultado da corrotina antes de continuar.

Vá para a próxima lição: 6.3 Usar o LiveData para controlar os estados do botão

Para ver links de outros codelabs neste curso, consulte a página de destino dos codelabs do curso Conceitos básicos do Kotlin para Android.