Interfejsy API zakresu ViewModel   część Androida Jetpack.

Zakres jest kluczowy do efektywnego korzystania z ViewModel. Każdy ViewModel jest ograniczony do obiektu, który implementuje interfejs ViewModelStoreOwner. Istnieje kilka interfejsów API, które ułatwiają zarządzanie zakresem ViewModel. W tym dokumencie opisujemy niektóre z najważniejszych technik, które warto znać.

Metoda ViewModelProvider.get() umożliwia uzyskanie instancji ViewModel ograniczonej do dowolnego ViewModelStoreOwner. Użytkownicy języka Kotlin mają do dyspozycji różne funkcje rozszerzające, które można wykorzystać w najczęstszych przypadkach użycia. Wszystkie implementacje funkcji rozszerzających Kotlin korzystają z interfejsu ViewModelProvider API.

ViewModel ograniczone do najbliższego ViewModelStoreOwner

Możesz ograniczyć ViewModel do elementu kompozycyjnego, aktywności lub miejsca docelowego w grafie nawigacji. Funkcja viewModel() w Compose umożliwia uzyskanie instancji ViewModel ograniczonej do najbliższego ViewModelStoreOwner.

import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun MyScreen(
    modifier: Modifier = Modifier,
    // ViewModel API available in lifecycle.lifecycle-viewmodel-compose
    // The ViewModel is scoped to the closest ViewModelStoreOwner provided
    // via the LocalViewModelStoreOwner CompositionLocal. In order of proximity,
    // this could be the destination of a Navigation graph
    // or the host Activity.
    viewModel: MyViewModel = viewModel()
) { /* ... */ }

ViewModel ograniczone do dowolnego ViewModelStoreOwner

Funkcja viewModel() przyjmuje opcjonalny parametr viewModelStoreOwner, którego możesz użyć, aby określić, do którego ViewModelStoreOwner ma być ograniczona instancja ViewModel.

import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.ViewModelStoreOwner

@Composable
fun MyScreen(
    // A custom owner passed in, such as a parent NavBackStackEntry
    customOwner: ViewModelStoreOwner,
    // The ViewModel is now scoped to the provided customOwner
    viewModel: MyViewModel = viewModel(viewModelStoreOwner = customOwner)
) {
    /* ... */
}

ViewModel ograniczone do elementu kompozycyjnego

Możesz użyć rememberViewModelStoreOwner(), aby ograniczyć ViewModel bezpośrednio do miejsca wywołania funkcji kompozycyjnej. Jest to szczególnie przydatne w przypadku komponentów interfejsu, które są dynamicznie dodawane do ekranu lub z niego usuwane na podstawie stanu, np. elementów strony lub listy leniwej. Gdy element kompozycyjny, który jest właścicielem ViewModelStoreOwner opuści kompozycję, powiązany ViewModelStore zostanie wyczyszczony, a ViewModel zostanie zniszczony.

Użyj rememberViewModelStoreOwner(), aby utworzyć magazyn, który jest świadomy cyklu życia i przetrwa zmiany konfiguracji.

@Composable
fun RememberViewModelStoreOwnerSample() {
    // Create a ViewModelStoreOwner scoped to this specific call site.
    // When this composable leaves the composition,
    // the associated ViewModelStore will be cleared.
    val scopedOwner = rememberViewModelStoreOwner()

    CompositionLocalProvider(LocalViewModelStoreOwner provides scopedOwner) {
        // This ViewModel is scoped to `scopedOwner`.
        // It will survive configuration changes but will be cleared when
        // the composable is removed from the UI tree.
        val viewModel = viewModel { TestViewModel("scoped_data") }
        // Use the ViewModel
    }
}

W przypadku bardziej złożonych implementacji, takich jak HorizontalPager, lub przypadków wymagających wielu niezależnych zakresów użyj rememberViewModelStoreProvider(). Umożliwia to generowanie różnych instancji ViewModelStoreOwner dla różnych kluczy (np. indeksów stron). Dzięki temu każda strona zachowuje własny, niezależny stan ViewModel.

@Composable
fun RememberViewModelStoreProviderSample() {
    val storeProvider = rememberViewModelStoreProvider()
    val pages = listOf("Page 1", "Page 2", "Page 3")

    HorizontalPager(pageCount = pages.size) { page ->
        // Create a ViewModelStoreOwner for the specific page using the provider.
        val pageOwner = rememberViewModelStoreOwner(provider = storeProvider, key = page)

        CompositionLocalProvider(LocalViewModelStoreOwner provides pageOwner) {
            val pageViewModel = viewModel { TestViewModel(pages[page]) }
            // Use pageViewModel
        }
    }
}

ViewModel ograniczone do grafu nawigacji

Grafy nawigacji są też właścicielami magazynu ViewModel. Jeśli używasz Navigation Compose, możesz uzyskać instancję ViewModel ograniczoną do grafu nawigacji za pomocą funkcji getBackStackEntry().

viewModel() pobiera instancję z najbliższego ViewModelStoreOwner dostarczonego przez LocalViewModelStoreOwner CompositionLocal. W typowej aplikacji Compose korzystającej z Jetpack Navigation ten właściciel jest bieżącym wpisem w stosie wstecznym nawigacji. Oznacza to, że ViewModel pozostaje w pamięci tak długo, jak długo to miejsce docelowe jest obecne w stosie wstecznym.

import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun MyAppNavHost() {
    // ...
    composable("myScreen") { backStackEntry ->
        // Retrieve the NavBackStackEntry of "parentNavigationRoute"
        val parentEntry = remember(backStackEntry) {
            navController.getBackStackEntry("parentNavigationRoute")
        }
        // Get the ViewModel scoped to the `parentNavigationRoute` Nav graph
        val parentViewModel: SharedViewModel = viewModel(parentEntry)
        // ...
    }
}

Jeśli oprócz Jetpack Navigation używasz Hilt, możesz użyć interfejsu hiltNavGraphViewModels(graphId) API w ten sposób.

import androidx.hilt.navigation.compose.hiltViewModel

@Composable
fun MyAppNavHost() {
    // ...
    composable("myScreen") { backStackEntry ->
        val parentEntry = remember(backStackEntry) {
            navController.getBackStackEntry("parentNavigationRoute")
        }

        // ViewModel API available in hilt.hilt-navigation-compose
        // The ViewModel is scoped to the `parentNavigationRoute` Navigation graph
        // and is provided using the Hilt-generated ViewModel factory
        val parentViewModel: SharedViewModel = hiltViewModel(parentEntry)
        // ...
    }
}

Dodatkowe materiały

Więcej informacji o ViewModel i zakresie znajdziesz w tych dodatkowych materiałach:

Dokumentacja

Wyświetlanie treści