Android Kotlin Fundamentals 05.3: associazione di dati con ViewModel e LiveData

Questo codelab fa parte del corso Android Kotlin Fundamentals. Otterrai il massimo valore da questo corso se lavori in sequenza nei codelab. Tutti i codelab del corso sono elencati nella pagina di destinazione di Android Kotlin Fundamentals.

Introduzione

Nei codelab precedenti di questa lezione hai migliorato il codice per l'app GuessTheWord. Ora l'app utilizza oggetti ViewModel, quindi i dati dell'app si adeguano alle modifiche della configurazione del dispositivo, come le rotazioni dello schermo e le modifiche relative alla disponibilità della tastiera. Hai anche aggiunto LiveData osservabile, in modo che le visualizzazioni vengano notificate automaticamente quando vengono modificati i dati osservati.

In questo codelab, continui a lavorare con l'app GuessTheWord. associare le visualizzazioni ai corsi ViewModel nell'app in modo che le visualizzazioni nel tuo layout comunichino direttamente con gli oggetti ViewModel. Finora, nell'app le visualizzazioni hanno comunicato indirettamente con ViewModel, tramite i frammenti dell'app. Dopo aver integrato l'associazione di dati con gli oggetti ViewModel, non hai più bisogno dei gestori dei clic nei frammenti dell'app, quindi li rimuovi.

Puoi anche modificare l'app GuessTheWord per utilizzare LiveData come origine di associazione di dati per informare l'interfaccia utente delle modifiche ai dati, senza utilizzare i metodi Osservatore LiveData.

Informazioni importanti

  • Come creare app Android di base in Kotlin.
  • Come funzionano i cicli di vita di attività e frammenti.
  • Come utilizzare gli oggetti ViewModel nell'app.
  • Come archiviare dati utilizzando LiveData in un ViewModel.
  • Come aggiungere metodi di osservazione per osservare le modifiche nei dati di LiveData.

Obiettivi didattici

  • Come utilizzare gli elementi della libreria dei dati.
  • Come integrare ViewModel con l'associazione di dati.
  • Come integrare LiveData con l'associazione di dati.
  • Come utilizzare le associazioni dei listener per sostituire i listener di clic in un frammento.
  • Come aggiungere la formattazione di stringa alle espressioni di associazione di dati.

In questo lab proverai a:

  • Le visualizzazioni nei layout di GuessTheWord comunicano indirettamente con oggetti ViewModel, utilizzando i controller (frammenti) dell'interfaccia utente per inoltrare le informazioni. In questo codelab, puoi associare le viste dell'app agli oggetti ViewModel in modo che le viste comunichino direttamente con gli oggetti ViewModel.
  • Se cambi l'app, utilizzerai LiveData come origine di associazione di dati. Dopo questa modifica, gli oggetti LiveData inviano una notifica all'interfaccia utente riguardo alle modifiche dei dati e i metodi degli osservatori LiveData non sono più necessari.

Nei codelab della Lezione 5, sviluppi l'app GuessTheWord, iniziando con il codice di avvio. GuessTheWord è un gioco in stile charade per giocatori, in cui i giocatori collaborano per raggiungere il punteggio più alto possibile.

Il primo giocatore controlla le parole nell'app e le reagisce a turno, assicurandoti di non mostrare la parola al secondo giocatore. Il secondo giocatore prova a indovinare la parola.

Per giocare, il primo giocatore apre l'app sul dispositivo e vede una parola, ad esempio "chitarra", come mostrato nello screenshot di seguito.

Il primo giocatore pronuncia la parola, facendo attenzione a non pronunciare la parola stessa.

  • Quando il secondo giocatore indovina la parola correttamente, il primo pulsante preme il pulsante OK, che aumenta il conteggio di uno e mostra la parola successiva.
  • Se il secondo giocatore non riesce a indovinare la parola, il primo giocatore preme il pulsante Ignora: in questo modo il numero viene ridotto di uno e passa alla parola successiva.
  • Per terminare il gioco, premi il pulsante Termina gioco. Questa funzionalità non è presente nel codice di avvio del primo codelab della serie.

In questo codelab, migliorerai l'app GuessTheWord integrando l'associazione di dati con LiveData negli oggetti ViewModel. Questa operazione automatizza la comunicazione tra le viste nel layout e gli oggetti ViewModel e consente di semplificare il codice utilizzando LiveData.

Schermata del titolo

Schermata di gioco

Schermata punteggio

In questa attività, troverai ed eseguirai il tuo codice di avvio per questo codelab. Puoi usare come codice di avvio l'app GuessTheWord che hai creato nel codelab precedente oppure puoi scaricare un'app iniziale.

  1. (Facoltativo) Se non utilizzi il codice del codelab precedente, scarica il codice di avvio per questo codelab. Decomprimi il codice e apri il progetto in Android Studio.
  2. Esegui l'app e gioca.
  3. Tieni presente che il pulsante OK mostra la parola successiva e aumenta il punteggio di uno, mentre il pulsante Salta mostra la parola successiva e riduce il punteggio di uno. Il pulsante Termina gioco consente di terminare il gioco.
  4. Scorri tutte le parole e nota che l'app passa automaticamente alla schermata del punteggio.

In un codelab precedente, hai utilizzato l'associazione di dati come metodo sicuro per il tipo per accedere alle viste nell'app GuessTheWord. Tuttavia, il vero vantaggio dell'associazione di dati è l'associazione del nome suggerito: associazione di dati direttamente agli oggetti di visualizzazione nell'app.

Architettura attuale dell'app

Nell'app, le viste sono definite nel layout XML e i relativi dati vengono conservati in oggetti ViewModel. Tra ogni vista e il corrispondente ViewModel è presente un controller dell'interfaccia utente, che funge da inoltro tra di loro.

Ad esempio:

  • Il pulsante OK è definito come una visualizzazione Button nel file di layout game_fragment.xml.
  • Quando l'utente tocca il pulsante OK, un listener di clic nel frammento GameFragment chiama il listener di clic corrispondente in GameViewModel.
  • Il punteggio viene aggiornato in GameViewModel.

La vista Button e la comunicazione GameViewModel non comunicano direttamente, perciò è necessario che l'ascoltatore dei clic sia in GameFragment.

ViewModel passato nell'associazione di dati

Sarebbe più semplice se le viste nel layout comunicassero direttamente con i dati negli oggetti ViewModel, senza fare affidamento sui controller dell'interfaccia utente come intermediari.

Gli oggetti ViewModel contengono tutti i dati dell'interfaccia utente nell'app GuessTheWord. Passando gli oggetti ViewModel nell'associazione di dati, puoi automatizzare alcune delle comunicazioni tra le viste e gli oggetti ViewModel.

In questa attività assocerai le classi GameViewModel e ScoreViewModel ai layout XML corrispondenti. Hai anche configurato le associazioni di listener per gestire gli eventi di clic.

Passaggio 1: aggiungi l'associazione di dati per GameViewmodel

In questo passaggio dovrai associare GameViewModel al file di layout corrispondente, game_fragment.xml.

  1. Nel file game_fragment.xml, aggiungi una variabile di associazione di dati del tipo GameViewModel. Se sul tuo dispositivo sono presenti errori in Android Studio, pulisci e ricrea il progetto.
<layout ...>

   <data>

       <variable
           name="gameViewModel"
           type="com.example.android.guesstheword.screens.game.GameViewModel" />
   </data>
  
   <androidx.constraintlayout...
  1. Nel file GameFragment, passa GameViewModel nell'associazione di dati.

    A tal fine, assegna viewModel alla variabile binding.gameViewModel dichiarata nel passaggio precedente. Inserisci questo codice all'interno di onCreateView(), dopo l'inizializzazione di viewModel. Se sul tuo dispositivo sono presenti errori in Android Studio, pulisci e ricrea il progetto.
// Set the viewmodel for databinding - this allows the bound layout access 
// to all the data in the ViewModel
binding.gameViewModel = viewModel

Passaggio 2: utilizza le associazioni degli ascoltatori per la gestione degli eventi

Le associazioni di listener sono espressioni di binding che vengono eseguite quando vengono attivati eventi come onClick(), onZoomIn() o onZoomOut(). Le associazioni di listener sono scritte come espressioni lambda.

L'associazione di dati crea un listener e imposta il listener nella vista. Quando si verifica l'evento, il listener valuta l'espressione lambda. Le associazioni degli ascoltatori funzionano con il plug-in Android per Gradle versione 2.0 o successive. Per saperne di più, consulta Layout ed espressioni di associazione.

In questo passaggio devi sostituire i listener di clic in GameFragment con le associazioni di listener nel file game_fragment.xml.

  1. In game_fragment.xml, aggiungi l'attributo onClick alla skip_button. Definisci un'espressione di associazione e chiama il metodo onSkip() in GameViewModel. Questa espressione di associazione è chiamata associazione di ascolto.
<Button
   android:id="@+id/skip_button"
   ...
   android:onClick="@{() -> gameViewModel.onSkip()}"
   ... />
  1. Allo stesso modo, associa l'evento di clic di correct_button al metodo onCorrect() in GameViewModel.
<Button
   android:id="@+id/correct_button"
   ...
   android:onClick="@{() -> gameViewModel.onCorrect()}"
   ... />
  1. Associa l'evento di clic di end_game_button al metodo onGameFinish() in GameViewModel.
<Button
   android:id="@+id/end_game_button"
   ...
   android:onClick="@{() -> gameViewModel.onGameFinish()}"
   ... />
  1. In GameFragment, rimuovi le istruzioni che impostano i listener di clic e rimuovi le funzioni che i listener di clic chiamano. Non ti serve più.

Codice da rimuovere:

binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
binding.endGameButton.setOnClickListener { onEndGame() }

/** Methods for buttons presses **/
private fun onSkip() {
   viewModel.onSkip()
}
private fun onCorrect() {
   viewModel.onCorrect()
}
private fun onEndGame() {
   gameFinished()
}

Passaggio 3: aggiungi l'associazione di dati per il ScoreViewmodel

In questo passaggio dovrai associare ScoreViewModel al file di layout corrispondente, score_fragment.xml.

  1. Nel file score_fragment.xml, aggiungi una variabile di associazione di tipo ScoreViewModel. Questo passaggio è simile a quello che hai effettuato per GameViewModel.
<layout ...>
   <data>
       <variable
           name="scoreViewModel"
           type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
   </data>
   <androidx.constraintlayout.widget.ConstraintLayout
  1. In score_fragment.xml, aggiungi l'attributo onClick alla play_again_button. Definisci un'associazione di listener e chiama il metodo onPlayAgain() in ScoreViewModel.
<Button
   android:id="@+id/play_again_button"
   ...
   android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
   ... />
  1. In ScoreFragment, all'interno di onCreateView(), inizializza viewModel. Quindi inizializza la variabile di associazione binding.scoreViewModel.
viewModel = ...
binding.scoreViewModel = viewModel
  1. In ScoreFragment, rimuovi il codice che imposta il listener di clic per playAgainButton. Se Android Studio mostra un errore, pulisci e ricrea il progetto.

Codice da rimuovere:

binding.playAgainButton.setOnClickListener {  viewModel.onPlayAgain()  }
  1. Esegui l'app. L'app dovrebbe funzionare come prima, ma ora le visualizzazioni dei pulsanti comunicano direttamente con gli oggetti ViewModel. Le visualizzazioni non comunicano più tramite i gestori dei clic sui pulsanti in ScoreFragment.

Risoluzione dei problemi dovuti a messaggi di errore relativi all'associazione di dati

Quando un'app utilizza l'associazione di dati, la procedura di compilazione genera classi intermedie che vengono utilizzate per l'associazione di dati. Un'app può avere errori che Android Studio non rileva finché non provi a compilare l'app, quindi non puoi visualizzare avvisi o codice rosso mentre scrivi il codice. Tuttavia, al momento della compilazione vengono visualizzati errori crittografici provenienti dalle classi intermedie generate.

Se viene visualizzato un messaggio di errore criptico:

  1. Osserva attentamente il messaggio nel riquadro Build di Android Studio. Se vedi una sede che termina con databinding, si è verificato un errore con l'associazione di dati.
  2. Nel file XML di layout, controlla se ci sono errori negli attributi onClick che utilizzano l'associazione di dati. Cerca la funzione chiamata dall'espressione lambda e assicurati che esista.
  3. Nella sezione <data> del file XML, controlla l'ortografia della variabile di associazione di dati.

Ad esempio, prendi nota dell'errore ortografico nel nome della funzione onCorrect() nel seguente valore dell'attributo:

android:onClick="@{() -> gameViewModel.onCorrectx()}"

Tieni presente anche l'errore di ortografia di gameViewModel nella sezione <data> del file XML:

<data>
   <variable
       name="gameViewModelx"
       type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>

Android Studio non rileva errori di questo tipo finché non compili l'app, quindi il compilatore mostra un messaggio di errore simile al seguente:

error: cannot find symbol
import com.example.android.guesstheword.databinding.GameFragmentBindingImpl"

symbol:   class GameFragmentBindingImpl
location: package com.example.android.guesstheword.databinding

L'associazione di dati funziona bene con LiveData che vengono utilizzati con gli oggetti ViewModel. Ora che hai aggiunto un'associazione di dati agli oggetti ViewModel, puoi incorporare LiveData.

In questa attività, modificherai l'app GuessTheWord in modo che utilizzi LiveData come origine di associazione di dati per informare l'interfaccia utente delle modifiche ai dati, senza usare i metodi osservatori LiveData.

Passaggio 1: aggiungi la parola LiveData al file game_fragment.xml

In questo passaggio devi associare la visualizzazione testuale corrente direttamente all'oggetto LiveData in ViewModel.

  1. In game_fragment.xml, aggiungi l'attributo android:text alla visualizzazione di testo word_text.

Impostalo sull'oggetto LiveData, word da GameViewModel, utilizzando la variabile di associazione gameViewModel.

<TextView
   android:id="@+id/word_text"
   ...
   android:text="@{gameViewModel.word}"
   ... />

Tieni presente che non devi necessariamente utilizzare word.value. In alternativa, puoi utilizzare l'oggetto LiveData effettivo. L'oggetto LiveData mostra il valore corrente dell'elemento word. Se il valore di word è null, l'oggetto LiveData mostra una stringa vuota.

  1. In GameFragment, in onCreateView(), dopo l'inizializzazione di gameViewModel, imposta l'attività corrente come proprietario del ciclo di vita della variabile binding. Definisce l'ambito dell'oggetto LiveData sopra riportato, consentendo all'oggetto di aggiornare automaticamente le viste nel layout, game_fragment.xml.
binding.gameViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this
  1. In GameFragment, rimuovi l'osservatore per word di LiveData.

Codice da rimuovere:

/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
   binding.wordText.text = newWord
})
  1. Esegui l'app e gioca. Ora la parola corrente viene aggiornata senza un metodo osservatore nel controller dell'interfaccia utente.

Passaggio 2: aggiungi il punteggio LiveData al file score_fragment.xml

In questo passaggio devi associare LiveData score alla visualizzazione del testo del punteggio nel frammento del punteggio.

  1. In score_fragment.xml, aggiungi l'attributo android:text alla visualizzazione testo del punteggio. Assegna scoreViewModel.score all'attributo text. Poiché score è un numero intero, convertilo in una stringa utilizzando String.valueOf().
<TextView
   android:id="@+id/score_text"
   ...
   android:text="@{String.valueOf(scoreViewModel.score)}"
   ... />
  1. In ScoreFragment, dopo aver inizializzato l'elemento scoreViewModel, imposta l'attività corrente come proprietario del ciclo di vita della variabile binding.
binding.scoreViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this
  1. In ScoreFragment, rimuovi l'osservatore per l'oggetto score.

Codice da rimuovere:

// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. Esegui l'app e gioca. Tieni presente che il punteggio nel frammento del punteggio viene visualizzato correttamente, senza un osservatore nel frammento del punteggio.

Passaggio 3: aggiungi la formattazione delle stringhe con l'associazione di dati

Nel layout puoi aggiungere la formattazione di stringa insieme all'associazione di dati. In questa attività, formatti la parola corrente per aggiungere le virgolette. Puoi anche formattare la stringa di punteggio in modo che faccia riferimento al Punteggio corrente, come mostrato nell'immagine seguente.

  1. In string.xml, aggiungi le seguenti stringhe che utilizzerai per formattare le visualizzazioni di testo di word e score. %s e %d sono i segnaposto per la parola corrente e il punteggio corrente.
<string name="quote_format">\"%s\"</string>
<string name="score_format">Current Score: %d</string>
  1. In game_fragment.xml, aggiorna l'attributo text della visualizzazione di testo word_text in modo da utilizzare la risorsa stringa quote_format. Supera gameViewModel.word. In questo modo la parola corrente viene trasferita come argomento alla stringa di formattazione.
<TextView
   android:id="@+id/word_text"
   ...
   android:text="@{@string/quote_format(gameViewModel.word)}"
   ... />
  1. Formatta la visualizzazione di testo score in modo simile alla word_text. In game_fragment.xml, aggiungi l'attributo text alla visualizzazione di testo score_text. Utilizza la risorsa stringa score_format, che include un argomento numerico, rappresentato dal segnaposto %d. Passa l'oggetto LiveData, score, come argomento a questa stringa di formattazione.
<TextView
   android:id="@+id/score_text"
   ...
   android:text="@{@string/score_format(gameViewModel.score)}"
   ... />
  1. Nella classe GameFragment, all'interno del metodo onCreateView(), rimuovi il codice osservatore score.

Codice da rimuovere:

viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. Pulisci, ricostruisci ed esegui la tua app, quindi gioca. Tieni presente che la parola corrente e il punteggio sono formattati nella schermata del gioco.

Complimenti! Hai integrato LiveData e ViewModel con l'associazione di dati nella tua app. In questo modo le viste nel layout possono comunicare direttamente con ViewModel, senza utilizzare i gestori dei clic nel frammento. Hai anche utilizzato gli oggetti LiveData come origine di associazione di dati per avvisare automaticamente l'interfaccia utente delle modifiche apportate ai dati, senza i metodi osservatore LiveData.

Progetto Android Studio: GuessTheWord

  • La libreria di associazione di dati funziona perfettamente con componenti di architettura Android come ViewModel e LiveData.
  • I layout nella tua app possono essere associati ai dati nei componenti dell'architettura. In questo modo puoi già gestire il ciclo di vita del controller UI e ricevere notifiche sulle modifiche ai dati.

Associazione dati ViewModel

  • Puoi associare una ViewModel a un layout utilizzando un'associazione di dati.
  • Gli oggetti ViewModel contengono i dati dell'interfaccia utente. Passando l'oggetto ViewModel nell'associazione di dati, puoi automatizzare alcune delle comunicazioni tra le viste e gli oggetti ViewModel.

Come associare un ViewModel a un layout:

  • Nel file di layout, aggiungi una variabile di associazione di dati del tipo ViewModel.
   <data>

       <variable
           name="gameViewModel"
           type="com.example.android.guesstheword.screens.game.GameViewModel" />
   </data>
  • Nel file GameFragment, trasmetti l'elemento GameViewModel nell'associazione di dati.
binding.gameViewModel = viewModel

Associazioni listener

  • Le associazioni di listener sono espressioni di binding nel layout che vengono eseguite quando vengono attivati eventi di clic come onClick().
  • Le associazioni di listener sono scritte come espressioni lambda.
  • L'utilizzo di associazioni di listener comporta la sostituzione dei listener di clic nei controller dell'interfaccia utente con le associazioni di listener nel file di layout.
  • L'associazione di dati crea un listener e imposta il listener nella vista.
 android:onClick="@{() -> gameViewModel.onSkip()}"

Aggiunta di LiveData all'associazione di dati

  • Gli oggetti LiveData possono essere utilizzati come origine di associazione di dati per informare automaticamente l'interfaccia utente delle modifiche apportate ai dati.
  • Puoi associare la vista direttamente all'oggetto LiveData in ViewModel. Quando LiveData in ViewModel cambia, le visualizzazioni nel layout possono essere aggiornate automaticamente, senza i metodi osservatore nei controller dell'interfaccia utente.
android:text="@{gameViewModel.word}"
  • Per far funzionare l'associazione di dati LiveData, imposta l'attività corrente (il controller dell'interfaccia utente) come proprietario del ciclo di vita della variabile binding nel controller dell'interfaccia utente.
binding.lifecycleOwner = this

Formattazione delle stringhe con associazione di dati

  • Utilizzando l'associazione di dati, puoi formattare una risorsa stringa con segnaposto come %s per le stringhe e %d per i numeri interi.
  • Per aggiornare l'attributo text della vista, trasmetti l'oggetto LiveData come argomento alla stringa di formattazione.
 android:text="@{@string/quote_format(gameViewModel.word)}"

Corso Udacity:

Documentazione per gli sviluppatori Android:

In questa sezione sono elencati i possibili compiti per gli studenti che lavorano attraverso questo codelab nell'ambito di un corso tenuto da un insegnante. Spetta all'insegnante fare quanto segue:

  • Assegna i compiti, se necessario.
  • Comunica agli studenti come inviare compiti.
  • Valuta i compiti.

Gli insegnanti possono utilizzare i suggerimenti solo quanto e come vogliono e dovrebbero assegnare i compiti che ritengono appropriati.

Se stai lavorando da solo a questo codelab, puoi utilizzare questi compiti per mettere alla prova le tue conoscenze.

Rispondi a queste domande

Domanda 1

Quale delle seguenti affermazioni sulle associazioni degli ascoltatori non è vera?

  • Le associazioni di listener sono espressioni di associazione eseguite quando si verifica un evento.
  • Le associazioni degli ascoltatori funzionano con tutte le versioni del plug-in Android per Gradle.
  • Le associazioni di listener sono scritte come espressioni lambda.
  • Le associazioni di ascoltatori sono simili ai riferimenti ai metodi, ma consentono di eseguire espressioni di associazione di dati arbitrari.

Domanda 2

Supponiamo che la tua app includa questa risorsa di stringa:
<string name="generic_name">Hello %s</string>

Qual è la sintassi corretta per formattare la stringa utilizzando l'espressione di associazione di dati?

  • android:text= "@{@string/generic_name(user.name)}"
  • android:text= "@{string/generic_name(user.name)}"
  • android:text= "@{@generic_name(user.name)}"
  • android:text= "@{@string/generic_name,user.name}"

Domanda 3

Quando viene valutata ed eseguita un'espressione di associazione degli ascoltatori?

  • Quando vengono modificati i dati conservati tramite LiveData
  • Quando un'attività viene ricreata da una modifica della configurazione
  • Quando si verifica un evento come onClick()
  • Quando l'attività passa in background

Inizia la lezione successiva: 5.4: Trasformazioni di LiveData

Per i link ad altri codelab in questo corso, consulta la pagina di destinazione di Android Kotlin Fundamentals.