Descripción general de ViewModel   Parte de Android Jetpack.

Se diseñó la clase ViewModel a fin de almacenar y administrar datos relacionados con la IU de manera optimizada para los ciclos de vida. La clase ViewModel permite que se conserven los datos luego de cambios de configuración, como las rotaciones de pantallas.

El framework de Android administra el ciclo de vida de los controladores de IU, como las actividades y los fragmentos. El framework podría decidir destruir o recrear un controlador de IU como respuesta a acciones del usuario o eventos del dispositivo determinados que están completamente fuera de tu control.

Si el sistema destruye o recrea un controlador de IU, se perderán todos los datos relacionados con la IU que almacenes en el controlador. Por ejemplo, tu app podría incluir una lista de usuarios en una de sus actividades. Cuando se recrea la actividad para un cambio de configuración, la actividad nueva tiene que volver a recuperar la lista de usuarios. En el caso de los datos simples, la actividad puede usar el método onSaveInstanceState() y restablecer sus datos a partir del paquete en onCreate(), aunque este enfoque solo es apto para pequeñas cantidades de datos que se pueden serializar y deserializar, no para cantidades de datos posiblemente grandes, como una lista de usuarios o mapas de bits.

Otro problema es que los controladores de IU frecuentemente necesitan hacer llamadas asíncronas que podrían tardar en devolverse. El controlador de IU necesita administrar estas llamadas y garantizar que el sistema las borre después de su destrucción para evitar potenciales pérdidas de memoria. Esta administración requiere mucho mantenimiento y, en los casos en que se recrea el objeto para un cambio de configuración, es una pérdida de recursos, ya que el objeto quizás deba volver a emitir llamadas que ya hizo.

Los controladores de IU, como las actividades y los fragmentos, tienen como objetivo principal mostrar datos de IU, reaccionar a las acciones de los usuarios o administrar la comunicación del sistema operativo, como las solicitudes de permisos. Si se establece que los controladores de IU también sean responsables de cargar datos de una red o base de datos, la clase estará sobrecargada. Asignar demasiadas responsabilidades a los controladores de IU puede provocar que una sola clase trate de administrar todo el trabajo de una app por su cuenta, en lugar de delegar el trabajo a otras clases. Si asignas demasiadas responsabilidades a los controladores de IU de este modo, la prueba también se dificulta considerablemente.

Es más fácil y eficiente separar la propiedad de los datos de visualización de la lógica del controlador de IU.

Cómo implementar un ViewModel

Los componentes de arquitectura proporcionan una clase de ayuda de ViewModel para el controlador de IU que es responsable de preparar los datos de la IU. Durante los cambios de configuración, se retienen automáticamente los objetos ViewModel, de manera que los datos que contienen estén disponibles de inmediato para la siguiente instancia de actividad o fragmento. Por ejemplo, si necesitas mostrar una lista de usuarios en tu app, asegúrate de asignar la responsabilidad de adquirir y mantener la lista de usuarios a un ViewModel, en lugar de una actividad o un fragmento, como se muestra en el siguiente código de muestra:

Vistas

class MyViewModel : ViewModel() {
    private val users: MutableLiveData<List<User>> by lazy {
        MutableLiveData<List<User>>().also {
            loadUsers()
        }
    }

    fun getUsers(): LiveData<List<User>> {
        return users
    }

    private fun loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

Vistas

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<User>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

Luego, puedes acceder a la lista desde una actividad de la siguiente manera:

Vistas

class MyActivity : 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 MyViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val model: MyViewModel by viewModels()
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI
        })
    }
}

Vistas

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle 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.

        MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

Si se vuelve a crear la actividad, recibe la misma instancia de MyViewModel que creó la primera actividad. Cuando finaliza la actividad del propietario, el framework llama al método onCleared() de los objetos ViewModel para que borre los recursos.

Los objetos ViewModel están diseñados para sobrevivir a instancias específicas de vistas o LifecycleOwners. Este diseño también te permite escribir pruebas para abarcar un ViewModel con mayor facilidad, ya que no sabe acerca de los objetos de vista y Lifecycle. Los objetos ViewModel pueden contener LifecycleObservers, como objetos LiveData. Sin embargo, los objetos ViewModel no deben observar cambios en los elementos observables optimizados para ciclos de vida, como los objetos LiveData. Si el ViewModel necesita el contexto de Application, por ejemplo, para buscar un servicio del sistema, puede extender la clase AndroidViewModel y hacer que un constructor reciba la Application, ya que la clase Application extiende Context.

Cómo crear ViewModels con dependencias

Según las prácticas recomendadas para insertar dependencias, los ViewModels pueden tomar dependencias como parámetros en su constructor. En su mayoría, son de los tipos de capas de dominio o datos. Debido a que el framework proporciona los ViewModels, se requiere un mecanismo especial para crear instancias de ellos. Ese mecanismo es la interfaz ViewModelProvider.Factory. Solo las implementaciones de esta interfaz pueden crear instancias de ViewModels en el alcance correcto.

Si una clase ViewModel recibe dependencias en su constructor, proporciona una fábrica que implemente la interfaz ViewModelProvider.Factory. Anula la función create(Class<T>, CreationExtras) para proporcionar una instancia nueva del ViewModel.

CreationExtras te permite acceder a información relevante que te ayuda a crear una instancia de ViewModel. Esta es una lista de claves a las que se puede acceder desde los extras:

Clave Funcionalidad
ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY Esta Key proporciona acceso a la clave personalizada que pasaste a ViewModelProvider.get().
ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY Proporciona acceso a la instancia de la clase Application.
SavedStateHandleSupport.DEFAULT_ARGS_KEY Proporciona acceso al paquete de argumentos que debes usar para construir un SavedStateHandle.
SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEY Proporciona acceso al SavedStateRegistryOwner que se usa para construir el ViewModel.
SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEY Proporciona acceso al ViewModelStoreOwner que se usa para construir el ViewModel.

Para crear una instancia nueva de SavedStateHandle, usa la función CreationExtras.createSavedStateHandle().createSavedStateHandle()) y pásala al ViewModel.

A continuación, se muestra un ejemplo de a fin de proporcionar una instancia de un ViewModel que toma un repositorio definido para la clase Application y SavedStateHandle como dependencias:

Vistas

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.CreationExtras

class MyViewModel(
    private val myRepository: MyRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // ViewModel logic
    // ...

    // Define ViewModel factory in a companion object
    companion object {

        val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
            @Suppress("UNCHECKED_CAST")
            override fun <T : ViewModel> create(
                modelClass: Class<T>,
                extras: CreationExtras
            ): T {
                // Get the Application object from extras
                val application = checkNotNull(extras[APPLICATION_KEY])
                // Create a SavedStateHandle for this ViewModel from extras
                val savedStateHandle = extras.createSavedStateHandle()

                return MyViewModel(
                    (application as MyApplication).myRepository,
                    savedStateHandle
                ) as T
            }
        }
    }
}

Vistas

import static androidx.lifecycle.SavedStateHandleSupport.createSavedStateHandle;
import static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY;

import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.viewmodel.ViewModelInitializer;

public class MyViewModel extends ViewModel {

    public MyViewModel(
        MyRepository myRepository,
        SavedStateHandle savedStateHandle
    ) { /* Init ViewModel here */ }

    static final ViewModelInitializer<MyViewModel> initializer = new ViewModelInitializer<>(
        MyViewModel.class,
        creationExtras -> {
            MyApplication app = (MyApplication) creationExtras.get(APPLICATION_KEY);
            assert app != null;
            SavedStateHandle savedStateHandle = createSavedStateHandle(creationExtras);

            return new MyViewModel(app.getMyRepository(), savedStateHandle);
        }
    );
}

Luego, puedes usar esta fábrica cuando recuperas una instancia del ViewModel:

Vistas

import androidx.activity.viewModels

class MyActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels { MyViewModel.Factory }

    // Rest of Activity code
}

Vistas

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;

public class MyActivity extends AppCompatActivity {

    MyViewModel myViewModel = new ViewModelProvider(
        this,
        ViewModelProvider.Factory.from(MyViewModel.initializer)
    ).get(MyViewModel.class);

    // Rest of Activity code
}

Compose

import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun MyScreen(
    modifier: Modifier = Modifier,
    viewModel: MyViewModel = viewModel(factory = MyViewModel.Factory)
) {
    // ...
}

Como alternativa, puedes usar el DSL de fábrica de ViewModel para crear fábricas con una API de Kotlin más idiomática:

Vistas

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory

class MyViewModel(
    private val myRepository: MyRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    // ViewModel logic

    // Define ViewModel factory in a companion object
    companion object {
        val Factory: ViewModelProvider.Factory = viewModelFactory {
            initializer {
                val savedStateHandle = createSavedStateHandle()
                val myRepository = (this[APPLICATION_KEY] as MyApplication).myRepository
                MyViewModel(
                    myRepository = myRepository,
                    savedStateHandle = savedStateHandle
                )
            }
        }
    }
}

Fábricas para una versión de ViewModel anterior a la 2.5.0

Si usas una versión de ViewModel anterior a la 2.5.0, debes proporcionar fábricas de un subconjunto de clases que extiendan ViewModelProvider.Factory e implementar la función create(Class<T>). Según las dependencias que necesite ViewModel, se debe extender una clase diferente:

Si Application o SavedStateHandle no son necesarios, solo debes extenderlos desde ViewModelProvider.Factory.

En el siguiente ejemplo, se usa un AbstractSavedStateViewModelFactory para un ViewModel que toma un repositorio y un tipo SavedStateHandle como dependencia:

Vistas

class MyViewModel(
    private val myRepository: MyRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // ViewModel logic ...

    // Define ViewModel factory in a companion object
    companion object {
        fun provideFactory(
            myRepository: MyRepository,
            owner: SavedStateRegistryOwner,
            defaultArgs: Bundle? = null,
        ): AbstractSavedStateViewModelFactory =
            object : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
                @Suppress("UNCHECKED_CAST")
                override fun <T : ViewModel> create(
                    key: String,
                    modelClass: Class<T>,
                    handle: SavedStateHandle
                ): T {
                    return MyViewModel(myRepository, handle) as T
                }
            }
    }
}

Vistas

import androidx.annotation.NonNull;
import androidx.lifecycle.AbstractSavedStateViewModelFactory;
import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.ViewModel;

public class MyViewModel extends ViewModel {
    public MyViewModel(
        MyRepository myRepository,
        SavedStateHandle savedStateHandle
    ) { /* Init ViewModel here */ }
}

public class MyViewModelFactory extends AbstractSavedStateViewModelFactory {

    private final MyRepository myRepository;

    public MyViewModelFactory(
        MyRepository myRepository
    ) {
        this.myRepository = myRepository;
    }

    @SuppressWarnings("unchecked")
    @NonNull
    @Override
    protected <T extends ViewModel> T create(
        @NonNull String key, @NonNull Class<T> modelClass, @NonNull SavedStateHandle handle
    ) {
        return (T) new MyViewModel(myRepository, handle);
    }
}

Luego, puedes usar fábrica para recuperar tu ViewModel:

Vistas

import androidx.activity.viewModels

class MyActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels {
        MyViewModel.provideFactory((application as MyApplication).myRepository, this)
    }

    // Rest of Activity code
}

Vistas

public class MyActivity extends AppCompatActivity {

    MyViewModel myViewModel = new ViewModelProvider(
        this,
        new MyViewModelFactory(((MyApplication) getApplication()).getMyRepository())
    ).get(MyViewModel.class);

    // Rest of Activity code
}

Compose

import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun MyScreen(
    modifier: Modifier = Modifier,
    viewModel: MyViewModel = viewModel(
        factory = MyViewModel.provideFactory(
            (LocalContext.current.applicationContext as MyApplication).myRepository,
            owner = LocalSavedStateRegistryOwner.current
        )
    )
) {
    // ...
}

El ciclo de vida de un ViewModel

El alcance de los objetos ViewModel se determina según el Lifecycle que se pasa al ViewModelProvider cuando se recibe el ViewModel. El ViewModel permanece en la memoria hasta que el Lifecycle que determina su alcance desaparece de forma permanente. En el caso de una actividad, cuando termina; en el caso de un fragmento, cuando se desconecta.

La figura 1 muestra los estados del ciclo de vida de una actividad a medida que atraviesa una rotación y hasta que termina. La ilustración también muestra el ciclo de vida del ViewModel junto al de la actividad asociada. Este diagrama en particular muestra los estados de una actividad. Los mismos estados básicos se aplican al ciclo de vida de un fragmento.

Muestra el ciclo de vida de un ViewModel a medida que una actividad cambia de estado.

Por lo general, solicitas un ViewModel la primera vez que el sistema llama al método onCreate() del objeto de una actividad. El sistema puede llamar a onCreate() varias veces durante la vida de una actividad, como cuando rota la pantalla de un dispositivo. El ViewModel existe desde la primera vez que solicitas un ViewModel hasta que finaliza la actividad y se destruye.

Cómo compartir los datos entre fragmentos

Es muy común que dos o más fragmentos en una actividad necesiten comunicarse entre sí. Imagina un caso común de fragmentos de vista dividida (list-detail), en el que tienes un fragmento donde el usuario selecciona un elemento de una lista y otro fragmento que muestra el contenido del elemento seleccionado. Este caso nunca es banal, ya que ambos fragmentos necesitan definir una parte de la descripción de la interfaz y la actividad del propietario debe vincularlos. Además, los dos fragmentos deben manejar la situación en la que el otro fragmento todavía no se haya creado o no esté visible.

Se puede abordar este punto problemático común usando objetos ViewModel. Esos fragmentos pueden compartir un ViewModel mediante su alcance de actividad para administrar esta comunicación, como se indica en el siguiente código de muestra:

Vistas

class SharedViewModel : ViewModel() {
    val selected = MutableLiveData()

    fun select(item: Item) {
        selected.value = item
    }
}

class ListFragment : Fragment() {

    private lateinit var itemSelector: Selector

    // Use the 'by activityViewModels()' Kotlin property delegate
    // from the fragment-ktx artifact
    private val model: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        itemSelector.setOnClickListener { item ->
            // Update the UI
        }
    }
}

class DetailFragment : Fragment() {

    // Use the 'by activityViewModels()' Kotlin property delegate
    // from the fragment-ktx artifact
    private val model: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        model.selected.observe(viewLifecycleOwner, Observer { item ->
            // Update the UI
        })
    }
}

Vistas

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData getSelected() {
        return selected;
    }
}

public class ListFragment extends Fragment {
    private SharedViewModel model;

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        model.getSelected().observe(getViewLifecycleOwner(), item -> {
           // Update the UI.
        });
    }
}

Ten en cuenta que ambos fragmentos recuperan la actividad que los contiene. De esa manera, cuando cada fragmento obtiene el ViewModelProvider, reciben la misma instancia de SharedViewModel, cuyo alcance está determinado por esta actividad.

Este enfoque ofrece las siguientes ventajas:

  • La actividad no necesita hacer nada ni saber sobre esta comunicación.
  • Los fragmentos no necesitan saber acerca del otro, excepto por el contrato de SharedViewModel. Si uno de los fragmentos desaparece, el otro sigue funcionando como de costumbre.
  • Cada fragmento tiene su propio ciclo de vida y no se ve afectado por el ciclo de vida del otro. Si un fragmento reemplaza al otro, la IU continúa funcionando sin problemas.

Cómo reemplazar cargadores con ViewModel

Con frecuencia, se usan las clases de cargador como CursorLoader para mantener los datos en la IU de una app sincronizados con una base de datos. Puedes usar ViewModel, con algunas otras clases, para reemplazar el cargador. Usar un ViewModel te permite separar el controlador de IU de la operación de carga de datos. De esa forma, tienes menos referencias pesadas entre clases.

En un enfoque común con respecto al uso de cargadores, una app podría usar un CursorLoader para observar el contenido de una base de datos. Cuando un valor en la base de datos cambia, el cargador activa automáticamente una nueva carga de los datos y actualiza la IU.

Figura 2: carga de datos con cargadores

ViewModel funciona con Room y LiveData para reemplazar el cargador. El ViewModel garantiza que los datos sobrevivan al cambio de configuración del dispositivo. Room informa sobre tu LiveData cuando cambia la base de datos, y LiveData, a su vez, actualiza la IU con los datos revisados.

Figura 3: carga de datos con ViewModel

Cómo usar corrutinas con ViewModel

ViewModel incluye compatibilidad con corrutinas de Kotlin. Para obtener más información, consulta Cómo usar corrutinas de Kotlin con componentes de la arquitectura de Android.

Más información

A medida que los datos se hacen más complejos, quizás decidas tener una clase por separado solo para cargar los datos. El objetivo de ViewModel es encapsular los datos para un controlador de IU a fin de que estos sobrevivan a los cambios de configuración. Si quieres obtener más información para cargar, conservar y administrar datos durante cambios de configuración, consulta Cómo guardar estados de IU.

La Guía para la arquitectura de apps de Android sugiere crear una clase de repositorio para administrar estas funciones.

Recursos adicionales

Para obtener más información sobre la clase ViewModel, consulta los siguientes recursos.

Ejemplos

Codelabs

Blogs

Videos