Omówienie ViewModela Część Androida Jetpack.
Klasa ViewModel
to obiekt przechowujący stan logiki biznesowej lub ekranu. Udostępnia stan interfejsowi i enkapsuluje powiązaną logikę biznesową.
Jego główną zaletą jest to, że buforuje stan i utrzymuje 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 podmiotach państwowych znajdziesz w tym przewodniku. Podobnie więcej informacji o warstwie interfejsu znajdziesz w wytycznych dotyczących warstwy interfejsu.
Korzyści z używania ViewModel
Alternatywą dla ViewModelu 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 tych danych za pomocą mechanizmu zapisywania stanu instancji, zostaną one zniszczone. ViewModel udostępnia wygodny interfejs API do utrwalania danych, który rozwiązuje ten problem.
Klasa ViewModel ma 2 główne zalety:
- Umożliwia zachowanie stanu interfejsu.
- Zapewnia dostęp do logiki biznesowej.
Trwałość
ViewModel umożliwia zachowanie trwałości zarówno stanu, który przechowuje, jak i operacji, które wywołuje. Dzięki temu buforowaniu nie musisz ponownie pobierać danych w przypadku typowych zmian konfiguracji, takich jak obrócenie ekranu.
Zakres
Podczas tworzenia instancji klasy ViewModel przekazujesz do niej obiekt implementujący interfejs ViewModelStoreOwner
. Może to być miejsce docelowe nawigacji, wykres nawigacji, aktywność, fragment lub dowolny inny typ, który implementuje interfejs. ViewModel jest następnie ograniczony do cyklu życia ViewModelStoreOwner
. Pozostaje w pamięci do momentu, gdy ViewModelStoreOwner
zniknie na stałe.
Szereg klas jest bezpośrednimi lub pośrednimi podklasami interfejsu ViewModelStoreOwner
. Bezpośrednie podklasy to ComponentActivity
, Fragment
i NavBackStackEntry
.
Pełną listę pośrednich podklas znajdziesz w dokumentacji ViewModelStoreOwner
.
Gdy fragment lub aktywność, do których zakresu należy ViewModel, zostaną zniszczone, praca asynchroniczna będzie kontynuowana w obiekcie ViewModel, który jest do nich przypisany. To klucz do wytrwałości.
Więcej informacji znajdziesz w sekcji Cykl życia obiektu ViewModel poniżej.
SavedStateHandle
SavedStateHandle umożliwia zachowywanie danych nie tylko w przypadku zmian konfiguracji, ale też podczas ponownego tworzenia procesu. Oznacza to, że możesz zachować stan interfejsu, nawet gdy użytkownik zamknie aplikację i otworzy ją później.
Dostęp do logiki biznesowej
Chociaż większość logiki biznesowej znajduje się w warstwie danych, warstwa interfejsu użytkownika 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.
Jetpack Compose
Gdy używasz Jetpack Compose, ViewModel jest podstawowym sposobem udostępniania stanu interfejsu ekranu komponentom kompozycyjnym. W aplikacji hybrydowej aktywności i fragmenty po prostu hostują funkcje typu „composable”. To zmiana w porównaniu z dotychczasowymi metodami, w których tworzenie wielokrotnego użytku elementów interfejsu z aktywnościami i fragmentami nie było tak proste i intuicyjne, co powodowało, że były one znacznie bardziej aktywne jako kontrolery interfejsu.
Najważniejszą rzeczą, o której należy pamiętać podczas używania ViewModel z Compose, jest to, że nie można ograniczyć zakresu ViewModel do funkcji kompozycyjnej. Dzieje się tak, ponieważ funkcja kompozycyjna nie jest ViewModelStoreOwner
. Dwa wystąpienia tego samego komponentu w kompozycji lub dwa różne komponenty uzyskujące dostęp do tego samego typu ViewModel w ramach tego samego ViewModelStoreOwner
otrzymają ten sam egzemplarz ViewModel, co często nie jest oczekiwanym zachowaniem.
Aby uzyskać korzyści z ViewModel w Compose, umieść każdy ekran w fragmencie lub aktywności albo użyj nawigacji Compose i używaj ViewModel w funkcjach kompozycyjnych jak najbliżej miejsca docelowego nawigacji. Dzieje się tak, ponieważ możesz ograniczyć zakres ViewModel do miejsc docelowych nawigacji, wykresów nawigacji, aktywności i fragmentów.
Więcej informacji znajdziesz w przewodniku na temat przenoszenia stanu w Jetpack Compose.
Implementowanie klasy ViewModel
Poniżej znajdziesz przykładową implementację ViewModelu dla ekranu, 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
)
);
}
}
Następnie możesz uzyskać dostęp do ViewModel z aktywności w ten 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
}
Korzystanie z korutyn w klasie ViewModel
ViewModel
obejmuje obsługę korutyn w języku Kotlin. Może ona utrwalać pracę asynchroniczną w taki sam sposób, jak utrwala stan interfejsu.
Więcej informacji znajdziesz w artykule Korzystanie z korutyn Kotlin z komponentami architektury Androida.
Cykl życia obiektu ViewModel
Cykl życia ViewModel
jest bezpośrednio powiązany z jego zakresem. ViewModel
pozostaje w pamięci do czasu zniknięcia ViewModelStoreOwner
, do którego jest przypisany. Może się to zdarzyć w tych sytuacjach:
- W przypadku aktywności – po jej zakończeniu.
- W przypadku fragmentu – gdy się odłączy.
- W przypadku wpisu nawigacyjnego, gdy zostanie on usunięty ze stosu wstecznego.
Dzięki temu ViewModele są doskonałym rozwiązaniem do przechowywania danych, które przetrwają zmiany konfiguracji.
Ilustracja 1 przedstawia różne stany cyklu życia aktywności podczas obracania, a następnie zakończenia. Ilustracja pokazuje też czas życia ViewModel
obok powiązanego cyklu życia aktywności. Ten diagram ilustruje stany aktywności. W przypadku cyklu życia fragmentu obowiązują te same stany podstawowe.
Zwykle żądanie ViewModel
jest wysyłane, gdy system po raz pierwszy wywołuje metodę onCreate()
obiektu działania. System może wywołać funkcję
onCreate()
kilka razy w trakcie działania aktywności, np. gdy ekran urządzenia zostanie obrócony. ViewModel
istnieje od momentu, w którym po raz pierwszy poprosisz o ViewModel
, do momentu zakończenia i usunięcia aktywności.
Usuwanie zależności ViewModel
ViewModel wywołuje metodę onCleared
, gdy ViewModelStoreOwner
jest niszczony w trakcie swojego cyklu życia. Pozwala to usunąć wszystkie zadania lub zależności, które są powiązane z cyklem życia obiektu ViewModel.
Poniższy przykład pokazuje alternatywę dla viewModelScope
.
viewModelScope
to wbudowany CoroutineScope
, który automatycznie śledzi cykl życia ViewModelu. ViewModel używa go do wywoływania operacji związanych z działalnością. Jeśli chcesz użyć zakresu niestandardowego zamiast viewModelScope
, aby ułatwić testowanie, ViewModel może otrzymać CoroutineScope
jako zależność w konstruktorze. Gdy ViewModelStoreOwner
wyczyści ViewModel na końcu 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 przekazywać do konstruktora ViewModela co najmniej 1 Closeable
obiekt, który jest automatycznie zamykany po wyczyszczeniu instancji ViewModela.
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órych należy przestrzegać podczas wdrażania klasy ViewModel:
- Ze względu na zakres używaj obiektów ViewModel jako szczegółów implementacji elementu przechowującego stan na poziomie ekranu. Nie używaj ich jako elementów przechowujących stan komponentów interfejsu wielokrotnego użytku, takich jak grupy elementów czy formularze. W przeciwnym razie w różnych zastosowaniach tego samego komponentu interfejsu w ramach tego samego ViewModelStoreOwner otrzymasz tę samą instancję ViewModel, chyba że użyjesz jawnego klucza modelu widoku dla każdego elementu.
- Obiekty ViewModel nie powinny znać szczegółów implementacji interfejsu. Nazwy metod udostępnianych przez interfejs API ViewModel i nazwy pól stanu interfejsu powinny być jak najbardziej ogólne. Dzięki temu ViewModel może obsługiwać dowolny typ interfejsu: telefon komórkowy, urządzenie składane, tablet, a nawet Chromebooka.
- Mogą one potencjalnie istnieć dłużej niż
ViewModelStoreOwner
, dlatego obiekty ViewModel nie powinny zawierać żadnych odwołań 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 komponentów interfejsu. Ponieważ platforma nimi zarządza, powinny być jak najbliżej niej. W pobliżu funkcji typu „composable” na poziomie aktywności, fragmentu lub ekranu. Zapobiega to uzyskiwaniu przez komponenty niższego poziomu dostępu do większej ilości danych i logiki niż jest to konieczne.
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. Więcej informacji o wczytywaniu, utrwalaniu i zarządzaniu danymi w przypadku zmian konfiguracji znajdziesz w artykule Zapisane stany 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
- Zmienne stanów i stan interfejsu
- Produkcja państwowa
- Warstwa danych
Próbki
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony.
- Używanie korutyn Kotlin z komponentami uwzględniającymi cykl życia
- Zapisywanie stanów interfejsu
- Wczytywanie i wyświetlanie danych podzielonych na strony