Descripción general de ViewModel Parte de Android Jetpack

La clase ViewModel está diseñada para almacenar y administrar datos relacionados con la IU de manera optimizada para los ciclos de vida. La clase ViewModel permite que los datos sobrevivan a cambios de configuración, como las rotaciones de pantallas.

El marco de trabajo de Android administra los ciclos de vida de los controladores de IU, como las actividades y los fragmentos. El marco de trabajo 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 posibles grandes cantidades de datos, como una lista de usuarios o mapas de bits.

Otro problema es que, con frecuencia, los controladores de IU 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, será mucho más difícil realizar las pruebas.

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 la arquitectura brindan una clase de ayuda de ViewModel para el controlador de IU que es responsable de preparar los datos para la IU. Los objetos ViewModel se retienen automáticamente durante los cambios de configuración, de manera que los datos que conservan 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 ejemplo:

Kotlin

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

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

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

Java

    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:

Kotlin

    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.

            val model = ViewModelProviders.of(this)[MyViewModel::class.java]
            model.getUsers().observe(this, Observer<List<User>>{ users ->
                // update UI
            })
        }
    }
    

Java

    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 = ViewModelProviders.of(this).get(MyViewModel.class);
            model.getUsers().observe(this, users -> {
                // update UI
            });
        }
    }
    

Si se recrea la actividad, recibe la misma instancia MyViewModel que creó la primera actividad. Cuando la actividad del propietario se termina, el marco de trabajo llama al método onCleared() del objeto 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 la vista y los objetos Lifecycle. Los objetos ViewModel pueden contener LifecycleObservers, como objetos LiveData. Sin embargo, los objetos ViewModel nunca deben observar cambios de 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 lograr que un constructor reciba la Application, ya que la clase Application extiende Context.

El ciclo de vida de un ViewModel

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

En la figura 1, se muestran 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 la vida útil del ViewModel junto al ciclo de vida de la actividad asociado. 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 útil de una actividad, como cuando rota la pantalla de un dispositivo. El ViewModel existe a partir de la primera solicitud de un ViewModel hasta que la actividad se termina y se destruye.

Cómo compartir los datos entre fragmentos

Es muy común que dos o más fragmentos de una actividad necesiten comunicarse entre sí. Imagina un caso común de fragmentos con detalles maestros, 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 administrar una situación en la que el otro fragmento todavía no se creó o no está visible.

Para solucionar esta dificultad habitual, puedes usar objetos ViewModel. Estos fragmentos pueden compartir un ViewModel mediante su alcance de actividad para administrar esta comunicación, como se muestra en el siguiente código de ejemplo:

Kotlin

    class SharedViewModel : ViewModel() {
        val selected = MutableLiveData<Item>()

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

    class MasterFragment : Fragment() {

        private lateinit var itemSelector: Selector

        private lateinit var model: SharedViewModel

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            model = activity?.run {
                ViewModelProviders.of(this)[SharedViewModel::class.java]
            } ?: throw Exception("Invalid Activity")
            itemSelector.setOnClickListener { item ->
                // Update the UI
            }
        }
    }

    class DetailFragment : Fragment() {

        private lateinit var model: SharedViewModel

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            model = activity?.run {
                ViewModelProviders.of(this)[SharedViewModel::class.java]
            } ?: throw Exception("Invalid Activity")
            model.selected.observe(this, Observer<Item> { item ->
                // Update the UI
            })
        }
    }
    

Java

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

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

        public LiveData<Item> getSelected() {
            return selected;
        }
    }

    public class MasterFragment extends Fragment {
        private SharedViewModel model;
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
            itemSelector.setOnClickListener(item -> {
                model.select(item);
            });
        }
    }

    public class DetailFragment extends Fragment {
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
            model.getSelected().observe(this, { 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 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 de manera habitual.
  • 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

Las clases de cargador, como CursorLoader, se usan con frecuencia 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 al cargador. El uso de un ViewModel te permite separar tu controlador de IU de la operación de carga de datos. Así, consigues tener menos referencias pesadas entre las 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: Cómo cargar datos con cargadores

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

Figura 3: Cómo cargar los datos con ViewModel

Cómo usar corrutinas con ViewModel

ViewModel incluye admite 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 los datos sobrevivan a los cambios de configuración. Para obtener más información sobre cómo cargar, conservar y administrar los datos durante los 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