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.
Schermata del titolo |
Schermata di gioco |
Schermata punteggio |
Introduzione
In questo codelab, scopri uno dei componenti dell'architettura Android, ViewModel
:
- Utilizza la classe
ViewModel
per archiviare e gestire i dati correlati all'interfaccia utente in modo consapevole. La classeViewModel
consente ai dati di sopravvivere a modifiche della configurazione del dispositivo, ad esempio rotazioni schermo e modifiche alla disponibilità della tastiera. - Utilizza la classe
ViewModelFactory
per creare un'istanza e restituire l'oggettoViewModel
che sopravvive alle modifiche alla configurazione.
Informazioni importanti
- Come creare app Android di base in Kotlin.
- Come utilizzare il grafico di navigazione per implementare la navigazione nella tua app.
- Come aggiungere codice per navigare tra le destinazioni della tua app e passare 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 usando Logcat in Android Studio.
Obiettivi didattici
- Come utilizzare l'architettura delle app Android consigliata.
- Come utilizzare i corsi
Lifecycle
,ViewModel
eViewModelFactory
nell'app. - Come conservare i dati dell'interfaccia utente attraverso le modifiche alla configurazione del dispositivo.
- Cos'è il modello 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 elemento
ViewModel
all'app per salvare i dati di quest'ultima, 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 costruttore.
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 questa attività dovrai scaricare ed eseguire l'app iniziale ed esaminare il codice.
Passaggio 1: inizia
- Scarica il codice di avvio 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 Ignora mostra la parola successiva e riduce il punteggio di uno, mentre il pulsante OK mostra la parola successiva e aumenta il punteggio di uno. Il pulsante Termina gioco non è implementato, quindi non accade nulla quando lo tocchi.
Passaggio 2: esegui una procedura dettagliata del codice
- In Android Studio, esplora il codice per farti un'idea di come funziona l'app.
- Assicurati di esaminare i file descritti di seguito, che sono particolarmente importanti.
MainActivity.kt
Questo file contiene solo un codice predefinito generato dal modello.
res/layout/main_activity.xml
Questo file contiene il layout principale dell'app. L'NavHostFragment
ospita gli altri frammenti durante la navigazione dell'utente all'interno dell'app.
Frammenti dell'interfaccia utente
Il codice di avvio contiene tre frammenti in tre pacchetti diversi nel pacchetto com.example.android.guesstheword.screens
:
title/TitleFragment
per la schermata del titologame/GameFragment
per la schermata del giocoscore/ScoreFragment
per la schermata del punteggio
screen/title/TitleFragment.kt
Il frammento del titolo è la prima schermata che viene visualizzata all'avvio dell'app. Un gestore di clic è impostato sul pulsante Gioca per andare alla schermata del gioco.
screens/game/GameFragment.kt
Si tratta del frammento principale in cui avviene la maggior parte del gioco:
- Sono definite le variabili per la parola corrente e il punteggio corrente.
- Il metodo
wordList
definito nel metodoresetList()
è un elenco di parole di esempio da utilizzare nel gioco. - Il metodo
onSkip()
è il gestore dei clic per il pulsante Ignora. Diminuisce il punteggio di 1, quindi mostra la parola successiva utilizzando il metodonextWord()
. - Il metodo
onCorrect()
è il gestore dei clic per il pulsante OK. Questo metodo viene implementato in modo simile al metodoonSkip()
. L'unica differenza è che questo metodo aggiunge 1 al punteggio anziché sottrarre.
screens/score/ScoreFragment.kt
ScoreFragment
è la schermata finale 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 la modalità di connessione dei frammenti tramite navigazione:
- Dal frammento del titolo, l'utente può passare al frammento del gioco.
- Dal frammento di gioco, l'utente può passare al frammento di punteggio.
- Dal frammento del punteggio, l'utente può tornare al frammento del gioco.
In questa attività, troverai problemi con l'app iniziale di GuessTheWord.
- Esegui il codice iniziale e gioca con qualche parola, toccando Salta o OK dopo ogni parola.
- Ora sullo schermo del gioco vengono visualizzati una parola e il punteggio corrente. Modifica l'orientamento dello schermo facendo ruotare il dispositivo o l'emulatore. Nota che il punteggio attuale è perso.
- Esegui il gioco con qualche altra parola. Quando la schermata del gioco viene visualizzata con un punteggio, chiudi e riapri l'app. Tieni presente che il gioco si riavvia dall'inizio perché lo stato dell'app non è stato salvato.
- Gioca con poche parole, poi tocca il pulsante Termina gioco. Tieni presente che non accade 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 spegne e si riavvia.
Puoi risolvere il problema utilizzando il callbackonSaveInstanceState()
. Tuttavia, per usare il metodoonSaveInstanceState()
è necessario scrivere un codice aggiuntivo per salvare lo stato in un pacchetto e implementare la logica per recuperarlo. Inoltre, la quantità di dati che è possibile archiviare è minima. - La schermata del gioco non viene visualizzata quando l'utente tocca il pulsante Termina gioco.
Puoi risolvere questi problemi utilizzando i componenti dell'architettura dell'app illustrati in questo codelab.
Architettura dell'app
L'architettura delle app è un modo per progettare le classi e le relazioni tra le applicazioni, in modo che il codice sia organizzato, funzioni bene in particolari scenari e sia facile da utilizzare. In questo insieme di quattro codelab, i miglioramenti che apporti all'app GuessTheWord seguono le linee guida dell'architettura dell'app Android e usi i componenti dell'architettura Android. L'architettura delle app Android è simile al pattern architettonico MVVM (model-view-viewmodel).
L'app GuessTheWord segue il principio di progettazione delle separazioni dei problemi ed è suddivisa in classi, ognuna delle quali affronta una questione separata. In questo primo codelab della lezione, i corsi con cui lavori sono un controller dell'interfaccia utente, un ViewModel
e un ViewModelFactory
.
Controller dell'interfaccia utente
Un controller UI è una classe basata sull'interfaccia utente come Activity
o Fragment
. Un controller dell'interfaccia utente deve contenere solo la logica che gestisce le interazioni con l'interfaccia utente e il sistema operativo, come la visualizzazione delle visualizzazioni e l'acquisizione degli input degli utenti. Non inserire logica decisionale, ad esempio la logica che determina il testo da visualizzare, nel controller dell'interfaccia utente.
Nel codice di avvio di GuessTheWord, i controller dell'interfaccia utente sono i tre frammenti: GameFragment
, ScoreFragment,
e TitleFragment
. In linea con la"separazione dei problemi"secondo il principio di progettazione, GameFragment
è responsabile solo di disegnare elementi del 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 al GameViewModel
.
Visualizzazione modello
Un elemento ViewModel
contiene i dati da visualizzare in un frammento o in un'attività associata a ViewModel
. Un ViewModel
può eseguire semplici calcoli e trasformazioni sui dati per preparare quelli da mostrare con il controller dell'interfaccia utente. In questa architettura, ViewModel
esegue il processo decisionale.
La 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. La GameViewModel
contiene anche la logica di business per eseguire semplici calcoli per decidere lo stato corrente dei dati.
VisualizzaFabbrica modelli
Un elemento ViewModelFactory
crea un'istanza di ViewModel
oggetti, con o senza parametri di 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 i primi ViewModel
all'app, ovvero GameViewModel
per GameFragment
. Scoprirai anche cosa significa che ViewModel
è sensibile al 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, l'app della soluzione dovrebbe essere compilata come previsto. Se il problema persiste, 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 aiutare gli utenti a comprendere meglio come
ViewModel
è sensibile al ciclo di vita, aggiungi un bloccoinit
con un'istruzionelog
.
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}
Passaggio 2: sostituisci onCleared() e aggiungi il logging
Il ViewModel
viene eliminato quando il frammento associato viene scollegato o quando l'attività è terminata. Subito prima dell'eliminazione di ViewModel
, viene richiamato il callback onCleared()
per la pulizia delle risorse.
- Nel corso
GameViewModel
, sostituisci il 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 frammento di gioco
È necessario associare un ViewModel
a un controller dell'interfaccia utente. Per associare le due parti, crea un riferimento all'elemento ViewModel
all'interno del controller dell'interfaccia utente.
In questo passaggio, creerai un riferimento per GameViewModel
nel controller dell'interfaccia utente corrispondente, che è GameFragment
.
- Nel corso
GameFragment
, aggiungi come campo una variabile di tipoGameViewModel
al livello superiore.
private lateinit var viewModel: GameViewModel
Passaggio 4: inizializza il modello di modelli
Durante le modifiche alla configurazione, come la rotazione dello schermo, vengono ricreati i controller dell'interfaccia utente, ad esempio i frammenti. Tuttavia, esistono ancora ViewModel
istanze. Se crei l'istanza ViewModel
utilizzando la classe ViewModel
, viene creato un nuovo oggetto ogni volta che viene ricreato il frammento. Crea invece l'istanza ViewModel
utilizzando un elemento ViewModelProvider
.
Come funziona ViewModelProvider
:
ViewModelProvider
restituisce unViewModel
esistente se ne esiste uno o ne crea uno nuovo se non esiste già.ViewModelProvider
crea un'istanzaViewModel
in associazione con l'ambito specificato (un'attività o un frammento).- Il
ViewModel
creato viene conservato finché l'ambito è attivo. Ad esempio, se l'ambito è un frammento, il valore diViewModel
viene conservato fino a quando il frammento non viene scollegato.
Inizializza la ViewModel
, utilizzando il metodo ViewModelProviders.of()
per creare un ViewModelProvider
:
- Nella classe
GameFragment
, inizializza la variabileviewModel
. Inserisci questo codice inonCreateView()
, dopo la definizione della variabile di associazione. Utilizzare il metodoViewModelProviders.of()
e passare nel contestoGameFragment
associato e alla classeGameViewModel
. - Sopra l'inizializzazione dell'oggetto
ViewModel
, aggiungi un'istruzione di log per registrare la chiamata del 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
Game
. Tocca il pulsante Play sul tuo dispositivo o emulatore. Si apre la schermata del gioco.
Come mostrato in Logcat, il metodoonCreateView()
delGameFragment
chiama il metodoViewModelProviders.of()
per creare ilGameViewModel
. Le istruzioni di logging che hai aggiunto aGameFragment
eGameViewModel
vengono visualizzate in Logcat.
- Attiva l'impostazione di rotazione automatica sul tuo dispositivo o emulatore e modifica alcune volte l'orientamento dello schermo.
GameFragment
viene eliminato e ricreato ogni volta, quindiViewModelProviders.of()
viene richiamato ogni volta. Tuttavia, l'elementoGameViewModel
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 oppure esci dal frammento di gioco.
GameFragment
è stato eliminato. Anche ilGameViewModel
associato viene eliminato e viene richiamato 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
è in grado di sopravvivere ai cambiamenti di configurazione, quindi è un buon posto per i dati che devono sopravvivere ai cambiamenti di configurazione:
- Inserisci i dati da visualizzare sullo schermo e il codice per elaborare i dati nella
ViewModel
. - L'elemento
ViewModel
non deve mai contenere riferimenti a frammenti, attività o viste, in quanto attività, frammenti e viste non sopravvivono alle modifiche alla configurazione.
Per un confronto, ecco come vengono gestiti i dati dell'interfaccia utente di GameFragment
nell'app iniziale prima di aggiungere ViewModel
e dopo aver aggiunto ViewModel
:
- Prima di aggiungere
ViewModel
:
quando l'app esegue una modifica alla configurazione, ad esempio una rotazione dello schermo, il frammento di gioco viene eliminato e ricreato. I dati andranno persi. - Dopo aver aggiunto
ViewModel
e spostato i dati dell'interfaccia utente del frammento di gioco nellaViewModel
:
Tutti i dati che il frammento deve visualizzare sono oraViewModel
. Quando l'app passa attraverso una modifica alla configurazione,ViewModel
continua a essere valido e i dati vengono conservati.
In questa attività, sposti i dati dell'interfaccia utente 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 in Viewmodel
Sposta i seguenti campi di dati e metodi da GameFragment
a GameViewModel
:
- Sposta i campi di dati
word
,score
ewordList
. Assicurati cheword
escore
non sianoprivate
.
Non spostare la variabile di associazione,GameFragmentBinding
, perché contiene riferimenti alle viste. Questa variabile viene utilizzata per gonfiare il layout, impostare i listener di clic e visualizzare i dati sullo schermo, ovvero le responsabilità del frammento. - Sposta i metodi
resetList()
enextWord()
. Questi metodi decidono quale parola mostrare sullo schermo. - Dall'interno del metodo
onCreateView()
, sposta le chiamate metodo aresetList()
enextWord()
al bloccoinit
diGameViewModel
.
Questi metodi devono essere inseriti nel bloccoinit
, perché dovresti reimpostare l'elenco di parole quando viene creato l'elementoViewModel
, non ogni volta che viene creato il frammento. Puoi eliminare l'istruzione di log nel bloccoinit
diGameFragment
.
I gestori dei clic di onSkip()
e onCorrect()
in GameFragment
contengono codice per l'elaborazione dei dati e l'aggiornamento dell'interfaccia utente. Il codice per aggiornare l'interfaccia utente deve rimanere nel frammento, 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
inGameViewModel
. - In
GameViewModel
, assicurati che i metodionSkip()
eonCorrect()
non sianoprivate
, poiché farai riferimento a questi metodi dal frammento.
Ecco 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!")
}
}
Ecco 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 dei 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 che utilizzino le variabiliGameViewModel
, perché si trovano ora 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 ci siano errori. Se sono presenti errori, pulisci e ricrea il progetto.
- Esegui l'app e utilizza il gioco con alcune parole. Quando sei nella schermata del gioco, ruota il dispositivo. Tieni presente che il punteggio corrente e la parola corrente vengono conservati dopo la modifica dell'orientamento.
Benissimo! Ora tutti i dati dell'app vengono archiviati in un elemento ViewModel
, quindi vengono conservati durante le modifiche alla configurazione.
In questa attività, implementi il listener di clic per il pulsante Termina gioco.
- In
GameFragment
, aggiungi un metodo chiamatoonEndGame()
. Il metodoonEndGame()
viene richiamato quando l'utente tocca il pulsante Termina gioco.
private fun onEndGame() {
}
- In
GameFragment
, all'interno del metodoonCreateView()
, individua il codice che imposta i listener di clic per i pulsanti OK e Ignora. Appena sotto queste due righe, imposta un listener di clic per il pulsante Termina gioco. Utilizza la variabile di associazionebinding
. All'interno del listener di clic, chiama il metodoonEndGame()
.
binding.endGameButton.setOnClickListener { onEndGame() }
- In
GameFragment
, aggiungi un metodo chiamatogameFinished()
per andare all'app nella schermata del punteggio. Trasmetti il punteggio come argomento, utilizzando argomenti sicuri.
/**
* 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, usa il gioco e scorri alcune parole. Tocca il pulsante Termina gioco. Nota che l'app accede alla schermata del punteggio, ma non viene visualizzato il punteggio finale. Devi risolvere il problema nella prossima attività.
Quando l'utente termina il gioco, ScoreFragment
non mostra il punteggio. Vuoi che un valore ViewModel
venga mantenuto affinché il punteggio venga mostrato da ScoreFragment
. Passerai il valore del punteggio durante l'inizializzazione di ViewModel
utilizzando il pattern del metodo di fabbrica.
Il pattern di fabbrica è un motivo di progettazione creativa 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
.
- Sotto il pacchetto
score
, crea una nuova classe Kotlin denominataScoreViewModel
. Questa classe sarà laViewModel
per il frammento del punteggio. - Estendi la classe
ScoreViewModel
daViewModel.
Aggiungi un parametro costruttore per il punteggio finale. Aggiungi un bloccoinit
con un'istruzione di log. - Nel corso
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")
}
}
- Sotto il pacchetto
score
, crea un'altra classe Kotlin denominataScoreViewModelFactory
. Questa classe sarà responsabile dell'istanza dell'oggettoScoreViewModel
. - Estendi la classe
ScoreViewModelFactory
daViewModelProvider.Factory
. Aggiungi un parametro 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 il problema, sostituisci il metodocreate()
. Nel metodocreate()
, restituisci l'oggettoScoreViewModel
appena costruito.
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 classe perScoreViewModel
eScoreViewModelFactory
.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory
- In
ScoreFragment
, all'interno dionCreateView()
, dopo aver inizializzato la variabilebinding
, inizializza laviewModelFactory
. Usa l'ScoreViewModelFactory
. Passa il punteggio finale dall'argomento gruppo come parametro costruttore allaScoreViewModelFactory()
.
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
. L'oggettoScoreViewModel
verrà creato utilizzando il metodo della fabbrica 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 nella metricaScoreViewModel
.
binding.scoreText.text = viewModel.score.toString()
- Esegui l'app e gioca. Scorri alcune o tutte le parole e tocca Termina gioco. Nota che ora il frammento del punteggio mostra il punteggio finale.
- (Facoltativo) Controlla i log
ScoreViewModel
in Logcat filtrando perScoreViewModel
. Dovrebbe essere visualizzato il valore del punteggio.
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 con parametri per un elemento ViewModel
utilizzando l'interfaccia di ViewModelFactory
.
Complimenti! Hai modificato l'architettura della tua app in modo da utilizzare uno dei componenti dell'architettura Android, ViewModel
. Hai risolto il problema del ciclo di vita dell'app e ora i dati del gioco sono ancora validi. Hai anche imparato a creare un costruttore con parametri per la creazione di un ViewModel
, utilizzando l'interfaccia di ViewModelFactory
.
Progetto Android Studio: GuessTheWord
- Le linee guida relative all'architettura delle app per Android consiglia di separare i corsi che hanno responsabilità diverse.
- Un controller UI è una classe basata su 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. Inserisci i dati in un elementoViewModel
. - Il corso
ViewModel
archivia e gestisce i dati relativi all'interfaccia utente. La classeViewModel
consente ai dati di sopravvivere a modifiche della configurazione come le rotazioni schermo. ViewModel
è uno dei componenti dell'architettura Android consigliati.ViewModelProvider.Factory
è un'interfaccia che puoi utilizzare per creare un oggettoViewModel
.
La tabella riportata di seguito confronta i controller UI con le istanze ViewModel
che contengono i relativi dati:
Controller UI | Visualizza modello |
Un controller di interfaccia utente è un esempio di | Un |
Non contiene dati da visualizzare nell'interfaccia utente. | Contiene dati che il controller dell'interfaccia utente mostra nell'interfaccia. |
Contiene codice per visualizzare i dati e codice evento utente, come i listener di clic. | Contiene codice per l'elaborazione dei dati. |
Eliminato e ricreato durante ogni modifica alla configurazione. | Eliminata solo quando il controller dell'interfaccia utente associato scompare definitivamente, per un'attività, quando termina l'attività o per un frammento, quando il frammento viene scollegato. |
Contiene visualizzazioni. | Non devono mai contenere riferimenti ad attività, frammenti o viste, in quanto non sopravvivono alle modifiche alla configurazione, al contrario di |
Contiene un riferimento all'elemento | Non contiene alcun riferimento al controller dell'interfaccia utente associato. |
Corso Udacity:
Documentazione per gli 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:
- Modello architetturale MVVM (model-view-viewmodel).
- Principio di progettazione della separazione dei dubbi (SoC)
- Pattern di fabbrica
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
Per evitare di perdere dati durante una modifica alla configurazione del dispositivo, devi salvare i dati dell'app in quale classe?
ViewModel
LiveData
Fragment
Activity
Domanda 2
Un elemento ViewModel
non deve mai contenere riferimenti a frammenti, attività o viste. Vero o falso?
- Vero
- falso.
Domanda 3
Quando viene eliminata una ViewModel
?
- Quando il controller dell'interfaccia utente associato viene eliminato e ricreato durante una modifica dell'orientamento del dispositivo.
- Con una variazione di orientamento.
- Una volta terminato il controller dell'interfaccia utente associato (se è un'attività) o scollegato (se è un frammento).
- Quando l'utente preme il pulsante Indietro.
Domanda 4
A cosa serve l'interfaccia di ViewModelFactory
?
- Creazione dell'istanza di un oggetto
ViewModel
. - Conservazione dei dati durante i cambi di orientamento.
- Aggiornamento dei dati visualizzati sullo schermo.
- Ricezione di notifiche quando i dati dell'app vengono modificati.
Inizia la lezione successiva:
Per i link ad altri codelab in questo corso, consulta la pagina di destinazione di Android Kotlin Fundamentals.