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ê vai aproveitar mais este curso se fizer os codelabs em sequência. Todos os codelabs do curso estão listados na página inicial dos codelabs de princípios básicos do Kotlin para Android.

Introdução

Uma das principais prioridades para criar uma experiência do usuário perfeita no seu app é garantir que a interface esteja sempre responsiva e funcione sem problemas. Uma maneira de melhorar o desempenho da interface é mover tarefas de longa duração, como operações de banco de dados, para segundo plano.

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

O que você já precisa saber

Você precisa:

  • Criar uma interface do usuário (UI) básica usando uma atividade, fragmentos, visualizações e gerenciadores de cliques.
  • Navegar entre fragmentos e usar safeArgs para transmitir dados simples entre eles.
  • Visualizar modelos, fábricas de modelos, transformações e LiveData.
  • Como criar um banco de dados Room, um DAO e definir entidades.
  • É útil se você conhece os conceitos de threading 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 mostrar dados formatados em uma TextView.

Atividades deste laboratório

  • Estenda o app TrackMySleepQuality para coletar, armazenar e mostrar dados no banco de dados e dele.
  • Use 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 uma snackbar.
  • Use LiveData para ativar e desativar botões.

Neste codelab, você vai criar o ViewModel, as corrotinas e as partes de 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 parar 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 do usuário.

A segunda tela, mostrada à direita, é 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 rosto e os equivalentes numéricos deles.

O fluxo do usuário é o seguinte:

  • O usuário abre o app e a tela de monitoramento do sono aparece.
  • O usuário toca no botão Iniciar. Isso registra e mostra o horário de início. O botão Iniciar está desativado, e o botão Parar está ativado.
  • O usuário toca no botão Parar. Isso 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 fecha, e a tela de monitoramento mostra a hora de fim do sono e a qualidade dele. O botão Parar está desativado, e o botão Iniciar está ativado. O app está pronto para outra noite.
  • O botão Limpar fica ativado sempre que há dados no banco de dados. Quando o usuário toca no botão Limpar, todos os dados são apagados sem recurso. Não há uma mensagem "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 interface
  • Mostrar modelo e LiveData
  • Um banco de dados do Room

Nesta tarefa, você vai usar um TextView para mostrar os dados formatados de monitoramento do sono. (Esta não é a interface final. Você vai aprender uma maneira melhor em outro codelab.)

Você pode continuar com o app TrackMySleepQuality criado no codelab anterior ou baixar o 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. Ele vai mostrar a interface do fragmento SleepTrackerFragment, mas sem dados. Os botões não respondem aos toques.

Etapa 2: inspecionar o código

O código inicial deste codelab é o mesmo do código da solução do codelab 6.1 "Criar um banco de dados do 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, e é uma boa ideia usá-la. Um exemplo de layout redundante seria 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 a performance do app.
  2. Na pasta navigation, abra navigation.xml. Você pode ver dois fragmentos e as ações de navegação que os conectam.
  3. Na pasta layout, clique duas vezes no fragmento do rastreador de sono para conferir o layout XML dele. Confira se:
  • Os dados de layout são encapsulados 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 de marcador de posição <data>.

O app inicial também fornece dimensões, cores e estilos para a interface. O app contém um banco de dados Room, um DAO e uma entidade SleepNight. Se você não concluiu o codelab anterior, confira esses aspectos do código por conta própria.

Agora que você tem um banco de dados e uma interface, precisa coletar dados, adicioná-los ao banco de dados e mostrá-los. Todo esse trabalho é feito no modelo de visualização. O modelo de visualização do rastreador de sono vai processar cliques de botão, interagir com o banco de dados usando o DAO e fornecer dados à interface usando LiveData. Todas as operações do banco de dados precisarão ser executadas na linha de execução de interface principal. Isso será feito com corrotinas.

Etapa 1: adicionar SleepTrackerViewModel

  1. No pacote sleeptracker, abra SleepTrackerViewModel.kt.
  2. Inspecione a classe SleepTrackerViewModel, que é fornecida no app inicial e também mostrada abaixo. Observe que a classe estende AndroidViewModel(). Essa classe é igual a ViewModel, mas usa o contexto do aplicativo como um parâmetro e o disponibiliza como uma propriedade. Você vai 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 fornecido 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 o ViewModel e estende ViewModelProvider.Factory.
  • Dentro da fábrica, o código substitui 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 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 requireNotNull do Kotlin gera um IllegalArgumentException se o valor for null.
val application = requireNotNull(this.activity).application
  1. Você precisa de uma referência à fonte de dados por meio de uma referência ao DAO. Em onCreateView(), antes do return, defina um dataSource. Para receber uma referência ao DAO do banco de dados, use SleepDatabase.getInstance(application).sleepDatabaseDao.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. Em onCreateView(), antes do return, crie uma instância do viewModelFactory. É necessário transmitir dataSource e 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 se refere à classe Java de tempo de execução desse objeto.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)
  1. O código finalizado vai 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)

Confira 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 vinculação de dados ao modelo de visualização

Com o ViewModel básico no lugar, é necessário concluir a configuração da vinculação de dados no SleepTrackerFragment para conectar o ViewModel à interface.


No arquivo de layout fragment_sleep_tracker.xml:

  1. No bloco <data>, crie um <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 proprietária do ciclo de vida da vinculação. Adicione este código dentro do método onCreateView(), antes da instrução return:
binding.setLifecycleOwner(this)
  1. Atribua a variável de vinculação sleepTrackerViewModel ao sleepTrackerViewModel. Coloque este código em onCreateView(), abaixo do código que cria o SleepTrackerViewModel:
binding.sleepTrackerViewModel = sleepTrackerViewModel
  1. Você provavelmente vai ver um erro porque precisa recriar o objeto de vinculação. Limpe e recrie o projeto para se livrar do erro.
  2. Por fim, como sempre, verifique se o código é criado 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. Geralmente, é mais fácil ler um código programado de maneira sequencial. Além disso, ele também pode usar recursos de linguagem, como exceções. No fim, corrotinas e callbacks fazem o mesmo trabalho: esperam até que um resultado esteja disponível em 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 suspend para tornar o código assíncrono sequencial.

As corrotinas são assíncronas.

Uma corrotina é executada de forma independente das etapas principais de execução do programa. Isso pode ser feito em paralelo ou em um processador separado. Também é possível que, enquanto o restante do app aguarda entrada, você faça um pouco de processamento. Um dos aspectos importantes do async é que você não pode esperar que o resultado esteja disponível até que você espere explicitamente por ele.

Por exemplo, digamos que você tenha uma dúvida que exija pesquisa e peça a um colega para encontrar a resposta. Eles vão embora e trabalham nisso, como se estivessem fazendo o trabalho "de forma assíncrona" e "em uma linha de execução separada". Você pode continuar fazendo outros trabalhos que não dependem da resposta até que seu colega volte e diga qual é a resposta.

As corrotinas não causam bloqueios.

Não bloqueadora significa que uma corrotina não bloqueia a linha de execução principal ou de interface. Assim, com as corrotinas, os usuários sempre têm a experiência mais tranquila possível, porque a interação da interface 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 do Kotlin de marcar uma função ou um 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 a função seja retornada como uma chamada de função normal, a corrotina suspende a execução até que o resultado esteja pronto. Em seguida, a corrotina é retomada 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. Assim, 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 em Kotlin, você precisa de três coisas:

  • Um trabalho
  • Um despachante
  • Um escopo

Job: basicamente, um job é qualquer coisa que pode ser cancelada. Toda corrotina tem um job, e você pode usar esse job para cancelar a corrotina. Os jobs podem ser organizados em hierarquias pai-filho. Ao cancelar um job principal, todos os jobs filhos são cancelados imediatamente, o que é muito mais conveniente do que cancelar cada corrotina manualmente.

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

Escopo : o escopo de uma corrotina define o contexto em que ela é executada. Um escopo combina informações sobre o job e o dispatcher de uma corrotina. Os escopos rastreiam as corrotinas. Quando você inicia uma corrotina, ela está "em um escopo", o que significa que você indicou qual escopo vai acompanhar a corrotina.

Você quer que o usuário possa interagir 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 a armazena no banco de dados.
  • Quando o usuário toca no botão Parar, 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 do banco de dados.

Como essas operações podem levar muito tempo, elas precisam 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 Sleep Tracker é 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.

Tocar em qualquer um dos botões aciona uma operação de banco de dados, como criar ou atualizar um SleepNight. Por esse e outros motivos, você usa corrotinas para implementar manipuladores de cliques nos 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 ele uma instância de Job. Esse viewModelJob permite cancelar todas as corrotinas iniciadas por esse modelo de visualização quando ele não é mais usado e é destruído. Assim, você não terá corrotinas sem um lugar para retornar.
private var viewModelJob = Job()
  1. No final do corpo da classe, substitua onCleared() e cancele todas as corrotinas. Quando o ViewModel é destruído, 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, e ele também precisa saber sobre o job. Para receber um escopo, peça uma instância de CoroutineScope e transmita um dispatcher e um job.

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

private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  1. Abaixo da definição de uiScope, defina uma variável chamada tonight para armazenar a noite atual. Faça a variável MutableLiveData, porque você precisa observar os dados e mudá-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ê vai definir initializeTonight() na próxima etapa.
init {
   initializeTonight()
}
  1. Abaixo do bloco init, implemente initializeTonight(). No uiScope, inicie uma corrotina. Dentro, receba o valor de tonight do banco de dados chamando getTonightFromDatabase() e atribua o valor a tonight.value. Você vai definir getTonightFromDatabase() na próxima etapa.
private fun initializeTonight() {
   uiScope.launch {
       tonight.value = getTonightFromDatabase()
   }
}
  1. implementar getTonightFromDatabase(); Defina como uma função private suspend que retorna um SleepNight anulável se não houver um SleepNight iniciado no momento. Isso gera um erro, porque a função precisa retornar algo.
private suspend fun getTonightFromDatabase(): SleepNight? { }
  1. No corpo da função getTonightFromDatabase(), retorne o resultado de uma corrotina executada no contexto Dispatchers.IO. Use o agente de E/S, porque a obtenção de dados do banco de dados é uma operação de E/S e não tem nada a ver com a interface.
  return withContext(Dispatchers.IO) {}
  1. Dentro do bloco de retorno, deixe a corrotina buscar a noite de hoje (a mais recente) no banco de dados. Se os horários de início e término não forem iguais, ou seja, a noite já tiver sido concluída, retorne null. Caso contrário, retorne a noite.
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night

A função de suspensão getTonightFromDatabase() concluída vai ficar assim: Não deve 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 Start. É preciso criar um novo SleepNight, inseri-lo no banco de dados e atribuí-lo a tonight. A estrutura de onStartTracking() será muito parecida com initializeTonight().

  1. Comece com a definição da função para onStartTracking(). Você pode colocar os manipuladores de cliques 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 interface.
uiScope.launch {}
  1. Dentro do lançamento da corrotina, crie um novo SleepNight, que captura o horário atual como o horário de início.
        val newNight = SleepNight()
  1. Ainda dentro do lançamento da corrotina, chame insert() para inserir newNight no banco de dados. Você vai receber um erro porque ainda não definiu essa função de suspensão insert(). Essa não é a função DAO com o mesmo nome.
       insert(newNight)
  1. Também dentro do lançamento da corrotina, atualize 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. No 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 manipulador de cliques para onStartTracking() ao start_button usando a vinculação de dados que você configurou antes. A notação de função @{() -> cria uma função lambda que não usa argumentos e chama o gerenciador de cliques em sleepTrackerViewModel.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
  1. Crie e execute o app. Toque no botão Start. Essa ação cria dados, mas ainda não é possível ver nada. Isso será corrigido em seguida.
fun someWorkNeedsToBeDone {
   uiScope.launch {

        suspendFunction()

   }
}

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

Etapa 3: mostrar os dados

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

É um recurso do Room que atualiza o LiveData nights sempre que os dados no banco de dados mudam para mostrar as informações mais recentes. Não é necessário definir ou atualizar explicitamente o LiveData. Room atualiza os dados para corresponder ao banco de dados.

No entanto, se você mostrar nights em uma visualização de texto, a referência do objeto vai aparecer. Para conferir o conteúdo do objeto, transforme os dados em uma string formatada. Use um mapa Transformation 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 para a definição de formatNights() e das instruções import associadas. Para remover o comentário do código no Android Studio, selecione todo o código marcado com // e pressione Cmd+/ ou Control+/.
  2. Observe que 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 e mostrar os dados de sono.
  4. Abra SleepTrackerViewModel. Na classe SleepTrackerViewModel, abaixo da definição de uiScope, defina uma variável chamada 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 nights em um nightsString. Use a função formatNights() de Util.kt.

    Transmita nights para a função map() da classe Transformations. Para acessar os recursos de string, defina a função de mapeamento como uma chamada para 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. No TextView, na propriedade android:text, agora é possível substituir a string de recurso por uma referência a nightsString.
"@{sleepTrackerViewModel.nightsString}"
  1. Recompile o código e execute o app. Todos os dados de sono com horários de início vão aparecer.
  2. Toque no botão Iniciar mais algumas vezes para ver mais dados.

Na próxima etapa, você vai ativar a funcionalidade do botão Parar.

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

Usando o mesmo padrão da etapa anterior, implemente o manipulador de cliques do botão Stop em 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 o endTimeMilli como o horário atual do sistema e chame update() com os dados da noite.

    Em Kotlin, a sintaxe return@label especifica a função de que essa instrução retorna, 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 de insert().
private suspend fun update(night: SleepNight) {
   withContext(Dispatchers.IO) {
       database.update(night)
   }
}
  1. Para conectar o gerenciador de cliques à interface, abra o arquivo de layout fragment_sleep_tracker.xml e adicione o gerenciador de cliques ao stop_button.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
  1. Compile e execute o app.
  2. Toque em Iniciar e em Parar. Você vê o horário de início, o horário de término, a qualidade do sono sem valor e o tempo dormido.

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 à interface, abra fragment_sleep_tracker.xml e adicione o gerenciador de cliques ao clear_button.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
  1. Compile e execute o app.
  2. Toque em Limpar para remover todos os dados. Em seguida, toque em Iniciar e Parar para gerar novos dados.

Projeto do Android Studio: TrackMySleepQualityCoroutines (link em inglês)

  • Use ViewModel, ViewModelFactory e vinculação de dados para configurar a arquitetura da interface do app.
  • Para manter a interface 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 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 essa função seja retornada como uma chamada de função normal, ela suspende a execução até que o resultado esteja pronto. Em seguida, ele retoma de onde parou com o resultado.
  • A diferença entre bloquear e suspender é que, se uma linha de execução for bloqueada, nenhum outro trabalho será realizado. Se a linha de execução for suspensa, outro trabalho será realizado até que o resultado esteja disponível.

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

  • Basicamente, um job é qualquer coisa que possa ser cancelada. Toda corrotina tem um job, e você pode usar um job 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 o Dispartcher.IO é usado para realizar tarefas de E/S de bloqueio em um conjunto compartilhado de linhas de execução.
  • O escopo combina informações, incluindo um job e um dispatcher, para definir o contexto em que a corrotina é executada. Os escopos rastreiam as corrotinas.

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

  1. Inicie uma corrotina que é executada na linha de execução principal ou de interface, porque o resultado afeta a interface.
  2. Chame uma função de suspensão para fazer o trabalho de longa duração, evitando o bloqueio da linha de execução da UI enquanto aguarda o resultado.
  3. O trabalho de longa duração não tem nada a ver com a interface, então mude para o contexto de E/S. Assim, o trabalho pode ser executado em um pool de linhas de execução otimizado e reservado para esses tipos de operações.
  4. Em seguida, chame a função do banco de dados para fazer o trabalho.

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

Curso da Udacity:

Documentação para desenvolvedores Android:

Outros documentos e artigos:

Esta seção lista as possíveis atividades de dever de casa para os alunos que estão fazendo este codelab como parte de um curso ministrado por um professor. Cabe ao professor fazer o seguinte:

  • Atribuir o dever de casa, se necessário.
  • Informar aos alunos como enviar deveres de casa.
  • Atribuir nota aos deveres de casa.

Os professores podem usar essas sugestões o quanto quiserem, podendo passar os exercícios que acharem mais apropriados como dever de casa.

Se você estiver seguindo este codelab por conta própria, sinta-se à vontade para usar esses deveres de casa para testar seu conhecimento.

Responda estas perguntas

Pergunta 1

Quais das seguintes opções 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.
  • Elas causam 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 está sendo executada, a linha de execução da chamada é suspensa.
  • As funções de suspensão precisam sempre ser executadas em segundo plano.

Pergunta 3

Qual é a diferença entre bloquear e suspender uma encadeamento? Marque todas as opçõ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 em espera.
  • Seja bloqueada ou suspensa, a execução ainda aguardará o resultado da corrotina antes de continuar.

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

Para acessar links de outros codelabs neste curso, consulte a página inicial dos codelabs de conceitos básicos do Kotlin para Android.