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

Kotlin

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

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

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 (master-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:

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