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.
Schermata del titolo |
Schermata di gioco |
Schermata del punteggio |
Introduzione
In questo codelab, imparerai a conoscere uno dei componenti dell'architettura Android, ViewModel:
- Utilizzi la classe
ViewModelper archiviare e gestire i dati correlati all'interfaccia utente in modo consapevole del ciclo di vita. La classeViewModelconsente ai dati di sopravvivere alle modifiche alla configurazione del dispositivo, come rotazioni dello schermo e modifiche alla disponibilità della tastiera. - Utilizzi la classe
ViewModelFactoryper creare un'istanza e restituire l'oggettoViewModelche sopravvive alle modifiche alla configurazione.
Cosa devi già sapere
- Come creare app per Android di base in Kotlin.
- Come utilizzare il grafico di navigazione per implementare la navigazione nella tua app.
- Come aggiungere codice per spostarsi tra le destinazioni dell'app e trasferire dati tra le destinazioni di navigazione.
- Come funzionano i cicli di vita di attività e frammenti.
- Come aggiungere informazioni di logging a un'app e leggere i log utilizzando Logcat in Android Studio.
Obiettivi didattici
- Come utilizzare l'architettura delle app Android consigliata.
- Come utilizzare le classi
Lifecycle,ViewModeleViewModelFactorynella tua app. - Come conservare i dati dell'interfaccia utente durante le modifiche alla configurazione del dispositivo.
- Che cos'è il pattern di progettazione del metodo di fabbrica e come utilizzarlo.
- Come creare un oggetto
ViewModelutilizzando l'interfacciaViewModelProvider.Factory.
In questo lab proverai a:
- Aggiungi un
ViewModelall'app per salvare i dati dell'app in modo che sopravvivano alle modifiche alla configurazione. - Utilizza
ViewModelFactorye il pattern di progettazione del metodo di fabbrica per creare un'istanza di un oggettoViewModelcon parametri del costruttore.
Nei codelab della lezione 5, sviluppi l'app IndovinaParola, partendo dal codice iniziale. IndovinaParola è un gioco in stile Sarabanda per due giocatori, in cui i giocatori collaborano per ottenere il punteggio più alto possibile.
Il primo giocatore guarda le parole nell'app e le mima a turno, assicurandosi di non mostrarle al secondo giocatore. Il secondo giocatore cerca di 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 recita la parola, facendo attenzione a non dirla.
- Quando il secondo giocatore indovina la parola correttamente, il primo giocatore preme il pulsante Indovinato, 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 Salta, che diminuisce il conteggio di uno e passa alla parola successiva.
- Per terminare la partita, premi il pulsante Termina partita. Questa funzionalità non è presente nel codice iniziale del primo codelab della serie.
In questa attività scaricherai ed eseguirai l'app iniziale ed esaminerai il codice.
Passaggio 1: inizia
- Scarica il codice iniziale di GuessTheWord e apri il progetto in Android Studio.
- Esegui l'app su un dispositivo Android o su un emulatore.
- Tocca i pulsanti. Tieni presente che il pulsante Salta mostra la parola successiva e diminuisce il punteggio di uno, mentre il pulsante Capito mostra la parola successiva e aumenta il punteggio di uno. Il pulsante Termina partita non è implementato, quindi non succede nulla quando lo tocchi.
Passaggio 2: esegui una revisione del codice
- In Android Studio, esplora il codice per capire come funziona l'app.
- Assicurati di esaminare i file descritti di seguito, che sono particolarmente importanti.
MainActivity.kt
Questo file contiene solo codice predefinito generato dal modello.
res/layout/main_activity.xml
Questo file contiene il layout principale dell'app. Il NavHostFragment ospita gli altri fragment mentre l'utente naviga nell'app.
Frammenti di UI
Il codice iniziale ha tre frammenti in tre pacchetti diversi nel pacchetto com.example.android.guesstheword.screens:
title/TitleFragmentper la schermata del titologame/GameFragmentper la schermata di giocoscore/ScoreFragmentper la schermata del punteggio
screens/title/TitleFragment.kt
Il frammento del titolo è la prima schermata visualizzata all'avvio dell'app. Un gestore di clic è impostato sul pulsante Gioca per passare alla schermata di gioco.
screens/game/GameFragment.kt
Questo è il frammento principale, in cui si svolge la maggior parte dell'azione del gioco:
- Le variabili sono definite per la parola e il punteggio attuali.
wordListdefinito all'interno del metodoresetList()è un elenco di parole di esempio da utilizzare nel gioco.- Il metodo
onSkip()è il gestore dei clic per il pulsante Salta. Diminuisce il punteggio di 1, quindi visualizza la parola successiva utilizzando il metodonextWord(). - Il metodo
onCorrect()è il gestore dei clic per il pulsante Ho capito. Questo metodo viene implementato in modo simile al metodoonSkip(). L'unica differenza è che questo metodo aggiunge 1 al punteggio anziché sottrarlo.
screens/score/ScoreFragment.kt
ScoreFragment è l'ultima schermata del gioco e mostra il punteggio finale del giocatore. In questo codelab, aggiungi l'implementazione per visualizzare questa schermata e mostrare il punteggio finale.
res/navigation/main_navigation.xml
Il grafico di navigazione mostra come sono collegati i fragment tramite la navigazione:
- Dal frammento del titolo, l'utente può passare al frammento del gioco.
- Dal frammento della partita, l'utente può passare al frammento del punteggio.
- Dal frammento del punteggio, l'utente può tornare al frammento della partita.
In questa attività, troverai problemi con l'app iniziale GuessTheWord.
- Esegui il codice iniziale e gioca per qualche parola, toccando Salta o Ok dopo ogni parola.
- La schermata di gioco ora mostra una parola e il punteggio attuale. Modifica l'orientamento dello schermo ruotando il dispositivo o l'emulatore. Tieni presente che il punteggio attuale viene perso.
- Continua a giocare con altre parole. Quando viene visualizzata la schermata di gioco con un punteggio, chiudi e riapri l'app. Nota che il gioco riprende dall'inizio, perché lo stato dell'app non viene salvato.
- Gioca per qualche parola, poi tocca il pulsante Termina partita. Noterai che non succede nulla.
Problemi nell'app:
- L'app iniziale non salva e ripristina lo stato dell'app durante le modifiche alla configurazione, ad esempio quando l'orientamento del dispositivo cambia o quando l'app si chiude e si riavvia.
Puoi risolvere questo problema utilizzando il callbackonSaveInstanceState(). Tuttavia, l'utilizzo del metodoonSaveInstanceState()richiede di scrivere codice aggiuntivo per salvare lo stato in un bundle e per implementare la logica per recuperare lo stato. Inoltre, la quantità di dati che possono essere archiviati è minima. - Quando l'utente tocca il pulsante Fine partita, la schermata di gioco non viene reindirizzata alla schermata del punteggio.
Puoi risolvere questi problemi utilizzando i componenti dell'architettura dell'app che imparerai a conoscere in questo codelab.
Architettura dell'app
L'architettura dell'app è un modo per progettare le classi delle tue app e le relazioni tra loro, in modo che il codice sia organizzato, funzioni bene in scenari particolari e sia facile da usare. In questo insieme di quattro codelab, i miglioramenti apportati all'app GuessTheWord seguono le linee guida dell'architettura delle app per Android e utilizzi i componenti dell'architettura Android. L'architettura dell'app per Android è simile al pattern architetturale MVVM (model-view-viewmodel).
L'app GuessTheWord segue il principio di progettazione della separazione delle responsabilità ed è suddivisa in classi, ognuna delle quali si occupa di un problema separato. In questo primo codelab della lezione, le classi con cui lavori sono un controller UI, un ViewModel e un ViewModelFactory.
Controller UI
Un controller UI è una classe basata sulla UI come Activity o Fragment. Un controller UI deve contenere solo la logica che gestisce le interazioni con la UI e il sistema operativo, ad esempio la visualizzazione delle visualizzazioni e l'acquisizione dell'input utente. Non inserire la logica decisionale, ad esempio quella che determina il testo da visualizzare, nel controller UI.
Nel codice iniziale di IndovinaParola, i controller UI sono i tre frammenti: GameFragment, ScoreFragment, e TitleFragment. Seguendo il principio di progettazione della "separazione delle responsabilità", l'GameFragment è responsabile solo del disegno degli elementi di gioco sullo schermo e di sapere quando l'utente tocca i pulsanti, e nient'altro. Quando l'utente tocca un pulsante, queste informazioni vengono trasmesse a GameViewModel.
ViewModel
Un ViewModel contiene i dati da visualizzare in un fragment o in un'attività associata all'ViewModel. Un ViewModel può eseguire calcoli e trasformazioni semplici sui dati per prepararli alla visualizzazione da parte del controller UI. In questa architettura, ViewModel prende le decisioni.GameViewModel contiene dati come il valore del punteggio, l'elenco delle parole e la parola corrente, perché questi sono i dati da visualizzare sullo schermo. Il GameViewModel contiene anche la logica di business per eseguire calcoli semplici per decidere qual è lo stato attuale dei dati.
ViewModelFactory
Un ViewModelFactory crea istanze di oggetti ViewModel, con o senza parametri del costruttore.

Nei codelab successivi, scoprirai altri componenti dell'architettura Android correlati ai controller UI e a ViewModel.
La classe ViewModel è progettata per archiviare e gestire i dati relativi all'interfaccia utente. In questa app, ogni ViewModel è associato a un frammento.
In questa attività, aggiungi il primo ViewModel alla tua app, ovvero il GameViewModel per GameFragment. Scopri anche cosa significa che ViewModel è consapevole del ciclo di vita.
Passaggio 1: aggiungi la classe GameViewModel
- Apri il file
build.gradle(module:app). All'interno del bloccodependencies, aggiungi la dipendenza Gradle perViewModel.
Se utilizzi l'ultima versione della libreria, la compilazione dell'app di soluzione dovrebbe avvenire come previsto. In caso contrario, prova a risolvere il problema o ripristina la versione mostrata di seguito.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'- Nella cartella del pacchetto
screens/game/, crea una nuova classe Kotlin denominataGameViewModel. - Fai in modo che la classe
GameViewModelestenda la classe astrattaViewModel. - Per aiutarti a comprendere meglio in che modo
ViewModelè consapevole del ciclo di vita, aggiungi un bloccoinitcon un'istruzionelog.
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}Passaggio 2: esegui l'override di onCleared() e aggiungi la registrazione
ViewModel viene eliminato quando il fragment associato viene scollegato o quando l'attività è terminata. Poco prima che ViewModel venga distrutto, viene chiamata la richiamata onCleared() per pulire le risorse.
- Nella classe
GameViewModel, esegui l'override del metodoonCleared(). - Aggiungi un'istruzione di log all'interno di
onCleared()per monitorare il ciclo di vita diGameViewModel.
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}Passaggio 3: associa GameViewModel al fragment di gioco
Un ViewModel deve essere associato a un controller UI. Per associarli, crei un riferimento a ViewModel all'interno del controller UI.
In questo passaggio, crei un riferimento di GameViewModel all'interno del controller UI corrispondente, ovvero GameFragment.
- Nella classe
GameFragment, aggiungi un campo di tipoGameViewModelal livello superiore come variabile di classe.
private lateinit var viewModel: GameViewModelPassaggio 4: inizializza il ViewModel
Durante le modifiche alla configurazione, come le rotazioni dello schermo, i controller UI come i fragment vengono ricreati. Tuttavia, ViewModel istanze sopravvivono. Se crei l'istanza ViewModel utilizzando la classe ViewModel, viene creato un nuovo oggetto ogni volta che il fragment viene ricreato. Crea invece l'istanza ViewModel utilizzando un ViewModelProvider.

Come funziona ViewModelProvider:
ViewModelProviderrestituisce unViewModelesistente, se presente, o ne crea uno nuovo se non esiste già.ViewModelProvidercrea un'istanzaViewModelin associazione all'ambito specificato (un'attività o un fragment).ViewModelcreato viene conservato finché l'ambito è attivo. Ad esempio, se l'ambito è un frammento,ViewModelviene mantenuto fino al distacco del frammento.
Inizializza ViewModel utilizzando il metodo ViewModelProviders.of() per creare un ViewModelProvider:
- Nella classe
GameFragment, inizializza la variabileviewModel. Inserisci questo codice all'interno dionCreateView(), dopo la definizione della variabile di binding. Utilizza il metodoViewModelProviders.of()e trasmetti il contestoGameFragmentassociato e la classeGameViewModel. - Sopra l'inizializzazione dell'oggetto
ViewModel, aggiungi un'istruzione di log per registrare la chiamata al metodoViewModelProviders.of().
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)- Esegui l'app. In Android Studio, apri il riquadro Logcat e filtra in base a
Game. Tocca il pulsante Riproduci sul dispositivo o sull'emulatore. Si apre la schermata di gioco.
Come mostrato in Logcat, il metodoonCreateView()diGameFragmentchiama il metodoViewModelProviders.of()per creareGameViewModel. Le istruzioni di logging che hai aggiunto aGameFragmenteGameViewModelvengono visualizzate in Logcat.

- Attiva l'impostazione di rotazione automatica sul dispositivo o sull'emulatore e cambia l'orientamento dello schermo alcune volte.
GameFragmentviene distrutto e ricreato ogni volta, quindiViewModelProviders.of()viene chiamato ogni volta. Tuttavia,GameViewModelviene creato una sola volta e non viene ricreato o eliminato per ogni chiamata.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of
- Esci dal gioco o dal frammento di gioco.
GameFragmentè distrutto. Viene eliminato anche l'GameViewModelassociato e viene chiamato il callbackonCleared().
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel destroyed!
ViewModel sopravvive alle modifiche alla configurazione, quindi è un buon posto per i dati che devono sopravvivere a queste modifiche:
- Inserisci i dati da visualizzare sullo schermo e il codice per elaborarli in
ViewModel. ViewModelnon deve mai contenere riferimenti a frammenti, attività o visualizzazioni, perché attività, frammenti e visualizzazioni non sopravvivono alle modifiche alla configurazione.

A titolo di confronto, ecco come vengono gestiti i dati dell'interfaccia utente GameFragment nell'app iniziale prima di aggiungere ViewModel e dopo aver aggiunto ViewModel:
- Prima di aggiungere
ViewModel:
quando l'app subisce una modifica della configurazione, ad esempio una rotazione dello schermo, il frammento di gioco viene eliminato e ricreato. I dati vengono persi. - Dopo aver aggiunto
ViewModele spostato i dati dell'interfaccia utente del frammento di gioco inViewModel:
tutti i dati necessari per la visualizzazione del frammento sono oraViewModel. Quando l'app subisce una modifica alla configurazione,ViewModelrimane e i dati vengono conservati.

In questa attività, sposterai i dati dell'UI dell'app nella classe GameViewModel, insieme ai metodi per elaborare i dati. In questo modo, i dati vengono conservati durante le modifiche alla configurazione.
Passaggio 1: sposta i campi di dati e l'elaborazione dei dati nel ViewModel
Sposta i seguenti campi e metodi di dati da GameFragment a GameViewModel:
- Sposta i campi di dati
word,scoreewordList. Assicurati chewordescorenon sianoprivate.
Non spostare la variabile di binding,GameFragmentBinding, perché contiene riferimenti alle visualizzazioni. Questa variabile viene utilizzata per gonfiare il layout, configurare i listener di clic e visualizzare i dati sullo schermo, responsabilità del fragment. - Sposta i metodi
resetList()enextWord(). Questi metodi decidono quale parola mostrare sullo schermo. - Dall'interno del metodo
onCreateView(), sposta le chiamate di metodo aresetList()enextWord()nel bloccoinitdiGameViewModel.
Questi metodi devono trovarsi nel bloccoinit, perché devi reimpostare l'elenco di parole quando viene creatoViewModel, non ogni volta che viene creato il fragmento. Puoi eliminare l'istruzione di log nel bloccoinitdiGameFragment.
I gestori dei clic onSkip() e onCorrect() in GameFragment contengono il codice per l'elaborazione dei dati e l'aggiornamento della UI. Il codice per aggiornare la UI deve rimanere nel fragment, ma il codice per l'elaborazione dei dati deve essere spostato in ViewModel.
Per ora, inserisci i metodi identici in entrambi i punti:
- Copia i metodi
onSkip()eonCorrect()daGameFragmentaGameViewModel. - In
GameViewModel, assicurati che i metodionSkip()eonCorrect()non sianoprivate, perché farai riferimento a questi metodi dal fragment.
Di seguito è riportato il codice per la classe GameViewModel dopo il refactoring:
class GameViewModel : ViewModel() {
// The current word
var word = ""
// The current score
var score = 0
// The list of words - the front of the list is the next word to guess
private lateinit var wordList: MutableList<String>
/**
* Resets the list of words and randomizes the order
*/
private fun resetList() {
wordList = mutableListOf(
"queen",
"hospital",
"basketball",
"cat",
"change",
"snail",
"soup",
"calendar",
"sad",
"desk",
"guitar",
"home",
"railway",
"zebra",
"jelly",
"car",
"crow",
"trade",
"bag",
"roll",
"bubble"
)
wordList.shuffle()
}
init {
resetList()
nextWord()
Log.i("GameViewModel", "GameViewModel created!")
}
/**
* Moves to the next word in the list
*/
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
word = wordList.removeAt(0)
}
updateWordText()
updateScoreText()
}
/** Methods for buttons presses **/
fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
}Di seguito è riportato il codice per la classe GameFragment, dopo il refactoring:
/**
* Fragment where the game is played
*/
class GameFragment : Fragment() {
private lateinit var binding: GameFragmentBinding
private lateinit var viewModel: GameViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.game_fragment,
container,
false
)
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
updateScoreText()
updateWordText()
return binding.root
}
/** Methods for button click handlers **/
private fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
private fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
/** Methods for updating the UI **/
private fun updateWordText() {
binding.wordText.text = word
}
private fun updateScoreText() {
binding.scoreText.text = score.toString()
}
}Passaggio 2: aggiorna i riferimenti ai gestori di clic e ai campi di dati in GameFragment
- In
GameFragment, aggiorna i metodionSkip()eonCorrect(). Rimuovi il codice per aggiornare il punteggio e chiama invece i metodionSkip()eonCorrect()corrispondenti suviewModel. - Poiché hai spostato il metodo
nextWord()inViewModel, il frammento di gioco non può più accedervi.
InGameFragment, nei metodionSkip()eonCorrect(), sostituisci la chiamata anextWord()conupdateScoreText()eupdateWordText(). Questi metodi mostrano i dati sullo schermo.
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}- In
GameFragment, aggiorna le variabiliscoreewordin modo da utilizzare le variabiliGameViewModel, perché ora si trovano inGameViewModel.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}- In
GameViewModel, all'interno del metodonextWord(), rimuovi le chiamate ai metodiupdateWordText()eupdateScoreText(). Questi metodi vengono ora chiamati daGameFragment. - Crea l'app e assicurati che non siano presenti errori. Se si verificano errori, pulisci e ricompila il progetto.
- Esegui l'app e gioca con alcune parole. Mentre ti trovi nella schermata di gioco, ruota il dispositivo. Tieni presente che il punteggio attuale e la parola attuale vengono mantenuti dopo la modifica dell'orientamento.
Ottimo lavoro. Ora tutti i dati dell'app vengono archiviati in un ViewModel, quindi vengono conservati durante le modifiche alla configurazione.
In questa attività, implementa il listener di clic per il pulsante Termina partita.
- In
GameFragment, aggiungi un metodo chiamatoonEndGame(). Il metodoonEndGame()viene chiamato quando l'utente tocca il pulsante Termina partita.
private fun onEndGame() {
}- In
GameFragment, all'interno del metodoonCreateView(), individua il codice che imposta i listener di clic per i pulsanti Ho capito e Salta. Appena sotto queste due righe, imposta un listener dei clic per il pulsante Fine partita. Utilizza la variabile di bindingbinding. All'interno del listener di clic, chiama il metodoonEndGame().
binding.endGameButton.setOnClickListener { onEndGame() }- In
GameFragment, aggiungi un metodo chiamatogameFinished()per navigare nell'app fino alla schermata del punteggio. Trasferisci il punteggio come argomento utilizzando Safe Args.
/**
* Called when the game is finished
*/
private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
val action = GameFragmentDirections.actionGameToScore()
action.score = viewModel.score
NavHostFragment.findNavController(this).navigate(action)
}- Nel metodo
onEndGame(), chiama il metodogameFinished().
private fun onEndGame() {
gameFinished()
}- Esegui l'app, gioca e scorri alcune parole. Tocca il pulsante Termina partita . Nota che l'app passa alla schermata del punteggio, ma il punteggio finale non viene visualizzato. Lo correggerai nell'attività successiva.
|
|
Quando l'utente termina la partita, ScoreFragment non mostra il punteggio. Vuoi che un ViewModel contenga il punteggio da visualizzare sul ScoreFragment. Passerai il valore del punteggio durante l'inizializzazione di ViewModel utilizzando il pattern del metodo di fabbrica.
Il pattern del metodo di fabbrica è un pattern di progettazione creazionale che utilizza metodi di fabbrica per creare oggetti. Un metodo di fabbrica è un metodo che restituisce un'istanza della stessa classe.
In questa attività, creerai un ViewModel con un costruttore parametrizzato per il frammento del punteggio e un metodo di fabbrica per creare un'istanza di ViewModel.
- Nel pacchetto
score, crea una nuova classe Kotlin denominataScoreViewModel. Questo corso sarà ilViewModelper il frammento del punteggio. - Estendi la classe
ScoreViewModeldaViewModel.Aggiungi un parametro del costruttore per il punteggio finale. Aggiungi un bloccoinitcon un'istruzione di log. - Nella classe
ScoreViewModel, aggiungi una variabile denominatascoreper salvare il punteggio finale.
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}- Nel pacchetto
score, crea un'altra classe Kotlin chiamataScoreViewModelFactory. Questa classe sarà responsabile dell'istanza dell'oggettoScoreViewModel. - Estendi il corso
ScoreViewModelFactorya partire daViewModelProvider.Factory. Aggiungi un parametro del costruttore per il punteggio finale.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}- In
ScoreViewModelFactory, Android Studio mostra un errore relativo a un membro astratto non implementato. Per risolvere l'errore, esegui l'override del metodocreate(). Nel metodocreate(), restituisci l'oggettoScoreViewModelappena creato.
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
return ScoreViewModel(finalScore) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}- In
ScoreFragment, crea variabili di classe perScoreViewModeleScoreViewModelFactory.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory- In
ScoreFragment, all'interno dionCreateView(), dopo aver inizializzato la variabilebinding, inizializzaviewModelFactory. UtilizzaScoreViewModelFactory. Passa il punteggio finale dal bundle di argomenti come parametro del costruttore aScoreViewModelFactory().
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)- In
onCreateView(, dopo aver inizializzatoviewModelFactory, inizializza l'oggettoviewModel. Chiama il metodoViewModelProviders.of(), passa il contesto del frammento di punteggio associato eviewModelFactory. In questo modo viene creato l'oggettoScoreViewModelutilizzando il metodo factory definito nella classeviewModelFactory.
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)- Nel metodo
onCreateView(), dopo aver inizializzatoviewModel, imposta il testo della visualizzazionescoreTextsul punteggio finale definito inScoreViewModel.
binding.scoreText.text = viewModel.score.toString()- Esegui l'app e gioca. Scorri alcune o tutte le parole e tocca Termina partita. Nota che il frammento del punteggio ora mostra il punteggio finale.

- (Facoltativo) Controlla i log di
ScoreViewModelin Logcat filtrando in base aScoreViewModel. Il valore del punteggio deve essere visualizzato.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
In questa attività hai implementato ScoreFragment per utilizzare ViewModel. Hai anche imparato a creare un costruttore parametrizzato per un ViewModel utilizzando l'interfaccia ViewModelFactory.
Complimenti! Hai modificato l'architettura della tua app per utilizzare uno dei componenti dell'architettura Android, ViewModel. Hai risolto il problema del ciclo di vita dell'app e ora i dati del gioco sopravvivono alle modifiche alla configurazione. Hai anche imparato a creare un costruttore parametrizzato per creare un ViewModel utilizzando l'interfaccia ViewModelFactory.
Progetto Android Studio: GuessTheWord
- Le linee guida per l'architettura delle app per Android consigliano di separare le classi con responsabilità diverse.
- Un controller UI è una classe basata sulla UI come
ActivityoFragment. I controller dell'interfaccia utente devono contenere solo la logica che gestisce le interazioni con l'interfaccia utente e il sistema operativo; non devono contenere dati da visualizzare nell'interfaccia utente. Inserisci questi dati in unViewModel. - La classe
ViewModelarchivia e gestisce i dati relativi all'interfaccia utente. La classeViewModelconsente ai dati di sopravvivere alle modifiche alla configurazione, ad esempio le rotazioni dello schermo. ViewModelè uno dei componenti dell'architettura Android consigliati.ViewModelProvider.Factoryè un'interfaccia che puoi utilizzare per creare un oggettoViewModel.
La tabella seguente confronta i controller UI con le istanze ViewModel che contengono i dati:
Controller UI | ViewModel |
Un esempio di controller UI è | Un esempio di |
Non contiene dati da visualizzare nella UI. | Contiene i dati che il controller UI visualizza nella UI. |
Contiene il codice per la visualizzazione dei dati e il codice degli eventi utente, ad esempio i listener di clic. | Contiene il codice per l'elaborazione dei dati. |
Distrutti e ricreati a ogni modifica della configurazione. | Viene eliminato solo quando il controller UI associato viene rimosso definitivamente. Per un'attività, quando l'attività termina o, per un fragment, quando il fragment viene scollegato. |
Contiene le visualizzazioni. | Non deve mai contenere riferimenti ad attività, frammenti o visualizzazioni, perché non sopravvivono alle modifiche alla configurazione, a differenza di |
Contiene un riferimento al | Non contiene alcun riferimento al controller UI associato. |
Corso Udacity:
Documentazione per sviluppatori Android:
- Panoramica di ViewModel
- Gestione dei cicli di vita con componenti sensibili al ciclo di vita
- Guida all'architettura delle app
ViewModelProviderViewModelProvider.Factory
Altro:
- Pattern architetturale MVVM (Model-View-ViewModel).
- Principio di progettazione separazione delle competenze (SoC)
- Pattern del metodo di fabbrica
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
Per evitare di perdere dati durante una modifica della configurazione del dispositivo, in quale classe devi salvare i dati dell'app?
ViewModelLiveDataFragmentActivity
Domanda 2
Un ViewModel non deve mai contenere riferimenti a frammenti, attività o visualizzazioni. Vero o falso?
- Vero
- Falso
Domanda 3
Quando viene distrutto un ViewModel?
- Quando il controller UI associato viene eliminato e ricreato durante una modifica dell'orientamento del dispositivo.
- In un cambio di orientamento.
- Al termine del controller UI associato (se si tratta di un'attività) o quando viene scollegato (se si tratta di un frammento).
- Quando l'utente preme il pulsante Indietro.
Domanda 4
A cosa serve l'interfaccia ViewModelFactory?
- Creazione dell'istanza di un oggetto
ViewModel. - Conservazione dei dati durante le modifiche all'orientamento.
- Aggiornamento dei dati visualizzati sullo schermo.
- Ricevere notifiche quando i dati dell'app vengono modificati.
Inizia la lezione successiva:
Per i link ad altri codelab di questo corso, consulta la pagina di destinazione dei codelab di Android Kotlin Fundamentals.




