Omówienie ViewModel części Androida Jetpack.
Klasa ViewModel to kontener stanu logiki biznesowej lub stanu na poziomie ekranu
holder. Udostępnia stan interfejsowi i hermetyzuje powiązaną logikę biznesową.
Jej główną zaletą jest to, że buforuje stan i zachowuje go podczas zmian konfiguracji. Oznacza to, że interfejs nie musi ponownie pobierać danych podczas przechodzenia między aktywnościami ani po zmianach konfiguracji, np. po obróceniu ekranu.
Więcej informacji o kontenerach stanu znajdziesz w przewodniku po kontenerach stanu. Podobnie więcej informacji o warstwie interfejsu znajdziesz w przewodniku po warstwie interfejsu.
Korzyści ViewModel
Alternatywą dla ViewModel jest zwykła klasa, która zawiera dane wyświetlane w interfejsie. Może to stanowić problem podczas przechodzenia między aktywnościami lub miejscami docelowymi nawigacji. Jeśli nie zapiszesz danych za pomocą mechanizmu zapisanego stanu instancji, zostaną one zniszczone. ViewModel udostępnia wygodny interfejs API do utrwalania danych, który rozwiązuje ten problem.
W przypadku kontenerów stanu Compose oferuje funkcje retain, które umożliwiają zwykłym klasom przetrwanie zmian konfiguracji bez pełnej infrastruktury ViewModel. Chociaż oba mechanizmy pomagają w zachowaniu stanu, generalnie bezpieczniej jest udostępnić ViewModel instancji zachowanej niż odwrotnie, ponieważ ich cykle życia i zachowania związane z czyszczeniem różnią się.
Główne korzyści klasy ViewModel są zasadniczo 2:
- Umożliwia zachowanie stanu interfejsu.
- Zapewnia dostęp do logiki biznesowej.
Trwałość
ViewModel umożliwia trwałość zarówno dzięki stanowi, który zawiera, jak i operacjom, które wywołuje. Dzięki buforowaniu nie musisz ponownie pobierać danych po typowych zmianach konfiguracji, takich jak obrócenie ekranu.
Zakres
Podczas tworzenia instancji ViewModel przekazujesz jej obiekt, który implementuje interfejs
ViewModelStoreOwner. Może to być miejsce docelowe nawigacji, wykres nawigacji, aktywność lub dowolny inny typ, który implementuje ten interfejs. Możesz też bezpośrednio określić zakres ViewModel w komponencie za pomocą
interfejsu API rememberViewModelStoreOwner.
ViewModel jest wtedy ograniczony do cyklu życia
ViewModelStoreOwner. Pozostaje w pamięci, dopóki jego ViewModelStoreOwner nie zniknie na stałe (np. gdy właściciel komponentu opuści kompozycję).
Zakres klas jest bezpośrednio lub pośrednio podklasą interfejsu ViewModelStoreOwner. Bezpośrednie podklasy to
ComponentActivity i NavBackStackEntry.
Pełną listę pośrednich podklas znajdziesz w dokumentacji
ViewModelStoreOwner reference. Aby ograniczyć zakres ViewModels do poszczególnych elementów w LazyList lub Pager, użyj rememberViewModelStoreProvider(), aby przenieść zarządzanie właścicielem do elementu nadrzędnego.
Gdy aktywność hosta ulega zmianie konfiguracji, praca asynchroniczna jest kontynuowana w ViewModel, niezależnie od tego, czy jest ona ograniczona do aktywności, czy do konkretnego komponentu. Jest to klucz do trwałości.
Więcej informacji znajdziesz w sekcji Cykl życia ViewModel, interfejsach API zakresu ViewModel, i przewodniku po przenoszeniu stanu w Jetpack Compose.
SavedStateHandle
SavedStateHandle umożliwia zachowanie danych nie tylko podczas zmian konfiguracji, ale też po śmierci procesu. Oznacza to, że możesz zachować stan interfejsu nawet wtedy, gdy użytkownik zamknie aplikację i otworzy ją później.
Więcej informacji o zapisywaniu stanu interfejsu, zobacz Zapisywanie stanu interfejsu w Compose.
Dostęp do logiki biznesowej
Chociaż zdecydowana większość logiki biznesowej znajduje się w warstwie danych, warstwa interfejsu może również zawierać logikę biznesową. Może się tak zdarzyć, gdy łączysz dane z wielu repozytoriów, aby utworzyć stan interfejsu ekranu, lub gdy określony typ danych nie wymaga warstwy danych.
ViewModel to odpowiednie miejsce do obsługi logiki biznesowej w warstwie interfejsu. ViewModel odpowiada też za obsługę zdarzeń i przekazywanie ich do innych warstw hierarchii, gdy trzeba zastosować logikę biznesową, aby zmodyfikować dane aplikacji.
Implementowanie ViewModel
Poniżej znajdziesz przykład implementacji ViewModel dla ekranu, który umożliwia użytkownikowi rzucanie kostką.
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,
)
}
}
}
Następnie możesz uzyskać dostęp do ViewModel z komponentu na poziomie ekranu w ten sposób:
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
}
Używanie współprogramów z ViewModel
ViewModel obsługuje współprogramy Kotlin. Może zachowywać pracę asynchroniczną w taki sam sposób jak stan interfejsu.
Więcej informacji znajdziesz w artykule Używanie współprogramów Kotlin z komponentami architektury Androida.
Cykl życia ViewModel
Cykl życia ViewModel jest bezpośrednio powiązany z jego zakresem. A ViewModel
pozostaje w pamięci, dopóki nie zniknie ViewModelStoreOwner, do którego jest ograniczony
disappears. Może się to zdarzyć w tych kontekstach:
- W przypadku aktywności – gdy się zakończy.
- W przypadku wpisu nawigacji – gdy zostanie usunięty ze stosu wstecznego.
- W przypadku komponentu – gdy opuści kompozycję.
Za pomocą
rememberViewModelStoreOwnermożesz ograniczyć zakres ViewModel bezpośrednio do dowolnej części interfejsu (np.PagerlubLazyList).
Dzięki temu ViewModels są doskonałym rozwiązaniem do przechowywania danych, które przetrwają zmiany konfiguracji.
Rysunek 1 przedstawia różne stany cyklu życia aktywności, gdy ulega ona obróceniu, a następnie zostaje zakończona. Ilustracja pokazuje też czas życia
ViewModel obok powiązanego cyklu życia działania. Ten konkretny diagram przedstawia stany aktywności.
Zwykle prosisz o ViewModel przy pierwszym wywołaniu przez system metody onCreate() obiektu aktywności. System może wywołać
onCreate() kilka razy w trakcie istnienia aktywności, np.
gdy ekran urządzenia zostanie obrócony. ViewModel istnieje od momentu
pierwszego żądania ViewModel do momentu zakończenia i zniszczenia aktywności.
Czyszczenie zależności ViewModel
ViewModel wywołuje metodę onCleared, gdy ViewModelStoreOwner
niszczy ją w trakcie cyklu życia. Umożliwia to zwalnianie miejsca na wszelkie prace lub zależności, które są zgodne z cyklem życia ViewModel.
Poniższy przykład pokazuje alternatywę dla viewModelScope.
viewModelScope to wbudowany CoroutineScope, który
automatycznie śledzi cykl życia ViewModel. ViewModel używa go do wywoływania operacji związanych z działalnością. Jeśli chcesz używać niestandardowego zakresu zamiast
viewModelScope dla łatwiejszego testowania, ViewModel może otrzymać
CoroutineScope jako zależność w swoim konstruktorze. Gdy ViewModelStoreOwner wyczyści ViewModel na końcu jego cyklu życia, ViewModel anuluje też CoroutineScope.
class MyViewModel(
private val coroutineScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {
// Other ViewModel logic ...
override fun onCleared() {
coroutineScope.cancel()
}
}
Od wersji 2.5 cyklu życia możesz przekazać do konstruktora ViewModel co najmniej 1 obiekt Closeable, który zostanie automatycznie zamknięty po wyczyszczeniu instancji ViewModel.
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 ...
}
Sprawdzone metody
Oto kilka kluczowych sprawdzonych metod, których należy przestrzegać podczas implementowania ViewModel:
- Ze względu na ich zakres używaj ViewModels jako szczegółów implementacji zmiennej stanu na poziomie ekranu. Nie używaj ich jako kontenerów stanu komponentów interfejsu wielokrotnego użytku, takich jak grupy chipów czy formularze. W przeciwnym razie w różnych zastosowaniach tego samego komponentu interfejsu w ramach tego samego ViewModelStoreOwner uzyskasz tę samą instancję ViewModel, chyba że użyjesz jawnego klucza modelu widoku dla każdego chipa.
- ViewModels nie powinny znać szczegółów implementacji interfejsu. Nazwy metod udostępnianych przez interfejs API ViewModel i pól stanu interfejsu powinny być jak najbardziej ogólne. Dzięki temu ViewModel może obsługiwać każdy typ interfejsu: telefon komórkowy, urządzenie składane, tablet, a nawet Chromebooka.
- Ponieważ ViewModels mogą potencjalnie żyć dłużej niż
ViewModelStoreOwner, nie powinny zawierać żadnych odwołań do interfejsów API związanych z cyklem życia, takich jakContextczyResources, aby zapobiec wyciekom pamięci. - Nie przekazuj ViewModels do innych klas, funkcji ani komponentów interfejsu. Ponieważ platforma nimi zarządza, powinny one znajdować się jak najbliżej niej – blisko Activity, funkcji typu „composable” na poziomie ekranu lub miejsca docelowego Navigation. Zapobiega to uzyskiwaniu przez komponenty niższego poziomu dostępu do większej ilości danych i logiki niż potrzebują.
Dodatkowe informacje
W miarę jak dane stają się bardziej złożone, możesz utworzyć osobną klasę tylko do wczytywania danych. Celem ViewModel jest hermetyzacja danych dla
kontrolera interfejsu, aby dane przetrwały zmiany konfiguracji. Informacje
o tym, jak wczytywać, zachowywać i zarządzać danymi podczas zmian konfiguracji, znajdziesz w artykule
Zapisywanie stanów interfejsu.
Przewodnik po architekturze aplikacji na Androida sugeruje utworzenie klasy repozytorium do obsługi tych funkcji.
Dodatkowe materiały
Więcej informacji o klasie ViewModel znajdziesz w tych materiałach.
Dokumentacja
- Warstwa interfejsu
- Zdarzenia interfejsu
- Kontenery stanu i stan interfejsu
- Generowanie stanu
- Warstwa danych
Wyświetlanie treści
Przykłady
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Używanie współprogramów Kotlin z komponentami uwzględniającymi cykl życia
- Zapisywanie stanów interfejsu
- Wczytywanie i wyświetlanie danych podzielonych na strony