Únete a ⁠ #Android11: The Beta Launch Show el 3 de junio.

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 los datos sobrevivan a cambios de configuración, como las rotaciones de pantallas.

El framework 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 mostrarse. 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 un desperdicio de recursos, ya que el objeto puede tener que 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 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 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.

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

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 = 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 la vista y los objetos Lifecycle. Los objetos ViewModel pueden contener LifecycleObservers, como objetos LiveData. Sin embargo, los objetos ViewModel no deben observar cambios en los elementos optimizados para ciclos de vida, como los objetos LiveData. Si 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 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 permanentemente. En el caso de una actividad, cuando termina; en el caso de un fragmento, cuando se separa.

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 el ciclo de vida del ViewModel junto al ciclo de vida de la actividad asociado. Este diagrama en particular muestra los estados de una actividad. Se aplican los mismos estados básicos 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 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.

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

        // 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> { 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 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 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 desaparece uno de los fragmentos, 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

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 cambia un valor de a base de datos, 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 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: Cómo cargar 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 vuelvan más complejos, te recomendamos que implementes 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 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