1. Introdução
Introdução
Uma das principais prioridades para criar uma experiência de usuário perfeita para seu aplicativo é garantir que a IU esteja sempre responsiva e funcione sem problemas. Uma maneira de melhorar o desempenho da IU é mover tarefas de longa execução, como operações de banco de dados, para o segundo plano.
Neste tutorial, você implementa a parte voltada para o usuário do aplicativo TrackMySleepQuality, usando corrotinas Kotlin para executar operações de banco de dados longe da thread principal.
O que você já deveria saber
Você deve estar familiarizado com:
- Construindo uma interface de usuário (IU) básica usando uma atividade, fragmentos, vistas e tratadores de clique.
- Navegando entre fragmentos e usando
safeArgs
para passar dados simples entre fragmentos. - Vista de modelos, fábricas de modelos de vistas, transformações e
LiveData
. - Como criar um banco de dados
Room
, criar um DAO e definir entidades. - É útil se você estiver familiarizado com os conceitos de threading e multiprocessamento.
O que aprenderá
- Como as threads funcionam no Android.
- Como usar corrotinas Kotlin para mover operações de banco de dados para longe da thread principal.
- Como exibir dados formatados em um
TextView
.
O que fará
- Estender o aplicativo TrackMySleepQuality para coletar, armazenar e exibir dados no banco de dados e a partir dele.
- Usar corrotinas para executar operações de banco de dados de longa duração em segundo plano.
- Usar
LiveData
para acionar a navegação e a exibição de uma snackbar. - Usar
LiveData
para ativar e desativar os botões.
2. Visão geral do aplicativo
Neste tutorial, você constrói o modelo de vista, corrotinas e porções de exibição de dados do aplicativo TrackMySleepQuality.
O aplicativo possui duas telas, representadas por fragmentos, conforme mostrado na figura abaixo.
A primeira tela, mostrada à esquerda, possui botões para iniciar e parar o rastreamento. A tela mostra todos os dados de sono do usuário. O botão Clear exclui permanentemente todos os dados que o aplicativo coletou para o usuário.
A segunda tela, mostrada à direita, é para selecionar uma classificação de qualidade do sono. No aplicativo, a classificação é representada numericamente. Para fins de desenvolvimento, o aplicativo mostra os ícones de rosto e seus equivalentes numéricos.
O fluxo do usuário é o seguinte:
- O usuário abre o aplicativo e é apresentado à tela de rastreamento de sono.
- O usuário toca no botão Start. Isso registra a hora de início e a exibe. O botão Start está desabilitado e o botão Stop está habilitado.
- O usuário toca no botão Stop. Isso registra a hora 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 rastreamento exibe o tempo de término do sono e a qualidade do sono. O botão Stop está desativado e o botão Start está ativado. O aplicativo está pronto para mais uma noite.
- O botão Clear é habilitado sempre que houver dados no banco de dados. Quando o usuário toca no botão Clear, todos os seus dados são apagados sem recurso - não há "Tem certeza?" mensagem.
Este aplicativo usa uma arquitetura simplificada, conforme mostrado abaixo no contexto da arquitetura completa. O aplicativo usa apenas os seguintes componentes:
- Controlador de IU
- Modelo de vistas e
LiveData
- Um banco de dados Room
3. Tarefa: Inspecione o código inicial
Nesta tarefa, você usa um TextView
para exibir os dados de rastreamento do sono formatados. (Esta
não é a interface final. Você aprenderá uma maneira melhor em outro tutorial).
Você pode continuar com o aplicativo TrackMySleepQuality que você construiu no tutorial anterior ou baixar o aplicativo inicial para este tutorial.
Etapa 1: Baixe e execute o aplicativo inicial
- Baixe o aplicativo TrackMySleepQuality-Coroutines-Starter do GitHub.
- Construa e execute o aplicativo. O aplicativo mostra a IU para o fragmento
SleepTrackerFragment
, mas nenhum dado. Os botões não respondem aos toques.
Etapa 2: Inspecione o código
O código inicial para este codelab é o mesmo que o código da solução para o tutorial Criar um Banco de Dados Room.
- Abra res/layout/activity_main.xml. Este layout contém o fragmento
nav_host_fragment
. Além disso, observe a etiqueta<merge>
.
A etiquetamerge
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, onde o sistema pode ser capaz de eliminar o LinearLayout. Esse tipo de otimização pode simplificar a hierarquia de vistas e melhorar o desempenho do aplicativo. - 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 rasthreador de sono para ver seu layout XML. Observe o seguinte:
- Os dados de layout são agrupados em um elemento
<layout>
para habilitar a vinculação de dados. ConstraintLayout
e as outras vistas são organizadas dentro do elemento<layout>
.- O arquivo possui um espaço reservado para a etiqueta
<data>
.
O aplicativo inicial também fornece dimensões, cores e estilo para a IU. O aplicativo contém um banco de dados
Room
, um DAO e uma entidade SleepNight
. Se você não concluiu o tutorial anterior,
certifique-se de explorar esses aspectos do código por conta própria.
4. Tarefa: Adicione um ViewModel
Agora que você tem um banco de dados e uma IU, precisa coletar dados, adicionar os dados ao banco de dados e
exibir os dados. Todo esse trabalho é feito no modelo de vista. Seu modelo de vistas do sleep-tracker tratará
com cliques de botão, interagirá com o banco de dados por meio do DAO e fornecerá dados à IU por meio do
LiveData
. Todas as operações de banco de dados terão que ser executadas fora da thread de interface
do usuário principal, e você fará isso usando corrotinas.
Etapa 1: Adicione SleepTrackerViewModel
- No pacote sleeptracker, abra SleepTrackerViewModel.kt.
- Inspecione a classe
SleepTrackerViewModel
, que é fornecida no aplicativo inicial e também é mostrada abaixo. Observe que a classe estendeAndroidViewModel()
. Esta classe é igual aViewModel
, mas pega o contexto do aplicativo como parâmetro e o disponibiliza como propriedade. Você precisará disso mais tarde.
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application) : AndroidViewModel(application) {
}
Etapa 2: Adicione 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")
}
}
Observe o seguinte:
- O
SleepTrackerViewModelFactory
fornecido leva o mesmo argumento que oViewModel
e estendeViewModelProvider.Factory
. - Dentro da fábrica, o código sobrescreve
create()
, que pega 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 lançará uma exceção.
Etapa 3: Atualize SleepTrackerFragment
- No
SleepTrackerFragment
, obtenha uma referência ao contexto do aplicativo. Coloque a referência emonCreateView()
, abaixo debinding
. Você precisa de uma referência ao aplicativo ao qual este fragmento está anexado, para passar para o provedor de fábrica do modelo de exibição.
A funçãorequireNotNull
Kotlin lança umaIllegalArgumentException
se o valor fornull
.
val application = requireNotNull(this.activity).application
- Você precisa de uma referência à sua fonte de dados por meio de uma referência ao DAO. Em
onCreateView()
, antes doreturn
, defina umadataSource
. Para obter 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 deviewModelFactory
. Você precisa passar odataSource
e oapplication
.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- Agora que você tem uma fábrica, obtenha uma referência para o
SleepTrackerViewModel
. O parâmetroSleepTrackerViewModel::class.java
refere-se à classe Java de tempo de execução deste objeto.
val sleepTrackerViewModel =
ViewModelProvider(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
- Seu código finalizado deve ficar assim:
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
val sleepTrackerViewModel =
ViewModelProvider(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
Este é o método onCreateView()
até agora:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
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 =
ViewModelProvider(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
return binding.root
}
Etapa 4: Adicione vinculação de dados para o modelo de vista
Com o ViewModel
básico instalado, você precisa terminar de configurar a vinculação de dados no
SleepTrackerFragment
para conectar o ViewModel
à IU.
No arquivo de layout fragment_sleep_tracker.xml
:
- Dentro do bloco
<data>
, crie uma<variable>
que faz referência à classeSleepTrackerViewModel
.
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>
Em SleepTrackerFragment
:
- Defina a atividade atual como o proprietário 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 dentro deonCreateView()
, abaixo do código que cria oSleepTrackerViewModel
:
binding.sleepTrackerViewModel = sleepTrackerViewModel
- Você provavelmente verá um erro, pois terá que recriar o objeto de vinculação. Limpe e reconstrua o projeto para se livrar do erro.
- Finalmente, como sempre, certifique-se de que seu código seja compilado e executado sem erros.
5. Conceito: Corrotinas
No Kotlin, as corrotinas são a maneira de tratar com tarefas de longa duração com elegância e eficiência. As corrotinas Kotlin permitem converter código baseado em retorno de chamada em código sequencial. O código escrito sequencialmente é normalmente mais fácil de ler e pode até usar recursos de linguagem, como exceções. No final, corrotinas e retornos de chamada fazem a mesmo: Eles esperam até que um resultado de uma tarefa de longa execução esteja disponível e continuam a execução.
As corrotinas possuem 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.
Corrotinas são assíncronas.
Uma corrotina é executada independentemente das etapas principais de execução de seu programa. Isso pode ser em paralelo ou em um processador separado. Também pode ser que, enquanto o resto do aplicativo está esperando por uma entrada, você se intromete um pouco no processamento. Um dos aspectos importantes do assíncrono é 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 pergunta que exija pesquisa e peça a um colega para encontrar a resposta. Eles saem e trabalham nisso, o que é como se estivessem fazendo o trabalho "de forma assíncrona" e "em uma thread separado". Você pode continuar a fazer outro trabalho que não dependa da resposta, até que seu colega volte e lhe diga qual é a resposta.
Corrotinas não bloqueiam.
Sem bloqueio significa que uma corrotina não bloqueia a thread principal ou IU. Portanto, com as corrotinas, os usuários sempre possuem a experiência mais tranquila possível, pois a interação da IU sempre tem prioridade.
Corrotinas usam funções de suspensão para tornar o código assíncrono sequencial.
A palavra-chave suspend
é a maneira de Kotlin de marcar uma função, ou tipo de função, como
estando disponível para corrotinas. Quando uma corrotina chama uma função marcada com suspend
, em
vez de bloquear até que a função retorne como uma chamada de função normal, a corrotina suspende a execução até
que o resultado esteja pronto. Então, a corrotina continua de onde parou, com o resultado.
Enquanto a corrotina está suspensa e aguardando um resultado, ela desbloqueia a thread em que está sendo executado. Dessa forma, outras funções ou corrotinas podem ser executadas.
A palavra-chave suspend
não especifica a thread em que o código é executado. Uma função de
suspensão pode ser executada em uma thread de segundo plano ou na thread principal.
Para usar corrotinas em Kotlin, você precisa de três argumentos:
- Um trabalho
- Um despachante
- Um escopo
Job: Basicamente, um trabalho é algo que pode ser cancelada. Cada corrotina tem um trabalho e você pode usar o trabalho para cancelar a corrotina. Os trabalhos podem ser organizados em hierarquias pai-filho. Cancelando um trabalho pai cancela imediatamente todos os filhos do trabalho, o que é muito mais conveniente do que cancelar cada corrotina manualmente
Dispatcher: O dispatcher envia corrotinas para rodar em várias threads. Por exemplo,
Dispatcher.Main
executa tarefas na thread principal e Dispatcher.IO
descarrega as
tarefas de E/S de bloqueio para um pool compartilhado de threads.
Scope: O escopo de uma corrotina define o contexto no qual a corrotina é executada. Um escopo combina informações sobre o trabalho e o despachante de uma corrotina. Os osciloscópios monitoram as corrotinas. Quando você inicia uma corrotina, ela está "em um escopo", o que significa que você indicou qual escopo manterá o controle da corrotina.
6. Tarefa: Colete e exibir os dados
Você deseja que o usuário seja capaz de interagir com os dados do sono das seguintes maneiras:
- Quando o usuário toca no botão Start, o aplicativo cria uma noite de sono e armazena a noite de sono no banco de dados.
- Quando o usuário toca no botão Stop, o aplicativo atualiza a noite com um horário de término.
- Quando o usuário toca no botão Clear, o aplicativo apaga os dados do banco de dados.
Essas operações de banco de dados podem levar muito tempo, portanto, devem ser executadas em uma thread separado.
Etapa 1: Configure corrotinas para operações de banco de dados
Quando o botão Start no aplicativo Sleep Tracker é tocado, você deseja chamar uma função no
SleepTrackerViewModel
para criar uma instância de SleepNight
e armazene a instância no
banco de dados.
Tocando 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 tratadores de cliques
para os botões do aplicativo.
- Abra o arquivo
build.gradle
de nível de aplicativo e encontre as dependências para corrotinas. Para usar corrotinas, você precisa destas dependências, que foram adicionadas.
A$ coroutine_version
é definida 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
. EsteviewModelJob
permite cancelar todas as corrotinas iniciadas por este modelo de vistas quando o modelo de vistas não é mais usado e é destruído. Dessa forma, você não acaba com corrotinas que não têm para onde voltar.
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 thread a corrotina será executada, e o escopo também precisa saber sobre o trabalho. Para obter um escopo, peça uma instância deCoroutineScope
e passe um despachante e um trabalho.
Usando Dispatchers.Main
significa que as corrotinas lançadas no uiScope
serão
executadas na thread principal. Isso é sensato para muitas corrotinas iniciadas por um ViewModel
,
pois depois que essas corrotinas executam algum processamento, elas resultam em uma atualização da IU.
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
- Abaixo da definição de
uiScope
, defina uma variável chamadatonight
para manter a noite atual. Faça a variávelMutableLiveData
, pois você precisa ser capaz de observar os dados e alterá-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ê defineinitializeTonight()
na próxima etapa.
init {
initializeTonight()
}
- Abaixo do bloco
init
, implementeinitializeTonight()
. NouiScope
, inicie uma corrotina. Dentro, obtenha o valor paratonight
do banco de dados chamandogetTonightFromDatabase()
e atribua o valor atonight.value
. Você definegetTonightFromDatabase()
na próxima etapa.
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
- Implemente
getTonightFromDatabase()
. Defina-o como uma funçãoprivate suspend
que retorna umSleepNight
anulável, se não houverSleepNight
inicializado. Isso deixa você com um erro, pois a função deve retornar algo.
private suspend fun getTonightFromDatabase(): SleepNight? { }
- Dentro do corpo da função de
getTonightFromDatabase()
, retorne o resultado de uma corrotina que é executada no contextoDispatchers.IO
. Use o distribuidor de E/S, pois obter 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) {}
- Dentro do bloco de retorno, deixe a corrotina obter esta noite (a noite mais recente) do banco de dados. Se
os horários de início e término não forem iguais, o que significa que a noite já foi concluída, retorna
null
. Caso contrário, volte à noite.
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
Sua função de suspensão getTonightFromDatabase()
concluída deve ter a seguinte aparência. 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: Adicione o tratador de cliques para o botão Iniciar
Agora você pode implementar onStartTracking()
, o tratador de clique para o botão
Start. Você precisa criar um 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 tratadores de cliques acima deonCleared()
no arquivoSleepTrackerViewModel
.
fun onStartTracking() {}
- Dentro de
onStartTracking()
, inicie uma corrotina nouiScope
, pois você precisa deste resultado para continuar e atualizar a IU.
uiScope.launch {}
- Dentro do lançamento da corrotina, crie um
SleepNight
, que captura a hora atual como a hora de início.
val newNight = SleepNight()
- Ainda dentro do lançamento da corrotina, chame
insert()
para inserirnewNight
no banco de dados. Você verá um erro, pois ainda não definiu esta função de suspensãoinsert()
. (Esta 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 leva umSleepNight
como argumento.
private suspend fun insert(night: SleepNight) {}
- Para o corpo de
insert()
, lance 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 tratador de cliques paraonStartTracking()
aostart_button
usando a magia da vinculação de dados que você configurou mais cedo. A notação de função@{() ->
cria uma função lambda que não aceita argumentos e chama o tratador de cliques nosleepTrackerViewModel
.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
- Construa e execute seu aplicativo. Toque no botão Start. Esta ação cria dados, mas você não pode ver nada ainda. Você conserta isso a seguir.
fun someWorkNeedsToBeDone { uiScope.launch { suspendFunction() } } suspend fun suspendFunction() { withContext(Dispatchers.IO) { longrunningWork() } }
Etapa 3: Exiba os dados
No SleepTrackerViewModel
, a variável nights
faz referência a LiveData
,
pois getAllNights()
no DAO retorna LiveData
.
É um recurso de 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 explicitamente
o LiveData
ou atualizá-lo. Room
atualiza os dados para corresponder ao banco de dados.
No entanto, se você exibir nights
em uma vista de texto, ele mostrará a referência do objeto. Para
ver o conteúdo do objeto, transforme os dados em uma string formatada. Use um mapa de
Transformation
que é executado toda vez que nights
recebe novos dados do banco de
dados.
- Abra o arquivo
Util.kt
e descomente o código para a definição deformatNights()
e as instruçõesimport
associadas. Para descomentar o 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 para exibir os dados de sono. - Abra SleepTrackerViewModel. Na classe
SleepTrackerViewModel
, abaixo da definição deuiScope
, defina uma variável chamadanights
. Obtenha 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 umanightsString
. Use a funçãoformatNights()
doUtil.kt
.
Passenights
para o mapamap()
função da classeTransformations
. Para obter acesso aos seus recursos de string, defina a função de mapeamento como chamandoformatNights()
. Forneçanights
e um objetoResources
.
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
- Abra o arquivo de layout
fragment_sleep_tracker.xml
. EmTextView
, na propriedadeandroid:text
, agora você pode substituir a string de recurso por uma referência anightsString
.
"@{sleepTrackerViewModel.nightsString}"
- Reconstrua seu código e execute o aplicativo. Todos os seus dados de sono com horários de início devem ser exibidos agora.
- Toque no botão Start mais algumas vezes e você verá mais dados.
Na próxima etapa, você habilita a funcionalidade do botão Stop.
Etapa 4: Adicione o tratador de cliques para o botão Parar
Usando o mesmo padrão da etapa anterior, implemente o tratador de cliques para o botão Stop em
SleepTrackerViewModel
.
- Adicione
onStopTracking()
aoViewModel
. Inicie uma corrotina nouiScope
. Se a hora de término ainda não foi definida, defina oendTimeMilli
para a hora do sistema atual e chameupdate()
com os dados noturnos.
Em Kotlin, a sintaxereturn@
label
especifica a função a partir da qual esta 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 que usou para implementarinsert()
.
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
- Para conectar o tratador de cliques à IU, abra o arquivo de layout
fragment_sleep_tracker.xml
e adicione o tratador de cliques aostop_button
.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
- Construa e execute seu aplicativo.
- Toque em Start e, em seguida, toque em Stop. Você vê a hora de início,
hora de término, qualidade do sono sem valor e tempo de sono.
Etapa 5: Adicione o tratador 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 tratador de cliques à IU, abra
fragment_sleep_tracker.xml
e adicione o tratador de cliques aoclear_button
.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
- Construa e execute seu aplicativo.
- Toque em Clear para se livrar de todos os dados. Em seguida, toque em Start e Stop para criar novos dados.
7. Código da solução
Projeto Android Studio: TrackMySleepQualityCoroutines
8. Resumo
- Use
ViewModel
,ViewModelFactory
e vinculação de dados para configurar a arquitetura de IU para o aplicativo. - 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. Eles 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 a função 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 thread for bloqueada, nenhum outro trabalho acontece. Se a thread for suspenso, outro trabalho acontece até que o resultado esteja disponível.
Para iniciar uma corrotina, você precisa de um trabalho, um despachante e um escopo:
- Basicamente, um job é qualquer algo que pode ser cancelada. Cada corrotina tem um trabalho e você pode usar um trabalho para cancelar uma corrotina.
- O despachante envia corrotinas para rodar em várias threads.
Dispatcher.Main
executa tarefas na thread principal, eDispartcher.IO
é para descarregar tarefas de E/S de bloqueio para um pool compartilhado de threads. - O escopo combina informações, incluindo um trabalho e despachante, para definir o contexto no qual a corrotina é executada. Os osciloscópios monitoram as corrotinas.
Para implementar tratadores de cliques que acionam operações de banco de dados, siga este padrão:
- Inicie uma corrotina que é executada na thread principal ou IU, pois o resultado afeta a IU.
- Chame uma função de suspensão para fazer o trabalho de longa duração, de modo que você não bloqueie a thread de IU enquanto espera pelo resultado.
- O trabalho de longa duração não tem nada a ver com a IU, então mude para o contexto de E/S. Dessa forma, o trabalho pode ser executado em um pool de threads otimizado e reservado para esses tipos de operações.
- Em seguida, chame a função de banco de dados para fazer o trabalho.
Use um mapa de Transformations
para criar uma string de um objeto LiveData
sempre que
o objeto for alterado.
9. Saiba mais
Documentação do desenvolvedor Android:
RoomDatabase
- Reutilização de layouts com <include/>
ViewModelProvider.Factory
SimpleDateFormat
HtmlCompat
Outra documentação e artigos:
- Padrão de fábrica
- Corrotinas, documentação oficial
- Contexto de corrotina e despachantes
Dispatchers
Job
launch
- Retorna e pula em Kotlin
- CDATA significa dados de caractere. CDATA significa que os dados entre essas strings incluem dados que podem ser interpretados como marcação XML, mas não deveriam ser.
10. Dever de casa
Esta seção lista as possíveis tarefas de casa para os alunos que estão trabalhando neste tutorial como parte de um curso ministrado por um instrutor.
Responda a estasperguntas
Pergunta 1
Quais das seguintes são vantagens das corrotinas:
- Eles não bloqueiam
- Eles são executados de forma assíncrona.
- Eles podem ser executados em uma thread diferente da thread principal.
- Eles sempre tornam o aplicativo executado mais rápido.
- Eles podem usar exceções.
- Eles podem ser escritos e lidos 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á em execução, a thread de chamada é suspenso.
- As funções de suspensão devem sempre ser executadas em segundo plano.
Pergunta 3
Qual é a diferença entre bloquear e suspender uma thread? Marque tudo o que é verdade.
- Quando a execução é bloqueada, nenhum outro trabalho pode ser executado na thread bloqueado.
- Quando a execução é suspensa, a thread pode realizar outro trabalho enquanto espera a conclusão do trabalho transferido.
- Suspendendo é mais eficiente, pois as threads podem não estar esperando, sem fazer nada.
- Seja bloqueada ou suspensa, a execução ainda está aguardando o resultado da corrotina antes de continuar.
11. Próximo tutorial
Comece para a próxima lição:
Para obter enlaces para outros tutoriais neste curso, consulte a página de destino dos tutoriais Fundamentos de Android em Kotlin.