ViewModel 範圍設定 API   Android Jetpack 的一項工具

範圍是有效使用 ViewModel 的關鍵。每個 ViewModel 的範圍都是限定為實作 ViewModelStoreOwner 介面的物件。您可以運用幾種 API 更輕鬆管理 ViewModel 的範圍。本文件將概述您應瞭解的一些主要技巧。

ViewModelProvider.get() 方法可讓您取得範圍限定為任何 ViewModelStoreOwner 的 ViewModel 執行個體。如果是 Kotlin 使用者,針對常見的用途提供不同的擴充功能。所有 Kotlin 擴充功能函式實作都會使用 ViewModelProvider API 進階功能。

將 ViewModel 範圍限定為最接近的 ViewModelStoreOwner

您可以將 ViewModel 的範圍限定為可組合函式、活動或 Navigation 圖表的目的地。Compose 中的 viewModel() 函式可讓您取得 ViewModel 範圍限定為最接近 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()
) { /* ... */ }

範圍限定為任何 ViewModelStoreOwner 的 ViewModel

viewModel() 函式會採用選用 viewModelStoreOwner 參數,可用於指定 ViewModel 的範圍限定哪個 ViewModelStoreOwner 執行個體。

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

您可以透過 rememberViewModelStoreOwner(),直接將 ViewModel 範圍限定在可組合函式的呼叫位置。如果 UI 元件會根據狀態動態新增至畫面或從畫面中移除,這項功能就特別實用,例如頁面或延遲清單的項目。當擁有 ViewModelStoreOwner 的可組合項離開組合時,相關聯的 ViewModelStore 會清除,ViewModel 也會遭到刪除。

使用 rememberViewModelStoreOwner() 建立生命週期感知型儲存區,確保資料在設定變更後仍然有效。

@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
    }
}

如要進行更複雜的實作 (例如 HorizontalPager),或需要多個獨立範圍的情況,請使用 rememberViewModelStoreProvider()。 這樣一來,您就能為不同鍵 (例如網頁索引) 產生不同的 ViewModelStoreOwner 執行個體。這樣一來,每個頁面都會維持自己的獨立 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
        }
    }
}

範圍限定為 Navigation 圖形的 ViewModel

Navigation 圖表也是 ViewModel 儲存區擁有者。使用 Navigation Compose 時,您可以透過 getBackStackEntry() 函式,取得 ViewModel 範圍限定為 Navigation 圖表的執行個體。

viewModel() 會從 LocalViewModelStoreOwner CompositionLocal 提供的最接近 ViewModelStoreOwner 擷取例項。在一般使用 Jetpack Navigation 的 Compose 應用程式中,這個擁有者是目前的 Navigation 返回堆疊項目。也就是說,只要該目的地存在於返回堆疊中,ViewModel 就會保留在記憶體中。

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)
        // ...
    }
}

除了 Jetpack Navigation 之外,如果您還使用 Hilt,則可以如下所示使用 hiltNavGraphViewModels(graphId) API。

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)
        // ...
    }
}

其他資源

如要進一步瞭解 ViewModel 和範圍,請參閱下列其他資源:

說明文件

Views content