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
- Faça o download do app TrackMySleepQuality-Coroutines-Starter no GitHub.
- 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".
- Abra res/layout/activity_main.xml. Esse layout contém o fragmento
nav_host_fragment
. Observe também a tag<merge>
.
A tagmerge
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. - Na pasta navigation, abra navigation.xml. Você pode ver dois fragmentos e as ações de navegação que os conectam.
- 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
- No pacote sleeptracker, abra SleepTrackerViewModel.kt.
- Inspecione a classe
SleepTrackerViewModel
, que é fornecida no app inicial e também mostrada abaixo. Observe que a classe estendeAndroidViewModel()
. Essa classe é igual aViewModel
, 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
- No pacote sleeptracker, abra SleepTrackerViewModelFactory.kt.
- 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 oViewModel
e estendeViewModelProvider.Factory
. - Dentro da fábrica, o código substitui
create()
, que usa qualquer tipo de classe como argumento e retorna umViewModel
. - No corpo de
create()
, o código verifica se há uma classeSleepTrackerViewModel
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
- No
SleepTrackerFragment
, acesse uma referência ao contexto do aplicativo. Coloque a referência emonCreateView()
, abaixo debinding
. 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çãorequireNotNull
do Kotlin gera umIllegalArgumentException
se o valor fornull
.
val application = requireNotNull(this.activity).application
- Você precisa de uma referência à fonte de dados por meio de uma referência ao DAO. Em
onCreateView()
, antes doreturn
, defina umdataSource
. Para receber uma referência ao DAO do banco de dados, useSleepDatabase.getInstance(application).sleepDatabaseDao
.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
- Em
onCreateView()
, antes doreturn
, crie uma instância doviewModelFactory
. É necessário transmitirdataSource
eapplication
.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- Agora que você tem uma fábrica, acesse uma referência ao
SleepTrackerViewModel
. O parâmetroSleepTrackerViewModel::class.java
se refere à classe Java de tempo de execução desse objeto.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
- 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
:
- No bloco
<data>
, crie um<variable>
que faça referência à classeSleepTrackerViewModel
.
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>
Em SleepTrackerFragment
:
- 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çãoreturn
:
binding.setLifecycleOwner(this)
- Atribua a variável de vinculação
sleepTrackerViewModel
aosleepTrackerViewModel
. Coloque este código emonCreateView()
, abaixo do código que cria oSleepTrackerViewModel
:
binding.sleepTrackerViewModel = sleepTrackerViewModel
- Você provavelmente vai ver um erro porque precisa recriar o objeto de vinculação. Limpe e recrie o projeto para se livrar do erro.
- 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.
- 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 arquivobuild.gradle
do projeto comocoroutine_version =
'1.0.0'
.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
- Abra o arquivo
SleepTrackerViewModel
. - No corpo da classe, defina
viewModelJob
e atribua a ele uma instância deJob
. EsseviewModelJob
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()
- No final do corpo da classe, substitua
onCleared()
e cancele todas as corrotinas. Quando oViewModel
é destruído,onCleared()
é chamado.
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
- Logo abaixo da definição de
viewModelJob
, defina umuiScope
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 deCoroutineScope
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)
- Abaixo da definição de
uiScope
, defina uma variável chamadatonight
para armazenar a noite atual. Faça a variávelMutableLiveData
, porque você precisa observar os dados e mudá-los.
private var tonight = MutableLiveData<SleepNight?>()
- Para inicializar a variável
tonight
o mais rápido possível, crie um blocoinit
abaixo da definição detonight
e chameinitializeTonight()
. Você vai definirinitializeTonight()
na próxima etapa.
init {
initializeTonight()
}
- Abaixo do bloco
init
, implementeinitializeTonight()
. NouiScope
, inicie uma corrotina. Dentro, receba o valor detonight
do banco de dados chamandogetTonightFromDatabase()
e atribua o valor atonight.value
. Você vai definirgetTonightFromDatabase()
na próxima etapa.
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
- implementar
getTonightFromDatabase()
; Defina como uma funçãoprivate suspend
que retorna umSleepNight
anulável se não houver umSleepNight
iniciado no momento. Isso gera um erro, porque a função precisa retornar algo.
private suspend fun getTonightFromDatabase(): SleepNight? { }
- No corpo da função
getTonightFromDatabase()
, retorne o resultado de uma corrotina executada no contextoDispatchers.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) {}
- 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()
.
- Comece com a definição da função para
onStartTracking()
. Você pode colocar os manipuladores de cliques acima deonCleared()
no arquivoSleepTrackerViewModel
.
fun onStartTracking() {}
- No
onStartTracking()
, inicie uma corrotina nouiScope
, porque você precisa desse resultado para continuar e atualizar a interface.
uiScope.launch {}
- 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()
- Ainda dentro do lançamento da corrotina, chame
insert()
para inserirnewNight
no banco de dados. Você vai receber um erro porque ainda não definiu essa função de suspensãoinsert()
. Essa não é a função DAO com o mesmo nome.
insert(newNight)
- Também dentro do lançamento da corrotina, atualize
tonight
.
tonight.value = getTonightFromDatabase()
- Abaixo de
onStartTracking()
, definainsert()
como uma funçãoprivate suspend
que usa umSleepNight
como argumento.
private suspend fun insert(night: SleepNight) {}
- No corpo de
insert()
, inicie uma corrotina no contexto de E/S e insira a noite no banco de dados chamandoinsert()
do DAO.
withContext(Dispatchers.IO) {
database.insert(night)
}
- No arquivo de layout
fragment_sleep_tracker.xml
, adicione o manipulador de cliques paraonStartTracking()
aostart_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 emsleepTrackerViewModel
.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
- 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.
- Abra o arquivo
Util.kt
e remova a marca de comentário do código para a definição deformatNights()
e das instruçõesimport
associadas. Para remover o comentário do código no Android Studio, selecione todo o código marcado com//
e pressioneCmd+/
ouControl+/
. - Observe que
formatNights()
retorna um tipoSpanned
, que é uma string formatada em HTML. - Abra strings.xml. Observe o uso de
CDATA
para formatar os recursos de string e mostrar os dados de sono. - Abra SleepTrackerViewModel. Na classe
SleepTrackerViewModel
, abaixo da definição deuiScope
, defina uma variável chamadanights
. Receba todas as noites do banco de dados e atribua-as à variávelnights
.
private val nights = database.getAllNights()
- Logo abaixo da definição de
nights
, adicione o código para transformarnights
em umnightsString
. Use a funçãoformatNights()
deUtil.kt
.
Transmitanights
para a funçãomap()
da classeTransformations
. Para acessar os recursos de string, defina a função de mapeamento como uma chamada paraformatNights()
. Forneçanights
e um objetoResources
.
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
- Abra o arquivo de layout
fragment_sleep_tracker.xml
. NoTextView
, na propriedadeandroid:text
, agora é possível substituir a string de recurso por uma referência anightsString
.
"@{sleepTrackerViewModel.nightsString}"
- Recompile o código e execute o app. Todos os dados de sono com horários de início vão aparecer.
- 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.
.
- Adicione
onStopTracking()
aoViewModel
. Inicie uma corrotina nouiScope
. Se o horário de término ainda não tiver sido definido, defina oendTimeMilli
como o horário atual do sistema e chameupdate()
com os dados da noite.
Em Kotlin, a sintaxereturn@
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)
}
}
- Implemente
update()
usando o mesmo padrão deinsert()
.
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
- Para conectar o gerenciador de cliques à interface, abra o arquivo de layout
fragment_sleep_tracker.xml
e adicione o gerenciador de cliques aostop_button
.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
- Compile e execute o app.
- 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"
- Da mesma forma, implemente
onClear()
eclear()
.
fun onClear() {
uiScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}
- Para conectar o gerenciador de cliques à interface, abra
fragment_sleep_tracker.xml
e adicione o gerenciador de cliques aoclear_button
.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
- Compile e execute o app.
- 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 oDispartcher.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:
- Inicie uma corrotina que é executada na linha de execução principal ou de interface, porque o resultado afeta a interface.
- 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.
- 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.
- 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:
RoomDatabase
- Como reutilizar layouts com <include/>
ViewModelProvider.Factory
SimpleDateFormat
HtmlCompat
Outros documentos e artigos:
- Padrão de fábrica
- Codelab de corrotinas
- Documentação oficial de corrotinas (link em inglês)
- Contexto e agentes de corrotinas (link em inglês)
Dispatchers
- Exceder o limite de velocidade do Android
Job
launch
- Retornos e jumps em Kotlin (link em inglês)
- CDATA significa dados de caracteres. CDATA significa que os dados entre essas strings incluem dados que podem ser interpretados como marcação XML, mas não devem ser.
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:
Para acessar links de outros codelabs neste curso, consulte a página inicial dos codelabs de conceitos básicos do Kotlin para Android.