APIs de ViewModel Scoping   Parte de Android Jetpack.

El alcance es clave para usar ViewModels de manera eficaz. El alcance de cada ViewModel se define en un objeto que implementa la interfaz ViewModelStoreOwner. Hay varias APIs que te permiten administrar el alcance de tus ViewModels con mayor facilidad. En este documento, se describen algunas de las técnicas clave que debes conocer.

El método ViewModelProvider.get() te permite obtener una instancia de un ViewModel cuyo alcance sea cualquier ViewModelStoreOwner. Para los usuarios de Kotlin, hay diferentes funciones de extensión disponibles con los casos de uso más comunes. Todas las implementaciones de funciones de extensión de Kotlin usan la API de ViewModelProvider de forma interna.

ViewModels con alcance para el ViewModelStoreOwner más cercano

Puedes definir el alcance de un ViewModel según un elemento componible, una Activity o un destino de un gráfico de Navigation. La función viewModel() en Compose te permite obtener una instancia del ViewModel con el alcance más cercano a 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()
) { /* ... */ }

ViewModels con alcance para cualquier ViewModelStoreOwner

La función viewModel() toma un parámetro viewModelStoreOwner opcional que puedes usar para especificar el elemento ViewModelStoreOwner en el que se define el alcance de la instancia del 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)
) {
    /* ... */
}

ViewModels con alcance para un elemento componible

Puedes usar rememberViewModelStoreOwner() para definir el alcance de un ViewModel directamente en el sitio de llamada de un elemento componible. Esto es especialmente útil para los componentes de la IU que se agregan o quitan de forma dinámica de la pantalla según el estado, como los elementos de una página o una lista diferida. Cuando el elemento componible que posee el ViewModelStoreOwner sale de la composición, se borra el ViewModelStore asociado y se destruye el ViewModel.

Usa rememberViewModelStoreOwner() para crear un almacén optimizado para el ciclo de vida que sobreviva a los cambios de configuración.

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

Para implementaciones más complejas, como un HorizontalPager o casos que requieren varios permisos independientes, usa rememberViewModelStoreProvider(). Esto te permite generar instancias ViewModelStoreOwner distintas para diferentes claves (como índices de página). De esta manera, cada página mantiene su propio estado de ViewModel independiente.

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

ViewModels con alcance para el gráfico de navegación

Los gráficos de navegación también son propietarios de ViewModelStore. Si usas Navigation Compose, puedes obtener una instancia de un ViewModel con alcance para un gráfico de navegación con la función getBackStackEntry().

viewModel() recupera la instancia del ViewModelStoreOwner más cercano que proporciona el LocalViewModelStoreOwner CompositionLocal. En una aplicación de Compose típica que usa Jetpack Navigation, este propietario es la entrada actual de la pila de actividades de Navigation. Esto significa que el ViewModel permanece en la memoria mientras ese destino esté presente en la pila de actividades.

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

Si usas Hilt además de Jetpack Navigation, puedes usar la API de hiltNavGraphViewModels(graphId) de la siguiente manera.

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

Recursos adicionales

Para obtener más información sobre los ViewModels y el alcance, consulta los siguientes recursos adicionales:

Documentación

Mira contenido