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
safeArgsper passare i dati tra i fragment. - Visualizza modelli, fabbriche di modelli, trasformazioni e
LiveDatae 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
LiveDataper 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
LiveDataper attivare la visualizzazione di una snackbar. - Usa
LiveDataper 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
SleepTrackerFragmentaSleepQualityFragmente viceversa daSleepQualityFragmentaSleepTrackerFragment.
- Ispeziona il codice di navigation.xml. In particolare, cerca
<argument>denominatosleepNightKey.
Quando l'utente passa daSleepTrackerFragmentaSleepQualityFragment,, l'app trasmette unsleepNightKeyaSleepQualityFragmentper 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 aSleepQualityFragmentper raccogliere una valutazione della qualità. - In
SleepTrackerViewModel, crea unLiveDatache cambia quando vuoi che l'app passi aSleepQualityFragment. Utilizza l'incapsulamento per esporre solo una versione ottenibile diLiveDataaViewModel.
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 _navigateToSleepQualityalla 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 = oldNightSleepTrackerFragmentdeve osservare _navigateToSleepQualityin 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
SleepQualityViewModelche accetta unsleepNightKeye un database come argomenti. Come hai fatto perSleepTrackerViewModel, devi inseriredatabasedi fabbrica. Devi anche passaresleepNightKeydalla navigazione.
class SleepQualityViewModel(
private val sleepNightKey: Long = 0L,
val database: SleepDatabaseDao) : ViewModel() {
}- All'interno della classe
SleepQualityViewModel, definisciJobeuiScopeed 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
SleepTrackerFragmentutilizzando lo stesso pattern di prima, dichiara_navigateToSleepTracker. ImplementanavigateToSleepTrackeredoneNavigating().
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
uiScopee passa al dispatcher I/O. - Ottieni
tonightutilizzandosleepNightKey. - 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.kte 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 ottenereargumentsfornito 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
dataSourceesleepNightKey.
val viewModelFactory = SleepQualityViewModelFactory(arguments.sleepNightKey, dataSource)- Ottieni un riferimento per
ViewModel.
val sleepQualityViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepQualityViewModel::class.java)- Aggiungi
ViewModelall'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:enableda 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
SleepTrackerViewModele 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
tonightnon ènull. - Il pulsante Cancella deve essere attivato solo se
nightse 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 sutrueall'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
ViewModele unViewModelFactorye 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
LiveDataper 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
onClickper attivare la navigazione verso un fragment di destinazione. - In alternativa, per attivare la navigazione da un frammento all'altro:
- Definisci un valore
LiveDatada 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 inTextViewed ereditato da tutte le sottoclassi, inclusaButton. - L'attributo
android:enableddetermina se unViewè abilitato o meno. Il significato di "attivato" varia in base alla sottoclasse. Ad esempio, unEditTextnon abilitato impedisce all'utente di modificare il testo contenuto, mentre unButtonnon abilitato impedisce all'utente di toccare il pulsante. - L'attributo
enablednon è uguale all'attributovisibility. - Puoi utilizzare le mappe di trasformazione per impostare il valore dell'attributo
enableddei 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
Snackbarper 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 valoreLiveDatagotoBlueFragment. - In
RedFragment, osserva il valoregotoBlueFragment. Implementa il codiceobserve{}per passare aBlueFragmentquando opportuno, quindi reimposta il valore digotoBlueFragmentper indicare che la navigazione è completata. - Assicurati che il codice imposti la variabile
gotoBlueFragmentsul valore che attiva la navigazione ogni volta che l'app deve passare daRedFragmentaBlueFragment. - Assicurati che il codice definisca un gestore
onClickperViewsu cui l'utente fa clic per passare aBlueFragment, dove il gestoreonClickosserva 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
myNumberha 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:enableddiupdate_number_button buttonsuNumberViewModel.enableUpdateNumbersButton.
android:enabled="@{NumbersViewModel.enableUpdateNumberButton}"- Nel
Fragmentche utilizza la classeNumbersViewModel, aggiungi un osservatore all'attributoenableddel 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:enableddiupdate_number_button buttonsu"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.