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
ViewModel
per archiviare e gestire i dati correlati all'interfaccia utente in modo consapevole del ciclo di vita. La classeViewModel
consente ai dati di sopravvivere alle modifiche alla configurazione del dispositivo, come rotazioni dello schermo e modifiche alla disponibilità della tastiera. - Utilizzi la classe
ViewModelFactory
per creare un'istanza e restituire l'oggettoViewModel
che 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
,ViewModel
eViewModelFactory
nella 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
ViewModel
utilizzando l'interfacciaViewModelProvider.Factory
.
In questo lab proverai a:
- Aggiungi un
ViewModel
all'app per salvare i dati dell'app in modo che sopravvivano alle modifiche alla configurazione. - Utilizza
ViewModelFactory
e il pattern di progettazione del metodo di fabbrica per creare un'istanza di un oggettoViewModel
con 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/TitleFragment
per la schermata del titologame/GameFragment
per la schermata di giocoscore/ScoreFragment
per 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.
wordList
definito 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
GameViewModel
estenda la classe astrattaViewModel
. - Per aiutarti a comprendere meglio in che modo
ViewModel
è consapevole del ciclo di vita, aggiungi un bloccoinit
con 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 tipoGameViewModel
al livello superiore come variabile di classe.
private lateinit var viewModel: GameViewModel
Passaggio 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
:
ViewModelProvider
restituisce unViewModel
esistente, se presente, o ne crea uno nuovo se non esiste già.ViewModelProvider
crea un'istanzaViewModel
in associazione all'ambito specificato (un'attività o un fragment).ViewModel
creato viene conservato finché l'ambito è attivo. Ad esempio, se l'ambito è un frammento,ViewModel
viene 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 contestoGameFragment
associato 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()
diGameFragment
chiama il metodoViewModelProviders.of()
per creareGameViewModel
. Le istruzioni di logging che hai aggiunto aGameFragment
eGameViewModel
vengono visualizzate in Logcat.
- Attiva l'impostazione di rotazione automatica sul dispositivo o sull'emulatore e cambia l'orientamento dello schermo alcune volte.
GameFragment
viene distrutto e ricreato ogni volta, quindiViewModelProviders.of()
viene chiamato ogni volta. Tuttavia,GameViewModel
viene 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'GameViewModel
associato 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
. ViewModel
non 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
ViewModel
e 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,ViewModel
rimane 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
,score
ewordList
. Assicurati cheword
escore
non 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 bloccoinit
diGameViewModel
.
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 bloccoinit
diGameFragment
.
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()
daGameFragment
aGameViewModel
. - 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 variabiliscore
eword
in 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à ilViewModel
per il frammento del punteggio. - Estendi la classe
ScoreViewModel
daViewModel.
Aggiungi un parametro del costruttore per il punteggio finale. Aggiungi un bloccoinit
con un'istruzione di log. - Nella classe
ScoreViewModel
, aggiungi una variabile denominatascore
per 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
ScoreViewModelFactory
a 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'oggettoScoreViewModel
appena 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 perScoreViewModel
eScoreViewModelFactory
.
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'oggettoScoreViewModel
utilizzando 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 visualizzazionescoreText
sul 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
ScoreViewModel
in 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
Activity
oFragment
. 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
ViewModel
archivia e gestisce i dati relativi all'interfaccia utente. La classeViewModel
consente 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
ViewModelProvider
ViewModelProvider.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?
ViewModel
LiveData
Fragment
Activity
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.