Panoramica di ViewModel Parte di Android Jetpack.
La classe ViewModel
è un holder di stato a livello di logica di business o schermata. Espone lo stato all'interfaccia utente e incapsula la logica di business correlata.
Il suo vantaggio principale è che memorizza nella cache lo stato e lo mantiene
durante le modifiche alla configurazione. Ciò significa che la tua UI non deve recuperare nuovamente i dati
quando passi da un'attività all'altra o in seguito a modifiche alla configurazione, ad esempio
quando ruoti lo schermo.
Per saperne di più sui titolari dello stato, consulta le indicazioni sui titolari dello stato. Allo stesso modo, per ulteriori informazioni sul livello UI in generale, consulta le indicazioni relative al livello UI.
Vantaggi di ViewModel
L'alternativa a un ViewModel è una classe semplice che contiene i dati visualizzati nella UI. Questo può diventare un problema quando si passa da un'attività all'altra o alle destinazioni di navigazione. In questo modo, i dati vengono eliminati se non li memorizzi utilizzando il meccanismo di salvataggio dello stato dell'istanza. ViewModel fornisce un'API comoda per la persistenza dei dati che risolve questo problema.
I vantaggi principali della classe ViewModel sono essenzialmente due:
- Consente di rendere persistente lo stato dell'interfaccia utente.
- Fornisce l'accesso alla logica di business.
Persistenza
ViewModel consente la persistenza sia dello stato mantenuto da ViewModel sia delle operazioni attivate da ViewModel. Questa memorizzazione nella cache significa che non devi recuperare di nuovo i dati tramite modifiche alla configurazione comuni, ad esempio la rotazione dello schermo.
Ambito
Quando crei un'istanza di un ViewModel, gli passi un oggetto che implementa l'interfaccia
ViewModelStoreOwner
. Può trattarsi di una destinazione di navigazione,
di un grafico di navigazione, di un'attività, di un frammento o di qualsiasi altro tipo che implementi l'interfaccia. Il ViewModel viene quindi definito nell'ambito del ciclo di vita del
ViewModelStoreOwner
. Rimane in memoria finché il suo ViewModelStoreOwner
non scompare definitivamente.
Una serie di classi sono sottoclassi dirette o indirette dell'interfaccia
ViewModelStoreOwner
. Le sottoclassi dirette sono
ComponentActivity
, Fragment
e NavBackStackEntry
.
Per un elenco completo delle sottoclassi indirette, consulta il
riferimento ViewModelStoreOwner
.
Quando il fragment o l'attività a cui è associato il ViewModel viene eliminato, il lavoro asincrono continua nel ViewModel associato. Questa è la chiave per la persistenza.
Per maggiori informazioni, consulta la sezione Ciclo di vita di ViewModel di seguito.
SavedStateHandle
SavedStateHandle consente di rendere persistenti i dati non solo durante le modifiche alla configurazione, ma anche durante la ricreazione del processo. ovvero ti consente di mantenere intatto lo stato dell'interfaccia utente anche quando l'utente chiude l'app e la apre in un secondo momento.
Accesso alla logica di business
Anche se la stragrande maggioranza della logica di business è presente nel livello dati, anche il livello UI può contenere logica di business. Questo può accadere quando si combinano dati di più repository per creare lo stato dell'interfaccia utente della schermata o quando un particolare tipo di dati non richiede un livello dati.
ViewModel è il posto giusto per gestire la logica di business nel livello UI. Il ViewModel è anche responsabile della gestione degli eventi e della loro delega ad altri livelli della gerarchia quando è necessario applicare la logica di business per modificare i dati dell'applicazione.
Jetpack Compose
Quando utilizzi Jetpack Compose, ViewModel è il mezzo principale per esporre lo stato dell'interfaccia utente dello schermo ai tuoi composable. In un'app ibrida, le attività e i fragment ospitano semplicemente le funzioni componibili. Si tratta di un cambiamento rispetto agli approcci passati, in cui non era così semplice e intuitivo creare parti riutilizzabili dell'interfaccia utente con attività e frammenti, il che le rendeva molto più attive come controller dell'interfaccia utente.
L'aspetto più importante da tenere presente quando utilizzi ViewModel con Compose è
che non puoi definire l'ambito di un ViewModel per un composable. Questo perché un composable
non è un ViewModelStoreOwner
. Due istanze dello stesso elemento componibile nella
composizione o due elementi componibili diversi che accedono allo stesso tipo di ViewModel
nello stesso ViewModelStoreOwner
riceverebbero la stessa istanza di
ViewModel, il che spesso non è il comportamento previsto.
Per usufruire dei vantaggi di ViewModel in Compose, ospita ogni schermata in un Fragment o in un'Activity oppure utilizza Compose Navigation e usa i ViewModel nelle funzioni composable il più vicino possibile alla destinazione di navigazione. perché puoi definire l'ambito di un ViewModel per destinazioni di navigazione, grafici di navigazione, attività e fragment.
Per ulteriori informazioni, consulta la guida sul sollevamento dello stato per Jetpack Compose.
Implementare un ViewModel
Di seguito è riportato un esempio di implementazione di un ViewModel per una schermata che consente all'utente di tirare i dadi.
Kotlin
data class DiceUiState(
val firstDieValue: Int? = null,
val secondDieValue: Int? = null,
val numberOfRolls: Int = 0,
)
class DiceRollViewModel : ViewModel() {
// Expose screen UI state
private val _uiState = MutableStateFlow(DiceUiState())
val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()
// Handle business logic
fun rollDice() {
_uiState.update { currentState ->
currentState.copy(
firstDieValue = Random.nextInt(from = 1, until = 7),
secondDieValue = Random.nextInt(from = 1, until = 7),
numberOfRolls = currentState.numberOfRolls + 1,
)
}
}
}
Java
public class DiceUiState {
private final Integer firstDieValue;
private final Integer secondDieValue;
private final int numberOfRolls;
// ...
}
public class DiceRollViewModel extends ViewModel {
private final MutableLiveData<DiceUiState> uiState =
new MutableLiveData(new DiceUiState(null, null, 0));
public LiveData<DiceUiState> getUiState() {
return uiState;
}
public void rollDice() {
Random random = new Random();
uiState.setValue(
new DiceUiState(
random.nextInt(7) + 1,
random.nextInt(7) + 1,
uiState.getValue().getNumberOfRolls() + 1
)
);
}
}
Puoi quindi accedere al ViewModel da un'attività nel seguente modo:
Kotlin
import androidx.activity.viewModels
class DiceRollActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same DiceRollViewModel instance created by the first activity.
// Use the 'by viewModels()' Kotlin property delegate
// from the activity-ktx artifact
val viewModel: DiceRollViewModel by viewModels()
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect {
// Update UI elements
}
}
}
}
}
Java
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
DiceRollViewModel model = new ViewModelProvider(this).get(DiceRollViewModel.class);
model.getUiState().observe(this, uiState -> {
// update UI
});
}
}
Jetpack Compose
import androidx.lifecycle.viewmodel.compose.viewModel
// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
viewModel: DiceRollViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// Update UI elements
}
Utilizzare le coroutine con ViewModel
ViewModel
include il supporto per le coroutine Kotlin. È in grado di rendere persistente
il lavoro asincrono nello stesso modo in cui rende persistente lo stato della UI.
Per maggiori informazioni, vedi Utilizzare le coroutine Kotlin con i componenti dell'architettura Android.
Il ciclo di vita di un ViewModel
Il ciclo di vita di un ViewModel
è legato direttamente al suo ambito. Un ViewModel
rimane in memoria finché non scompare il ViewModelStoreOwner
a cui è associato. Ciò può verificarsi nei seguenti contesti:
- Nel caso di un'attività, al termine.
- Nel caso di un frammento, quando si stacca.
- Nel caso di una voce di navigazione, quando viene rimossa dallo stack precedente.
Ciò rende i ViewModel un'ottima soluzione per archiviare i dati che sopravvivono alle modifiche alla configurazione.
La Figura 1 illustra i vari stati del ciclo di vita di un'attività durante la rotazione e al termine. L'illustrazione mostra anche la durata del
ViewModel
accanto al ciclo di vita dell'attività associata. Questo particolare
diagramma illustra gli stati di un'attività. Gli stessi stati di base si applicano
al ciclo di vita di un fragment.
Di solito, richiedi un ViewModel
la prima volta che il sistema chiama il metodo onCreate()
di un oggetto attività. Il sistema potrebbe chiamare
onCreate()
più volte durante l'esistenza di un'attività, ad esempio
quando lo schermo di un dispositivo viene ruotato. L'ViewModel
esiste dal momento in cui
richiedi per la prima volta un ViewModel
fino al termine e all'eliminazione dell'attività.
Cancellazione delle dipendenze di ViewModel
ViewModel chiama il metodo onCleared
quando ViewModelStoreOwner
lo distrugge nel corso del suo ciclo di vita. In questo modo puoi eliminare qualsiasi lavoro
o dipendenza che segue il ciclo di vita di ViewModel.
L'esempio seguente mostra un'alternativa a viewModelScope
.
viewModelScope
è un CoroutineScope
integrato che
segue automaticamente il ciclo di vita di ViewModel. ViewModel lo utilizza per
attivare operazioni correlate all'attività. Se vuoi utilizzare un ambito personalizzato anziché
viewModelScope
per semplificare i test, il ViewModel può ricevere un
CoroutineScope
come dipendenza nel suo costruttore. Quando
ViewModelStoreOwner
cancella il ViewModel alla fine del suo ciclo di vita, il
ViewModel annulla anche CoroutineScope
.
class MyViewModel(
private val coroutineScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {
// Other ViewModel logic ...
override fun onCleared() {
coroutineScope.cancel()
}
}
A partire dal ciclo di vita versione 2.5 e successive, puoi passare uno o più oggetti Closeable
al costruttore di ViewModel che si chiude automaticamente quando l'istanza di ViewModel viene cancellata.
class CloseableCoroutineScope(
context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
class MyViewModel(
private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
// Other ViewModel logic ...
}
Best practice
Di seguito sono riportate alcune best practice chiave da seguire durante l'implementazione di ViewModel:
- A causa del loro ambito, utilizza i ViewModels come dettagli di implementazione di un holder dello stato a livello di schermata. Non utilizzarli come contenitori di stato di componenti UI riutilizzabili come gruppi di chip o moduli. In caso contrario, otterresti la stessa istanza di ViewModel in diversi utilizzi dello stesso componente UI nello stesso ViewModelStoreOwner, a meno che tu non utilizzi una chiave ViewModel esplicita per chip.
- I ViewModel non devono conoscere i dettagli di implementazione dell'UI. Mantieni i nomi dei metodi esposti dall'API ViewModel e quelli dei campi dello stato dell'UI il più generici possibile. In questo modo, il ViewModel può ospitare qualsiasi tipo di UI: un cellulare, un dispositivo pieghevole, un tablet o persino un Chromebook.
- Poiché possono potenzialmente durare più a lungo di
ViewModelStoreOwner
, i ViewModel non devono contenere riferimenti ad API correlate al ciclo di vita, comeContext
oResources
, per evitare perdite di memoria. - Non passare ViewModels ad altre classi, funzioni o altri componenti UI. Poiché vengono gestiti dalla piattaforma, devi tenerli il più vicino possibile. Vicino alla funzione componibile a livello di attività, frammento o schermata. In questo modo, i componenti di livello inferiore non possono accedere a più dati e logica di quelli necessari.
Ulteriori informazioni
Man mano che i dati diventano più complessi, potresti scegliere di avere una classe separata solo per caricare i dati. Lo scopo di ViewModel
è incapsulare i dati per
un controller UI per consentire ai dati di sopravvivere alle modifiche alla configurazione. Per informazioni
su come caricare, rendere persistenti e gestire i dati tra le modifiche alla configurazione, consulta
Stati dell'interfaccia utente salvati.
La Guida all'architettura delle app per Android suggerisce di creare una classe repository per gestire queste funzioni.
Risorse aggiuntive
Per ulteriori informazioni sul corso ViewModel
, consulta le seguenti risorse.
Documentazione
Campioni
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Utilizzare le coroutine Kotlin con componenti compatibili con il ciclo di vita
- Salvare gli stati dell'interfaccia utente
- Caricare e visualizzare i dati impaginati