Este tutorial recapitula como usar ViewModel
e fragmentos juntos para implementar a navegação.
Lembre-se que o objetivo é colocar a lógica de quando navegar no ViewModel
, mas
definir os caminhos nos fragmentos e no arquivo de navegação. Para atingir esse objetivo, você usa modelos
de vista, fragmentos, LiveData
e observadores.
O tutorial conclui mostrando uma maneira inteligente de rastrear estados de botão com código mínimo, de modo que cada botão seja habilitado e clicável apenas quando faz sentido para o usuário tocar nele.
Você deve estar familiarizado com:
safeArgs
para passar dados entre fragmentos.LiveData
e seus
observadores. Room
, criar um objeto de acesso a dados (DAO) e definir
entidades.LiveData
para rastrear os estados dos botões.LiveData
para acionar a exibição de um snackbar. LiveData
para ativar e desativar os botões.Neste tutorial, você constrói a gravação da qualidade do sono e a IU finalizada 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:
Este aplicativo usa uma arquitetura simplificada, conforme mostrado abaixo no contexto da arquitetura completa. O aplicativo usa apenas os seguintes componentes:
LiveData
Este tutorial pressupõe que você saiba como implementar a navegação usando fragmentos e o arquivo de navegação. Para economizar seu trabalho, uma boa parte desse código é fornecida.
SleepQualityFragment
. Essa classe infla o layout, obtém
o aplicativo e retorna binding.root
.SleepTrackerFragment
para SleepQualityFragment
e de volta novamente de
SleepQualityFragment
para SleepTrackerFragment
. <argument>
denominado sleepNightKey
. SleepTrackerFragment
para SleepQualityFragment
, o aplicativo
passará uma sleepNightKey
para o SleepQualityFragment
para a noite que precisa
ser atualizada. O grafo de navegação já inclui os caminhos do SleepTrackerFragment
para o
SleepQualityFragment
e vice-versa. No entanto, os tratadores de cliques que implementam a
navegação de um fragmento para o próximo ainda não estão codificados. Você adiciona esse código agora no
ViewModel
.
No tratador de cliques, você define um LiveData
que muda quando você deseja que o aplicativo
navegue para um destino diferente. O fragmento observa este LiveData
. Quando os dados são
alterados, o fragmento navega até o destino e informa ao modelo de vistas que está pronto, o que redefine a
variável de estado.
SleepTrackerViewModel
. Você precisa adicionar navegação para que, quando o usuário
tocar no botão Stop, o aplicativo navegue até SleepQualityFragment
para
coletar uma classificação de qualidade. SleepTrackerViewModel
, crie um LiveData
que muda quando você deseja que o
aplicativo navegue até SleepQualityFragment
. Use o encapsulamento para expor apenas uma
versão gettable do LiveData
para o ViewModel
. private val _navigateToSleepQuality = MutableLiveData<SleepNight>()
val navigateToSleepQuality: LiveData<SleepNight>
get() = _navigateToSleepQuality
doneNavigating()
que redefine a variável que dispara a navegação. fun doneNavigating() {
_navigateToSleepQuality.value = null
}
onStopTracking()
, acione a navegação
para o SleepQualityFragment
. Defina a variável _navigateToSleepQuality
no final
da função como a último método dentro do bloco launch{}
. Observe que esta variável é
definida para night
. Quando essa variável tem um valor, o aplicativo navega até o
SleepQualityFragment
, passando a noite._navigateToSleepQuality.value = oldNight
SleepTrackerFragment
precisa observar _navigateToSleepQuality
para que o
aplicativo saiba quando navegar. No SleepTrackerFragment
, em onCreateView()
,
adicione um observador para navigateToSleepQuality()
. Observe que a importação para isso é
ambígua e você precisa importar androidx.lifecycle.Observer
.sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer {
})
doneNavigating()
. Se sua importação for ambígua, importe
androidx.navigation.fragment.findNavController
.night ->
night?.let {
this.findNavController().navigate(
SleepTrackerFragmentDirections
.actionSleepTrackerFragmentToSleepQualityFragment(night.nightId))
sleepTrackerViewModel.doneNavigating()
}
SleepQualityFragment
. Para voltar, use o
botão Voltar do sistema. Nesta tarefa, você registra a qualidade do sono e navega de volta ao fragmento do rasthreador de sono. O
display deve ser atualizado automaticamente para mostrar o valor atualizado ao usuário. Você precisa criar
um ViewModel
e uma ViewModelFactory
, e você precisa atualizar o
SleepQualityFragment
.
sleepquality
, crie ou abra SleepQualityViewModel.kt.SleepQualityViewModel
que recebe uma sleepNightKey
e banco de
dados como argumentos. Assim como você fez para o SleepTrackerViewModel
, você precisa passar
o database
da fábrica. Você também precisa passar a sleepNightKey
da
navegação.class SleepQualityViewModel(
private val sleepNightKey: Long = 0L,
val database: SleepDatabaseDao) : ViewModel() {
}
SleepQualityViewModel
, defina um Job
e uiScope
e
substitua onCleared()
.private val viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
SleepTrackerFragment
usando o mesmo padrão acima, declare
_navigateToSleepTracker
. Implemente navigateToSleepTracker
e
doneNavigating()
.private val _navigateToSleepTracker = MutableLiveData<Boolean?>()
val navigateToSleepTracker: LiveData<Boolean?>
get() = _navigateToSleepTracker
fun doneNavigating() {
_navigateToSleepTracker.value = null
}
onSetSleepQuality()
, para todas as imagens com qualidade de
sono usar. uiScope
e mude para o despachante de E/S.tonight
usando a sleepNightKey
.Observe que o exemplo de código abaixo faz todo o trabalho no tratador de cliques, em vez de fatorar a operação do banco de dados no contexto diferente.
fun onSetSleepQuality(quality: Int) {
uiScope.launch {
withContext(Dispatchers.IO) {
val tonight = database.get(sleepNightKey) ?: return@withContext
tonight.sleepQuality = quality
database.update(tonight)
}
_navigateToSleepTracker.value = true
}
}
sleepquality
, crie ou abra SleepQualityViewModelFactory.kt
e
adicione a classe SleepQualityViewModelFactory
, conforme mostrado abaixo. Esta classe usa
uma versão do mesmo código clichê que você viu antes. Inspecione o código antes de prosseguir. class SleepQualityViewModelFactory(
private val sleepNightKey: Long,
private val dataSource: SleepDatabaseDao) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepQualityViewModel::class.java)) {
return SleepQualityViewModel(sleepNightKey, dataSource) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
SleepQualityFragment.kt
. onCreateView()
, depois de obter o application
, você precisa obter os
arguments
que vieram com a navegação. Esses argumentos estão em
SleepQualityFragmentArgs
. Você precisa extraí-los do pacote. val arguments = SleepQualityFragmentArgs.fromBundle(arguments!!)
dataSource
. val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
dataSource
e a sleepNightKey
. val viewModelFactory = SleepQualityViewModelFactory(arguments.sleepNightKey, dataSource)
ViewModel
. val sleepQualityViewModel =
ViewModelProvider(
this, viewModelFactory).get(SleepQualityViewModel::class.java)
ViewModel
ao objeto de vinculação. (Se você vir um erro com o objeto de
vinculação, ignore-o por enquanto).binding.sleepQualityViewModel = sleepQualityViewModel
androidx.lifecycle.Observer
. sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer {
if (it == true) {
this.findNavController().navigate(
SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment())
sleepQualityViewModel.doneNavigating()
}
})
fragment_sleep_quality.xml
. No bloco <data>
,
adicione uma variável para o SleepQualityViewModel
. <data>
<variable
name="sleepQualityViewModel"
type="com.example.android.trackmysleepquality.sleepquality.SleepQualityViewModel" />
</data>
android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(5)}"
Parabéns! Você acabou de construir um aplicativo de banco de dados Room
completo usando
corrotinas.
Agora seu aplicativo funciona muito bem. O usuário pode tocar em Start e Stop quantas vezes quiser. Quando o usuário toca em Stop, ele pode inserir uma qualidade de sono. Quando o usuário toca em Clear, todos os dados são limpos silenciosamente em segundo plano. No entanto, todos os botões estão sempre habilitados e clicáveis, o que não interrompe o aplicativo, mas permite que os usuários criem noites de sono incompletas.
Nesta última tarefa, você aprenderá a usar mapas de transformação para gerenciar a visibilidade do botão para que os usuários possam fazer apenas a escolha certa. Você pode usar um método semelhante para exibir uma mensagem amigável após todos os dados terem sido apagados.
A ideia é definir o estado do botão para que, no início, apenas o botão Start esteja habilitado, o que significa que é clicável.
Depois que o usuário toca em Start, o botão Stop é habilitado e Start não. O botão Clear somente é habilitado quando há dados no banco de dados.
fragment_sleep_tracker.xml
.android:enabled
a cada botão. A propriedade android:enabled
é um valor booleano que indica se o
botão está
habilitado ou não. (Um botão habilitado pode ser tocado; um botão desabilitado não). Provenha à
propriedade o valor de uma variável de estado que você definirá em um momento.start_button
:
android:enabled="@{sleepTrackerViewModel.startButtonVisible}"
stop_button
:
android:enabled="@{sleepTrackerViewModel.stopButtonVisible}"
clear_button
:
android:enabled="@{sleepTrackerViewModel.clearButtonVisible}"
SleepTrackerViewModel
e crie três variáveis correspondentes. Atribua a cada variável
uma transformação que a testa. tonight
for null
.
tonight
não for null
.
nights
e, portanto, o banco
de dados contiver noites de sono.val startButtonVisible = Transformations.map(tonight) {
it == null
}
val stopButtonVisible = Transformations.map(tonight) {
it != null
}
val clearButtonVisible = Transformations.map(nights) {
it?.isNotEmpty()
}
Depois que o usuário limpar o banco de dados, mostre ao usuário uma confirmação usando o widget Snackbar
. Uma snackbar fornece um breve retorno sobre uma
operação por meio de uma mensagem na parte inferior da tela. Uma snackbar desaparece após um tempo limite,
após uma interação do usuário em outro lugar na tela ou depois que o usuário desliza a snackbar para fora da
tela.
Mostrando a snackbar é uma tarefa de IU e deve acontecer no fragmento. A decisão de mostrar a snackbar
acontece no ViewModel
. Para configurar e acionar uma snackbar quando os dados forem apagados,
você pode usar a mesma técnica usada para acionar a navegação.
SleepTrackerViewModel
, crie o evento encapsulado.private var _showSnackbarEvent = MutableLiveData<Boolean>()
val showSnackBarEvent: LiveData<Boolean>
get() = _showSnackbarEvent
doneShowingSnackbar()
. fun doneShowingSnackbar() {
_showSnackbarEvent.value = false
}
SleepTrackerFragment
, em onCreateView()
, adicione um observador:sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer { })
if (it == true) {
Snackbar.make(
activity!!.findViewById(android.R.id.content),
getString(R.string.cleared_message),
Snackbar.LENGTH_SHORT
).show()
sleepTrackerViewModel.doneShowingSnackbar()
}
SleepTrackerViewModel
, acione o evento no método onClear()
. Para fazer
isso, defina o valor do evento como true
dentro do bloco launch
:_showSnackbarEvent.value = true
Projeto Android Studio: TrackMySleepQualityFinal
Implementando o rastreamento da qualidade do sono neste aplicativo é como tocar uma peça familiar de música em um novo tom. Embora os detalhes mudem, o padrão subjacente do que você fez nos tutoriais anteriores desta lição permanece o mesmo. Estando ciente desses padrões torna a codificação muito mais rápida, pois você pode reutilizar o código de aplicativos existentes. Aqui estão alguns dos padrões usados neste curso até agora:
ViewModel
e uma ViewModelFactory
e configure uma fonte de dados.LiveData
para rastrear e responder às mudanças de estado. LiveData
. Navegação desencadeada
Você define caminhos de navegação possíveis entre fragmentos em um arquivo de navegação. Existem algumas maneiras diferentes de acionar a navegação de um fragmento para o próximo. Esses incluem:
onClick
para acionar a navegação para um fragmento de destino. LiveData
para registrar se a navegação ocorrer.LiveData
. Configurando o android: Atributo ativado
android:enabled
é definido em TextView
e
herdado por
todas as subclasses, incluindo Button
.android:enabled
determina se uma View
está ou não ativada. O
significado de "habilitado" varia de acordo com a subclasse. Por exemplo, um EditText
não
ativado impede que o usuário edite o texto contido, e um Button
não ativado evita que o
usuário toque no botão.enabled
não é o mesmo que o atributo visibility
.enabled
dos botões
com base no estado de outro objeto ou variável. Outros pontos abordados neste tutorial:
Snackbar
para notificar o usuário.Documentação para desenvolvimento em Android:
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.
Uma maneira de habilitar seu aplicativo para acionar a navegação de um fragmento para o próximo é usar um
valor LiveData
para indicar se deve ou não acionar a navegação.
Quais são as etapas para usar um valor LiveData
, chamado gotoBlueFragment
, para
acionar a navegação do fragmento vermelho para o fragmento azul? Selecione tudo que se aplica:
ViewModel
, defina o valor LiveData
gotoBlueFragment
.
RedFragment
, observe o valor gotoBlueFragment
. Implemente o código
observe{}
para navegar até BlueFragment
quando apropriado e, em seguida,
redefina o valor de gotoBlueFragment
para indicar que a navegação foi concluída.gotoBlueFragment
para o valor que aciona
a navegação sempre que o aplicativo precisa ir de RedFragment
para
BlueFragment
.onClick
para a View
em que
o usuário clica para navegar até BlueFragment
, onde o onClick
o tratador
observa o valor goToBlueFragment
.Você pode alterar se um Button
está habilitado (clicável) ou não usando LiveData
.
Como você garantiria que seu aplicativo mudasse o botão UpdateNumber
para que:
myNumber
tiver um valor maior que 5. myNumber
for igual ou menor que 5. Suponha que o layout que contém o botão UpdateNumber
inclua a variável
<data>
para o NumbersViewModel
conforme mostrado aqui:
<data> <variable name="NumbersViewModel" type="com.example.android.numbersapp.NumbersViewModel" /> </data>
Suponha que o ID do botão no arquivo de layout seja o seguinte:
android:id="@+id/update_number_button"
O que mais você precisa fazer? Selecione tudo que se aplica.
NumbersViewModel
, defina uma variável LiveData
,
myNumber
, que representa o número. Defina também uma variável cujo valor é definido chamando
Transform.map()
na variável myNumber
, que retorna um booleano indicando se o
número é ou não maior que 5. ViewModel
, adicione o seguinte
código:val myNumber: LiveData<Int>
val enableUpdateNumberButton = Transformations.map(myNumber) {
myNumber > 5
}
android:enabled
do botão
update_number_button button
para NumberViewModel.enableUpdateNumbersButton
.
android:enabled="@{NumbersViewModel.enableUpdateNumberButton}"
Fragment
que usa a classe NumbersViewModel
, adicione um observador ao
atributo enabled
do botão. Fragment
, adicione o
seguinte código:viewModel.enabled.observe(this, Observer<Boolean> { isEnabled ->
myNumber > 5
})
android:enabled
do botão
update_number_button button
para "Observable"
.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.