Noticia Comunicación limpia entre Fragments usando un Shared ViewModel

Comunicación limpia entre Fragments usando un Shared ViewModel


Cuando nos metemos en el mundo del desarrollo de Android, uno de los quebraderos de cabeza más comunes es cómo hacer que dos pantallas, o fragmentos, se hablen entre sí sin que el código se convierta en un caos. Para que una aplicación sea escalable, lo ideal es que cada fragmento sea un componente totalmente independiente, con su propio diseño y comportamiento, evitando que dependan directamente unos de otros o de la actividad que los aloja.

Si queremos que la experiencia del usuario sea fluida, necesitamos canales de comunicación eficaces para reaccionar a eventos o compartir estados. Dependiendo de lo que busquemos, Android nos ofrece dos caminos principales: el uso de un ViewModel compartido para datos persistentes y la API de resultados de fragmentos para intercambios de información rápidos y sencillos.

Dominando el Shared ViewModel​


El ViewModel es la herramienta estrella cuando necesitamos que varios fragmentos o la actividad anfitriona compartan la misma información. Básicamente, estos objetos se encargan de almacenar y gestionar los datos de la interfaz, asegurando que la información no se pierda aunque el dispositivo rote o cambie su configuración.

Para lograr que dos fragmentos utilicen la misma instancia, el truco está en definir el alcance del ViewModelProvider. Si configuramos la actividad como el dueño del alcance, ambos fragmentos recibirán el mismo objeto. Esto es fundamental porque, si por error usamos el fragmento como alcance, cada uno tendría su propia copia de los datos y la comunicación simplemente no funcionaría.

Es importante tener en cuenta que un ViewModel compartido en una arquitectura de actividad única actúa prácticamente como un singleton en memoria. Los datos persistirán hasta que la actividad sea destruida definitivamente, por lo que debemos gestionar bien el ciclo de vida de las Activity para no consumir recursos innecesariamente.

Implementación Práctica y Flujo de Datos​


Imagina que estamos creando una app de pedidos. Necesitamos que el usuario elija la cantidad de productos en una pantalla, el sabor en otra y la fecha de entrega en una tercera. Para esto, creamos una clase OrderViewModel que extienda de ViewModel, donde guardamos las variables necesarias como el precio, la cantidad y la fecha.

Para mantener el código limpio y evitar que cualquier clase externa modifique los datos a su antojo, aplicamos una buena práctica: definimos propiedades mutables privadas con guion bajo (como _quantity) y exponemos una versión inmutable pública mediante LiveData. De este modo, solo el ViewModel puede cambiar el valor, pero cualquier fragmento puede observar el cambio.

Sincronización de la Interfaz con LiveData y Data Binding​


Para que la pantalla se actualice sola sin tener que escribir código repetitivo, entra en juego el Data Binding. Al vincular la variable del ViewModel directamente en el archivo XML, podemos hacer que elementos como los RadioButtons se marquen automáticamente si el valor coincide con el guardado en el modelo.

Un punto crítico aquí es la configuración del LifecycleOwner. Para que los observables de LiveData funcionen y la IU se refresque en tiempo real, debemos asignar binding.lifecycleOwner = viewLifecycleOwner. Sin este paso, aunque los datos cambien en el fondo, el usuario seguirá viendo la información antigua en su pantalla.

Cuando necesitamos procesar datos antes de mostrarlos, como convertir un número decimal en una moneda local, utilizamos Transformations.map(). Esta función nos permite transformar un LiveData<Double> en un LiveData<String> formateado, haciendo que la lógica de presentación quede separada de la lógica de negocio.

La API de Resultados de Fragmentos​


Comunicación limpia entre Fragments usando un Shared ViewModel


No siempre necesitamos un ViewModel complejo. Para casos donde solo queremos pasar un dato puntual (como un código QR escaneado) de vuelta a una pantalla anterior, la API de FragmentResult es la opción más ligera. Esta API utiliza el FragmentManager como un almacén central de resultados.

El proceso es sencillo: el fragmento que espera el dato configura un oyente con setFragmentResultListener() usando una clave específica. Mientras tanto, el fragmento que genera la información utiliza setFragmentResult() con esa misma clave. El resultado se entrega en cuanto el fragmento receptor alcanza el estado STARTED.

Si trabajamos con fragmentos secundarios, la clave es usar getChildFragmentManager() en el fragmento padre para escuchar los resultados. Esto mantiene la jerarquía organizada y evita que los datos se dispersen por toda la actividad, permitiendo una comunicación mucho más quirúrgica y eficiente.

Alternativas y enfoques tradicionales​


Aunque el ViewModel es la norma actual, existen métodos más antiguos que algunos desarrolladores siguen usando, como la implementación de interfaces personalizadas. En este esquema, el fragmento define una interfaz que la actividad debe implementar, actuando esta última como intermediaria entre dos fragmentos.

Otra técnica rudimentaria consiste en acceder a las vistas de un fragmento desde otro mediante el método getActivity().findViewById(). Sin embargo, este enfoque es altamente desaconsejado en aplicaciones modernas, ya que rompe totalmente la independencia de los fragmentos y provoca errores fatales si el fragmento objetivo no está visible o ha sido destruido.

La arquitectura moderna de Android prioriza que los componentes no se conozcan entre sí. Al delegar la gestión de los datos a un almacén externo como el ViewModel, logramos que el código sea más fácil de testear, mantener y escalar, evitando los típicos fallos de punteros nulos al intentar acceder a vistas que ya no existen en la jerarquía.

La clave para una navegación robusta reside en elegir la herramienta adecuada: el ViewModel compartido para estados complejos y persistentes, y la API de Fragment Result para respuestas rápidas y efímeras, asegurando siempre que el ciclo de vida de los componentes sea respetado para evitar fugas de memoria y errores de ejecución.

Continúar leyendo...