Informacje o modelu ViewModel Część pakietu Android Jetpack.
Klasa ViewModel
jest właścicielem stanu logiki biznesowej lub ekranu. Prezentuje stan w interfejsie użytkownika i obejmuje powiązaną logikę biznesową.
Jego główną zaletą jest to, że zapisuje stan w pamięci podręcznej i utrzymuje go po zmianach konfiguracji. Oznacza to, że interfejs użytkownika nie musi ponownie pobierać danych podczas przechodzenia między działaniami czy śledzenia zmian w konfiguracji, np. przy obróceniu ekranu.
Więcej informacji na temat posiadaczy uprawnień znajdziesz na stronie właścicieli stanów. Więcej informacji o warstwie interfejsu znajdziesz też w wytycznych dotyczących warstwy interfejsu.
Zalety ViewModel
Alternatywą dla obiektu ViewModel jest zwykła klasa przechowująca dane wyświetlane w interfejsie. Może to powodować problemy podczas nawigowania między działaniami lub miejscami docelowymi nawigacji. W ten sposób zostaną zniszczone dane, których nie zapiszesz za pomocą mechanizmu zapisywania stanu instancji. ViewModel zapewnia wygodny interfejs API zwiększający trwałość danych, który rozwiązuje ten problem.
Zasadniczo główne zalety klasy ViewModel są następujące:
- Pozwala zachować stan interfejsu użytkownika.
- Zapewnia dostęp do logiki biznesowej.
Trwałość
ViewModel zapewnia trwałość zarówno przez stan, jaki posiada model ViewModel, jak i przez operacje wywoływane przez niego. Dzięki temu nie musisz ponownie pobierać danych za pomocą typowych zmian konfiguracji, takich jak obrót ekranu.
Zakres
Gdy tworzysz instancję ViewModel, przekazujesz do niego obiekt implementujący interfejs ViewModelStoreOwner
. Może to być miejsce docelowe nawigacji, wykres nawigacyjny, aktywność, fragment lub dowolny inny typ implementujący interfejs. Model View jest następnie ograniczony do cyklu życia obiektu ViewModelStoreOwner
. Pozostaje w pamięci, dopóki ViewModelStoreOwner
nie zniknie na stałe.
Zakres klas jest bezpośrednimi lub pośrednimi podklasami interfejsu ViewModelStoreOwner
. Podklasy bezpośrednie to ComponentActivity
, Fragment
i NavBackStackEntry
.
Pełną listę podklas pośrednich znajdziesz w dokumentacji ViewModelStoreOwner
.
Gdy fragment lub działanie, do którego jest ograniczony model ViewModel, zostaną zniszczone, asynchroniczna praca będzie kontynuowana w modelu ViewModel, którego zakres jest ograniczony. To klucz do wytrwałości.
Więcej informacji znajdziesz w poniższej sekcji poświęconej cyklowi życia modelu ViewModel.
SavedStateHandle
SavedStateHandle pozwala na trwałe przechowywanie danych nie tylko w wyniku zmian konfiguracji, ale i przez odtworzenie każdego procesu. Oznacza to, że pozwala zachować stan UI niezmieniony, nawet gdy użytkownik zamknie aplikację i otworzy ją później.
Dostęp do logiki biznesowej
Mimo że w warstwie danych tkwi większość logiki biznesowej, warstwa interfejsu może też zawierać logikę biznesową. Może się tak zdarzyć, gdy łączysz dane z wielu repozytoriów w celu utworzenia stanu interfejsu ekranu lub gdy określony typ danych nie wymaga warstwy danych.
ViewModel to odpowiednie miejsce do obsługi logiki biznesowej w warstwie interfejsu użytkownika. Model ViewModel odpowiada również za obsługę zdarzeń i przekazywanie ich do innych warstw w hierarchii, gdy trzeba zastosować logikę biznesową do modyfikacji danych aplikacji.
Jetpack Compose
W przypadku korzystania z Jetpack Compose podstawowym sposobem udostępniania stanu interfejsu użytkownika w funkcjach kompozycyjnych jest model ViewModel. W aplikacji hybrydowej działania i fragmenty po prostu hostują funkcje kompozycyjne. Jest to odejście od wcześniejszego podejścia, ponieważ tworzenie elementów interfejsu wielokrotnego użytku z działaniami i fragmentami nie było tak proste i intuicyjne, co sprawiło, że były one znacznie bardziej aktywne jako kontrolery UI.
Najważniejszą rzeczą, o której należy pamiętać podczas korzystania z modelu ViewModel z funkcją Compose, jest to, że nie można określić zakresu obiektu ViewModel na obiekt kompozycyjny. To dlatego, że funkcja
kompozycyjna to nie ViewModelStoreOwner
. 2 instancje tego samego elementu kompozycyjnego w elemencie „Composition” lub 2 różne obiekty kompozycyjne uzyskujące dostęp do tego samego typu obiektu ViewModel w ramach jednego obiektu ViewModelStoreOwner
otrzymają to samo wystąpienie klasy ViewModel, co często nie jest oczekiwanym zachowaniem.
Aby wykorzystać zalety modelu ViewModel w funkcji Compose, przechowuj każdy ekran we fragmencie lub aktywności albo użyj nawigacji związanej z tworzeniem i używaj modeli ViewModels w funkcjach kompozycyjnych jak najbliżej miejsca docelowego Nawigacji. Wynika to z tego, że możesz określić zakres modelu ViewModel na miejsca docelowe nawigacji, wykresy nawigacyjne, aktywności i fragmenty.
Więcej informacji znajdziesz w przewodniku po przenoszeniu stanów w Jetpack Compose.
Wdrażanie modelu widoku
Poniżej znajduje się przykładowa implementacja modelu ViewModel na ekranie, który umożliwia użytkownikowi rzucanie kostkami.
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
)
);
}
}
Dostęp do modelu ViewModel możesz uzyskać w aktywności w następujący sposób:
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
}
Używanie współprogramów z funkcją ViewModel
ViewModel
zapewnia obsługę współprogramów Kotlin. Może utrzymywać pracę asynchroniczną w taki sam sposób jak utrzymywany stan interfejsu użytkownika.
Więcej informacji znajdziesz w artykule o używaniu współprogramów Kotlin z komponentami architektury Androida.
Cykl życia obiektu ViewModel
Cykl życia obiektu ViewModel
jest bezpośrednio powiązany z jego zakresem. Element ViewModel
pozostaje w pamięci do momentu, gdy zniknie element ViewModelStoreOwner
, do którego jest ograniczony. Może się to zdarzyć w tych kontekstach:
- W przypadku aktywności – po jej zakończeniu.
- W przypadku fragmentu, gdy się odłącza.
- w przypadku wpisu dotyczącego nawigacji, gdy jest on usuwany ze stosu wstecznego.
To sprawia, że obiekty ViewModels stanowią świetne rozwiązanie do przechowywania danych, które nie są w stanie przetrwać zmian konfiguracji.
Rysunek 1 przedstawia różne stany cyklu życia działania podczas jego rotacji, a następnie zakończenia. Ilustracja przedstawia też czas trwania ViewModel
obok powiązanego cyklu życia aktywności. Ten diagram
ilustruje stany działania. W cyklu życia fragmentu
mają zastosowanie te same stany podstawowe.
Żądanie ViewModel
jest zwykle wysyłane przy pierwszym wywołaniu przez system metody onCreate()
obiektu aktywności. System może wywołać metodę onCreate()
kilka razy w trakcie występowania działania, na przykład przy obróceniu ekranu urządzenia. Obiekt ViewModel
istnieje od momentu, gdy pierwszy raz zażądasz elementu ViewModel
, do zakończenia działania i jego zniszczenia.
Czyszczenie zależności ViewModel
ViewModel wywołuje metodę onCleared
, gdy ViewModelStoreOwner
zniszczy ją w trakcie swojego cyklu życia. Pozwala to wyczyścić wszelkie zadania i zależności zgodne z cyklem życia obiektu ViewModel.
Przykład poniżej pokazuje alternatywę dla viewModelScope
.
viewModelScope
to wbudowana właściwość CoroutineScope
, która automatycznie śledzi cykl życia obiektu ViewModel. ViewModel używa go
do aktywowania operacji związanych z działalnością firmy. Jeśli do łatwiejszego testowania chcesz użyć zakresu niestandardowego zamiast viewModelScope
, obiekt ViewModel może otrzymać zależność CoroutineScope
jako zależność w swoim konstruktorze. Gdy ViewModelStoreOwner
wyczyści model ViewModel na koniec swojego cyklu życia, obiekt ViewModel anuluje też obiekt CoroutineScope
.
class MyViewModel(
private val coroutineScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {
// Other ViewModel logic ...
override fun onCleared() {
coroutineScope.cancel()
}
}
Od cyklu życia w wersji 2.5 i nowszych możesz przekazać do konstruktora ViewModel co najmniej 1 obiekt Closeable
, który zamyka się automatycznie 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 najważniejszych sprawdzonych metod, które należy stosować podczas implementacji obiektu ViewModel:
- Ze względu na swój zakres możesz używać obiektów ViewModel jako szczegółów implementacji posiadacza stanu na poziomie ekranu. Nie używaj ich jako stanów elementów interfejsu wielokrotnego użytku, takich jak grupy elementów lub formularze. W przeciwnym razie otrzymasz tę samą instancję ViewModel w różnych przypadkach użycia tego samego komponentu interfejsu użytkownika w ramach tego samego obiektu ViewModelStoreOwner.
- Obiekty ViewModele nie powinny mieć informacji o szczegółach implementacji interfejsu użytkownika. Zachowaj jak najbardziej ogólne nazwy metod udostępnianych przez interfejs ViewModel API oraz pól stanu interfejsu. Dzięki temu ViewModel może obsłużyć każdy interfejs: telefon komórkowy, składany, tablet, a nawet Chromebooka.
- Modele View mogą żyć dłużej niż obiekt
ViewModelStoreOwner
, dlatego nie powinny zawierać żadnych odniesień do interfejsów API związanych z cyklem życia, takich jakContext
czyResources
, aby zapobiec wyciekom pamięci. - Nie przekazuj obiektów ViewModel do innych klas, funkcji ani innych komponentów interfejsu użytkownika. Ponieważ to platforma, którymi zarządza, staraj się, by były jak najbliżej siebie. W pobliżu funkcji, fragmentu lub funkcji kompozycyjnej na poziomie ekranu. Uniemożliwia to komponentom niższego poziomu dostęp do większej ilości danych i logiki, niż potrzebują.
Dodatkowe informacje
W miarę jak Twoje dane stają się coraz bardziej złożone, możesz utworzyć osobną klasę do ich wczytywania. Obiekt ViewModel
służy do herbaty danych dla kontrolera interfejsu użytkownika, tak aby dane mogły przetrwać zmiany w konfiguracji. Informacje o wczytywaniu i utrwalaniu danych oraz zarządzaniu nimi w przypadku różnych zmian konfiguracji znajdziesz w artykule o zapisywaniu stanów interfejsu.
W przewodniku po architekturze aplikacji na Androida zalecamy 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
- Posiadacze stanów i stan interfejsu użytkownika
- Produkcja państwowa
- Warstwa danych
Próbki
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy 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 z podziałem na strony