Questo codelab fa parte del corso Android Kotlin Fundamentals. Per ottenere il massimo valore da questo corso, ti consigliamo di seguire le codelab in sequenza. Tutti i codelab del corso sono elencati nella pagina di destinazione dei codelab Android Kotlin Fundamentals.
Introduzione
Questo codelab riepiloga come utilizzare ViewModel
e i fragment insieme per implementare la navigazione. Ricorda che l'obiettivo è inserire la logica di quando navigare in ViewModel
, ma definire i percorsi nei fragment e nel file di navigazione. Per raggiungere questo obiettivo, utilizzi view model, fragment, LiveData
e osservatori.
Il codelab si conclude mostrando un modo intelligente per monitorare gli stati dei pulsanti con un codice minimo, in modo che ogni pulsante sia abilitato e cliccabile solo quando è opportuno che l'utente lo tocchi.
Cosa devi già sapere
Devi avere familiarità con:
- Creazione di un'interfaccia utente (UI) di base utilizzando un'attività, frammenti e visualizzazioni.
- Spostarsi tra i fragment e utilizzare
safeArgs
per passare i dati tra i fragment. - Visualizza modelli, fabbriche di modelli, trasformazioni e
LiveData
e i relativi osservatori. - Come creare un database
Room
, creare un oggetto di accesso ai dati (DAO) e definire le entità. - Come utilizzare le coroutine per le interazioni con il database e altre attività di lunga durata.
Obiettivi didattici
- Come aggiornare un record esistente sulla qualità del sonno nel database.
- Come utilizzare
LiveData
per monitorare gli stati dei pulsanti. - Come visualizzare una snackbar in risposta a un evento.
In questo lab proverai a:
- Estendi l'app TrackMySleepQuality per raccogliere una valutazione della qualità, aggiungerla al database e visualizzare il risultato.
- Utilizza
LiveData
per attivare la visualizzazione di una snackbar. - Usa
LiveData
per attivare e disattivare i pulsanti.
In questo codelab, creerai la registrazione della qualità del sonno e l'interfaccia utente finale dell'app TrackMySleepQuality.
L'app ha due schermate, rappresentate da frammenti, come mostrato nella figura seguente.
La prima schermata, mostrata a sinistra, ha pulsanti per avviare e interrompere il monitoraggio. La schermata mostra tutti i dati sul sonno dell'utente. Il pulsante Cancella elimina definitivamente tutti i dati raccolti dall'app per l'utente.
La seconda schermata, mostrata a destra, serve per selezionare una valutazione della qualità del sonno. Nell'app, la valutazione è rappresentata numericamente. A scopo di sviluppo, l'app mostra sia le icone delle facce sia i relativi equivalenti numerici.
Il flusso dell'utente è il seguente:
- L'utente apre l'app e visualizza la schermata di monitoraggio del sonno.
- L'utente tocca il pulsante Avvia. Viene registrata l'ora di inizio e visualizzata. Il pulsante Avvia è disattivato e il pulsante Interrompi è attivato.
- L'utente tocca il pulsante Interrompi. Viene registrata l'ora di fine e si apre la schermata della qualità del sonno.
- L'utente seleziona un'icona della qualità del sonno. La schermata si chiude e la schermata di monitoraggio mostra l'ora di fine del sonno e la qualità del sonno. Il pulsante Interrompi è disattivato e il pulsante Avvia è attivato. L'app è pronta per un'altra notte.
- Il pulsante Cancella è attivo ogni volta che sono presenti dati nel database. Quando l'utente tocca il pulsante Cancella, tutti i suoi dati vengono cancellati senza possibilità di recupero. Non viene visualizzato alcun messaggio di conferma.
Questa app utilizza un'architettura semplificata, come mostrato di seguito nel contesto dell'architettura completa. L'app utilizza solo i seguenti componenti:
- Controller UI
- Visualizza modello e
LiveData
- Un database delle stanze
Questo codelab presuppone che tu sappia come implementare la navigazione utilizzando i fragment e il file di navigazione. Per risparmiarti del lavoro, gran parte di questo codice viene fornita.
Passaggio 1: esamina il codice
- Per iniziare, continua con il tuo codice dalla fine dell'ultimo codelab o scarica il codice iniziale.
- Nel codice iniziale, esamina
SleepQualityFragment
. Questa classe gonfia il layout, recupera l'applicazione e restituiscebinding.root
. - Apri navigation.xml nell'editor di progettazione. Vedi che esiste un percorso di navigazione da
SleepTrackerFragment
aSleepQualityFragment
e viceversa daSleepQualityFragment
aSleepTrackerFragment
. - Ispeziona il codice di navigation.xml. In particolare, cerca
<argument>
denominatosleepNightKey
.
Quando l'utente passa daSleepTrackerFragment
aSleepQualityFragment,
, l'app trasmette unsleepNightKey
aSleepQualityFragment
per la notte da aggiornare.
Passaggio 2: aggiungi la navigazione per il monitoraggio della qualità del sonno
Il grafico di navigazione include già i percorsi da SleepTrackerFragment
a SleepQualityFragment
e viceversa. Tuttavia, i gestori dei clic che implementano la navigazione da un frammento all'altro non sono ancora codificati. Aggiungi questo codice ora in ViewModel
.
Nel gestore dei clic, imposti un LiveData
che cambia quando vuoi che l'app si sposti in una destinazione diversa. Il frammento rispetta questo LiveData
. Quando i dati cambiano, il fragment passa alla destinazione e comunica al modello di visualizzazione che l'operazione è terminata, il che reimposta la variabile di stato.
- Apri
SleepTrackerViewModel
. Devi aggiungere la navigazione in modo che quando l'utente tocca il pulsante Stop, l'app passi aSleepQualityFragment
per raccogliere una valutazione della qualità. - In
SleepTrackerViewModel
, crea unLiveData
che cambia quando vuoi che l'app passi aSleepQualityFragment
. Utilizza l'incapsulamento per esporre solo una versione ottenibile diLiveData
aViewModel
.
Puoi inserire questo codice ovunque nel livello superiore del corpo della classe.
private val _navigateToSleepQuality = MutableLiveData<SleepNight>()
val navigateToSleepQuality: LiveData<SleepNight>
get() = _navigateToSleepQuality
- Aggiungi una funzione
doneNavigating()
che reimposta la variabile che attiva la navigazione.
fun doneNavigating() {
_navigateToSleepQuality.value = null
}
- Nel gestore dei clic per il pulsante Arresta,
onStopTracking()
, attiva la navigazione fino aSleepQualityFragment
. Imposta la variabile _navigateToSleepQuality
alla fine della funzione come ultima cosa all'interno del bloccolaunch{}
. Tieni presente che questa variabile è impostata sunight
. Quando questa variabile ha un valore, l'app passa allaSleepQualityFragment
, trasmettendo la notte.
_navigateToSleepQuality.value = oldNight
SleepTrackerFragment
deve osservare _navigateToSleepQuality
in modo che l'app sappia quando navigare. InSleepTrackerFragment
, inonCreateView()
, aggiungi un osservatore pernavigateToSleepQuality()
. Tieni presente che l'importazione è ambigua e devi importareandroidx.lifecycle.Observer
.
sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer {
})
- All'interno del blocco dell'osservatore, vai avanti e passa l'ID della notte corrente, poi chiama
doneNavigating()
. Se l'importazione è ambigua, importaandroidx.navigation.fragment.findNavController
.
night ->
night?.let {
this.findNavController().navigate(
SleepTrackerFragmentDirections
.actionSleepTrackerFragmentToSleepQualityFragment(night.nightId))
sleepTrackerViewModel.doneNavigating()
}
- Crea ed esegui l'app. Tocca Avvia, quindi tocca Arresta, che ti porta alla schermata
SleepQualityFragment
. Per tornare indietro, usa il pulsante Indietro del sistema.
In questa attività, registri la qualità del sonno e torni al fragment del monitoraggio del sonno. Il display dovrebbe aggiornarsi automaticamente per mostrare all'utente il valore aggiornato. Devi creare un ViewModel
e un ViewModelFactory
e aggiornare SleepQualityFragment
.
Passaggio 1: crea un ViewModel e un ViewModelFactory
- Nel pacchetto
sleepquality
, crea o apri SleepQualityViewModel.kt. - Crea una classe
SleepQualityViewModel
che accetta unsleepNightKey
e un database come argomenti. Come hai fatto perSleepTrackerViewModel
, devi inseriredatabase
di fabbrica. Devi anche passaresleepNightKey
dalla navigazione.
class SleepQualityViewModel(
private val sleepNightKey: Long = 0L,
val database: SleepDatabaseDao) : ViewModel() {
}
- All'interno della classe
SleepQualityViewModel
, definisciJob
euiScope
ed esegui l'override dionCleared()
.
private val viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
- Per tornare a
SleepTrackerFragment
utilizzando lo stesso pattern di prima, dichiara_navigateToSleepTracker
. ImplementanavigateToSleepTracker
edoneNavigating()
.
private val _navigateToSleepTracker = MutableLiveData<Boolean?>()
val navigateToSleepTracker: LiveData<Boolean?>
get() = _navigateToSleepTracker
fun doneNavigating() {
_navigateToSleepTracker.value = null
}
- Crea un gestore di clic,
onSetSleepQuality()
, per tutte le immagini della qualità del sonno da utilizzare.
Utilizza lo stesso pattern di coroutine del codelab precedente:
- Avvia una coroutine in
uiScope
e passa al dispatcher I/O. - Ottieni
tonight
utilizzandosleepNightKey
. - Imposta la qualità del sonno.
- Aggiorna il database.
- Attiva la navigazione.
Tieni presente che l'esempio di codice riportato di seguito esegue tutte le operazioni nel gestore dei clic, anziché estrarre l'operazione del database in un contesto diverso.
fun onSetSleepQuality(quality: Int) {
uiScope.launch {
// IO is a thread pool for running operations that access the disk, such as
// our Room database.
withContext(Dispatchers.IO) {
val tonight = database.get(sleepNightKey) ?: return@withContext
tonight.sleepQuality = quality
database.update(tonight)
}
// Setting this state variable to true will alert the observer and trigger navigation.
_navigateToSleepTracker.value = true
}
}
- Nel pacchetto
sleepquality
, crea o apriSleepQualityViewModelFactory.kt
e aggiungi la classeSleepQualityViewModelFactory
, come mostrato di seguito. Questa classe utilizza una versione dello stesso codice boilerplate che hai visto in precedenza. Controlla il codice prima di procedere.
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")
}
}
Passaggio 2: aggiorna SleepQualityFragment
- Apri
SleepQualityFragment.kt
. - In
onCreateView()
, dopo aver ottenutoapplication
, devi ottenerearguments
fornito con la navigazione. Questi argomenti si trovano inSleepQualityFragmentArgs
. Devi estrarli dal bundle.
val arguments = SleepQualityFragmentArgs.fromBundle(arguments!!)
- Poi, prendi la linea
dataSource
.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
- Crea una factory, passando
dataSource
esleepNightKey
.
val viewModelFactory = SleepQualityViewModelFactory(arguments.sleepNightKey, dataSource)
- Ottieni un riferimento per
ViewModel
.
val sleepQualityViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepQualityViewModel::class.java)
- Aggiungi
ViewModel
all'oggetto di binding. Se visualizzi un errore relativo all'oggetto di binding, ignoralo per il momento.
binding.sleepQualityViewModel = sleepQualityViewModel
- Aggiungi l'osservatore. Quando richiesto, importa
androidx.lifecycle.Observer
.
sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer {
if (it == true) { // Observed state is true.
this.findNavController().navigate(
SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment())
sleepQualityViewModel.doneNavigating()
}
})
Passaggio 3: aggiorna il file di layout ed esegui l'app
- Apri il file di layout
fragment_sleep_quality.xml
. Nel blocco<data>
, aggiungi una variabile perSleepQualityViewModel
.
<data>
<variable
name="sleepQualityViewModel"
type="com.example.android.trackmysleepquality.sleepquality.SleepQualityViewModel" />
</data>
- Per ciascuna delle sei immagini sulla qualità del sonno, aggiungi un gestore di clic come quello riportato di seguito. Abbina la valutazione della qualità all'immagine.
android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(5)}"
- Pulisci e ricompila il progetto. In questo modo dovrebbero essere risolti eventuali errori relativi all'oggetto di binding. In caso contrario, svuota la cache (File > Invalidate Caches / Restart) e ricompila l'app.
Complimenti! Hai appena creato un'app di database Room
completa utilizzando le coroutine.
Ora la tua app funziona alla perfezione. L'utente può toccare Avvia e Interrompi tutte le volte che vuole. Quando l'utente tocca Interrompi, può inserire una qualità del sonno. Quando l'utente tocca Cancella, tutti i dati vengono cancellati in modo invisibile in background. Tuttavia, tutti i pulsanti sono sempre attivi e selezionabili, il che non danneggia l'app, ma consente agli utenti di creare notti di sonno incomplete.
In quest'ultima attività, imparerai a utilizzare le mappe di trasformazione per gestire la visibilità dei pulsanti in modo che gli utenti possano fare solo la scelta giusta. Puoi utilizzare un metodo simile per visualizzare un messaggio amichevole dopo che tutti i dati sono stati cancellati.
Passaggio 1: aggiorna gli stati dei pulsanti
L'idea è di impostare lo stato del pulsante in modo che all'inizio sia attivato solo il pulsante Avvia, ovvero sia cliccabile.
Dopo che l'utente tocca Avvia, il pulsante Interrompi viene attivato e Avvia no. Il pulsante Cancella è attivo solo se nel database sono presenti dati.
- Apri il file di layout
fragment_sleep_tracker.xml
. - Aggiungi la proprietà
android:enabled
a ogni pulsante. La proprietàandroid:enabled
è un valore booleano che indica se il pulsante è abilitato o meno. Un pulsante attivo può essere toccato, mentre un pulsante disattivato non può. Assegna alla proprietà il valore di una variabile di stato che definirai tra poco.
start_button
:
android:enabled="@{sleepTrackerViewModel.startButtonVisible}"
stop_button
:
android:enabled="@{sleepTrackerViewModel.stopButtonVisible}"
clear_button
:
android:enabled="@{sleepTrackerViewModel.clearButtonVisible}"
- Apri
SleepTrackerViewModel
e crea tre variabili corrispondenti. Assegna a ogni variabile una trasformazione che la testa.
- Il pulsante Avvia deve essere attivato quando
tonight
ènull
. - Il pulsante Interrompi deve essere attivato quando
tonight
non ènull
. - Il pulsante Cancella deve essere attivato solo se
nights
e quindi il database contengono notti di sonno.
val startButtonVisible = Transformations.map(tonight) {
it == null
}
val stopButtonVisible = Transformations.map(tonight) {
it != null
}
val clearButtonVisible = Transformations.map(nights) {
it?.isNotEmpty()
}
- Esegui l'app e prova i pulsanti.
Passaggio 2: utilizza uno snackbar per notificare l'utente
Dopo che l'utente ha cancellato il database, mostragli una conferma utilizzando il widget Snackbar
. Uno snackbar fornisce un breve feedback su un'operazione tramite un messaggio nella parte inferiore dello schermo. Una snackbar scompare dopo un timeout, dopo un'interazione dell'utente in un altro punto dello schermo o dopo che l'utente la fa scorrere fuori dallo schermo.
La visualizzazione della snackbar è un'attività della UI e deve avvenire nel fragment. La decisione di mostrare la snackbar viene presa in ViewModel
. Per configurare e attivare una snackbar quando i dati vengono cancellati, puoi utilizzare la stessa tecnica di attivazione della navigazione.
- In
SleepTrackerViewModel
, crea l'evento incapsulato.
private var _showSnackbarEvent = MutableLiveData<Boolean>()
val showSnackBarEvent: LiveData<Boolean>
get() = _showSnackbarEvent
- Poi implementa
doneShowingSnackbar()
.
fun doneShowingSnackbar() {
_showSnackbarEvent.value = false
}
- In
SleepTrackerFragment
, inonCreateView()
, aggiungi un osservatore:
sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer { })
- All'interno del blocco dell'observer, mostra la snackbar e reimposta immediatamente l'evento.
if (it == true) { // Observed state is true.
Snackbar.make(
activity!!.findViewById(android.R.id.content),
getString(R.string.cleared_message),
Snackbar.LENGTH_SHORT // How long to display the message.
).show()
sleepTrackerViewModel.doneShowingSnackbar()
}
- In
SleepTrackerViewModel
, attiva l'evento nel metodoonClear()
. Per farlo, imposta il valore dell'evento sutrue
all'interno del bloccolaunch
:
_showSnackbarEvent.value = true
- Crea ed esegui la tua app.
Progetto Android Studio: TrackMySleepQualityFinal
L'implementazione del monitoraggio della qualità del sonno in questa app è come suonare un brano musicale familiare in una nuova tonalità. Sebbene i dettagli cambino, il pattern di base di ciò che hai fatto nei codelab precedenti di questa lezione rimane invariato. Essere consapevoli di questi pattern rende la programmazione molto più veloce, perché puoi riutilizzare il codice delle app esistenti. Ecco alcuni dei pattern utilizzati finora in questo corso:
- Crea un
ViewModel
e unViewModelFactory
e configura un'origine dati. - Attiva la navigazione. Per separare le problematiche, inserisci il gestore dei clic nel modello di visualizzazione e la navigazione nel fragmento.
- Utilizza l'incapsulamento con
LiveData
per monitorare e rispondere alle modifiche dello stato. - Utilizza le trasformazioni con
LiveData
. - Crea un database singleton.
- Configura le coroutine per le operazioni del database.
Attivazione della navigazione
Definisci i possibili percorsi di navigazione tra i frammenti in un file di navigazione. Esistono diversi modi per attivare la navigazione da un frammento all'altro. tra cui:
- Definisci i gestori di
onClick
per attivare la navigazione verso un fragment di destinazione. - In alternativa, per attivare la navigazione da un frammento all'altro:
- Definisci un valore
LiveData
da registrare se deve verificarsi la navigazione. - Collega un osservatore a questo valore
LiveData
. - Il codice modifica quindi questo valore ogni volta che la navigazione deve essere attivata o è completata.
Impostazione dell'attributo android:enabled
- L'attributo
android:enabled
è definito inTextView
ed ereditato da tutte le sottoclassi, inclusaButton
. - L'attributo
android:enabled
determina se unView
è abilitato o meno. Il significato di "attivato" varia in base alla sottoclasse. Ad esempio, unEditText
non abilitato impedisce all'utente di modificare il testo contenuto, mentre unButton
non abilitato impedisce all'utente di toccare il pulsante. - L'attributo
enabled
non è uguale all'attributovisibility
. - Puoi utilizzare le mappe di trasformazione per impostare il valore dell'attributo
enabled
dei pulsanti in base allo stato di un altro oggetto o variabile.
Altri punti trattati in questo codelab:
- Per attivare le notifiche per l'utente, puoi utilizzare la stessa tecnica che utilizzi per attivare la navigazione.
- Puoi utilizzare un
Snackbar
per inviare una notifica all'utente.
Corso Udacity:
Documentazione per sviluppatori Android:
Questa sezione elenca i possibili compiti a casa per gli studenti che seguono questo codelab nell'ambito di un corso guidato da un insegnante. Spetta all'insegnante:
- Assegna i compiti, se richiesto.
- Comunica agli studenti come inviare i compiti.
- Valuta i compiti a casa.
Gli insegnanti possono utilizzare questi suggerimenti nella misura che ritengono opportuna e sono liberi di assegnare qualsiasi altro compito a casa che ritengono appropriato.
Se stai seguendo questo codelab in autonomia, sentiti libero di utilizzare questi compiti per casa per mettere alla prova le tue conoscenze.
Rispondi a queste domande
Domanda 1
Un modo per consentire all'app di attivare la navigazione da un fragment all'altro è utilizzare un valore LiveData
per indicare se attivare o meno la navigazione.
Quali sono i passaggi per utilizzare un valore LiveData
, chiamato gotoBlueFragment
, per attivare la navigazione dal frammento rosso a quello blu? Seleziona tutte le risposte pertinenti:
- In
ViewModel
, definisci il valoreLiveData
gotoBlueFragment
. - In
RedFragment
, osserva il valoregotoBlueFragment
. Implementa il codiceobserve{}
per passare aBlueFragment
quando opportuno, quindi reimposta il valore digotoBlueFragment
per indicare che la navigazione è completata. - Assicurati che il codice imposti la variabile
gotoBlueFragment
sul valore che attiva la navigazione ogni volta che l'app deve passare daRedFragment
aBlueFragment
. - Assicurati che il codice definisca un gestore
onClick
perView
su cui l'utente fa clic per passare aBlueFragment
, dove il gestoreonClick
osserva il valoregoToBlueFragment
.
Domanda 2
Puoi modificare l'attivazione (cliccabile) o meno di un Button
utilizzando LiveData
. Come faresti in modo che la tua app modifichi il pulsante UpdateNumber
in modo che:
- Il pulsante è attivo se
myNumber
ha un valore maggiore di 5. - Il pulsante non è abilitato se
myNumber
è uguale o inferiore a 5.
Supponiamo che il layout che contiene il pulsante UpdateNumber
includa la variabile <data>
per NumbersViewModel
, come mostrato qui:
<data> <variable name="NumbersViewModel" type="com.example.android.numbersapp.NumbersViewModel" /> </data>
Supponiamo che l'ID del pulsante nel file di layout sia il seguente:
android:id="@+id/update_number_button"
Cos'altro devi fare? Seleziona tutte le opzioni pertinenti.
- Nella classe
NumbersViewModel
, definisci una variabileLiveData
,myNumber
, che rappresenta il numero. Definisci anche una variabile il cui valore viene impostato chiamandoTransform.map()
sulla variabilemyNumber
, che restituisce un valore booleano che indica se il numero è maggiore di 5.
Nello specifico, inViewModel
, aggiungi il seguente codice:
val myNumber: LiveData<Int>
val enableUpdateNumberButton = Transformations.map(myNumber) {
myNumber > 5
}
- Nel layout XML, imposta l'attributo
android:enabled
diupdate_number_button button
suNumberViewModel.enableUpdateNumbersButton
.
android:enabled="@{NumbersViewModel.enableUpdateNumberButton}"
- Nel
Fragment
che utilizza la classeNumbersViewModel
, aggiungi un osservatore all'attributoenabled
del pulsante.
Nello specifico, inFragment
, aggiungi il seguente codice:
// Observer for the enabled attribute
viewModel.enabled.observe(this, Observer<Boolean> { isEnabled ->
myNumber > 5
})
- Nel file di layout, imposta l'attributo
android:enabled
diupdate_number_button button
su"Observable"
.
Inizia la lezione successiva:
Per i link ad altri codelab di questo corso, consulta la pagina di destinazione dei codelab di Android Kotlin Fundamentals.