Patrón Observer en Flutter

- 👤 Andrés Cruz

🇺🇸 In english

Patrón Observer en Flutter

Cuando empiezas a trabajar con Flutter, es normal sentir que los widgets viven en su propio mundo. Cada uno está metido en su rama del árbol y comunicarlos entre sí puede volverse un dolor de cabeza si no usas un buen esquema reactivo. Justo ahí es donde el Patrón Observer brilla: te permite reaccionar automáticamente a cambios sin escribir código repetido ni acoplar componentes entre sí.

En mi caso, la primera vez que usé Observer en Flutter fue para actualizar el “saldo disponible” en pantalla sin tener que mover medio árbol de widgets. Tenía que cambiar un valor y quería que todo lo que dependiera de él se refrescara sin que yo hiciera nada extra. Y ahí dije: “ok… Observer sí que tiene sentido”.

Este patrón permite que objetos relacionados se mantengan en sincronía y reaccionen automáticamente a los cambios en el objeto observado, lo cual es excelente en Flutter gracias a su componente reactivo; con esto se facilita la coordinación y la comunicación entre los componentes de un sistema.

Recuerda que el patrón que presentamos anteriormente nos permite crear una instancia única, el Singleton en Flutter.

Si hoy tuviera que explicarle a alguien cómo empezar con la reactividad en Flutter, empezaría por aquí.

¿Qué es el patrón Observer y por qué es importante en Flutter?

Explicación simple del patrón Observer

El patrón Observer establece una relación uno-a-muchos:

  • Hay un objeto principal (Observable o Subject).
  • Hay uno o muchos Observadores.

Cuando el Observable cambia, notifica a todos los Observadores. Ellos reaccionan, se actualizan, o cambian su estado según necesiten.

Lo que me gusta de este enfoque es que no importa si tienes 1 observador o 1000, todos escuchan lo mismo, todos reaccionan igual, y tú no te preocupas por quién es quién.

Relación uno-a-muchos y notificaciones automáticas

El Observable no sabe quién lo escucha, solo sabe que tiene una lista. La comunicación es de difusión (broadcast). Notifica, y listo.

Cómo encaja con el modelo reactivo de Flutter

Flutter es un framework reactivo. Los widgets se reconstruyen cuando hay cambios de estado. El patrón Observer se integra perfectamente porque la idea es justamente esa:

“cambié un dato → notifícalo → quienes dependan del dato se actualizan”.

Cuando yo entendí la naturaleza reactiva de Flutter, empecé a usar Observer para probar comportamientos antes de meterme en herramientas más grandes como Provider, Bloc o Riverpod.

El patrón Observer nos ayuda a suscribirnos a algunos eventos de una Clase y recibir notificaciones. Esto quiere decir que hay alguien que emite datos y otro que los va a escuchar. Quien emite los datos simplemente se encarga de enviarlos a quien está suscrito a esos datos y nada más. Las entidades que están escuchando se pueden quitar o poner, y para la otra entidad es totalmente transparente.

Beneficios
Acoplamiento abstracto entre Sujeto y Observador. Todo lo que un sujeto sabe es que tiene una lista de observadores, cada uno de los cuales se ajusta a la interfaz simple de la clase Observer abstracta. El sujeto no conoce la clase concreta de ningún observador. Así, el acoplamiento entre sujetos y observadores es abstracto y mínimo.
Soporte para la comunicación de difusión. A diferencia de una solicitud ordinaria, la notificación que envía un sujeto no necesita especificar su destinatario. La notificación se transmite automáticamente a todos los objetos interesados que se suscribieron a ella. Al sujeto no le importa cuántos objetos interesados existen; su única responsabilidad es notificar a sus observadores. Esto le da la libertad de agregar y eliminar observadores en cualquier momento. Depende del observador manejar o ignorar una notificación.

Inconvenientes

Actualizaciones inesperadas. Debido a que los observadores no tienen conocimiento de la presencia de los demás, pueden estar ciegos al costo final de cambiar de tema. Una operación aparentemente inocua sobre el tema puede provocar una cascada de actualizaciones en los observadores y sus objetos dependientes.

¿Cuándo usarlo el patrón de observer?

  • Cuando varias vistas dependen de un dato, podemos suscribirnos a los eventos de esos datos y, cuando cambia, actualizamos todas las vistas de forma reactiva.
  • Cuando una abstracción tiene dos aspectos, uno dependiente del otro. Encapsular estos aspectos en objetos separados le permite variarlos y reutilizarlos de forma independiente.
  • Cuando un cambio en un objeto requiere cambiar otros y no sabe cuántos objetos necesita cambiar.
  • Cuando un objeto debería poder notificar a otros objetos sin hacer suposiciones sobre quiénes son estos objetos. En otras palabras, no desea que estos objetos estén estrechamente acoplados.
  • Varios widgets necesitan escuchar un mismo dato.
  • Trabajas con modelos simples que cambian seguido (precio, saldo, progreso, notificaciones).
  • Quieres practicar diseño de software sin saltar directamente a librerías pesadas.

Señales de que necesitas Observer (y no otro patrón)

  • Si tienes dependencias múltiples de un mismo estado.
  • Si quieres acoplamiento mínimo entre componentes.
  • Si deseas una solución nativa en Dart sin librerías externas.

Limitaciones y malas prácticas frecuentes

  • Tener demasiados observadores puede generar cascadas de actualizaciones.
  • Mala gestión de suscripciones → fugas de memoria.
  • Usarlo para proyectos grandes sin arquitectura → caos.

Implementación del patrón Observer en Dart

Para nuestro ejemplo, vamos a empezar creando dos interfaces o Clases Abstractas en Dart, que nos ayudarán a delegar la comunicación entre Clases. El primero es el Observable y el otro es el Observador.

Vamos a construir la estructura clásica del patrón usando tu ejemplo real de AvailableBalanceObservable.

import 'package:design_patterns/design_patterns/observer/observable/observer.dart';

abstract class Observable {
  void addObserver(Observer observer) {}
  void removeObserver(Observer observer) {}
  void notifyObservers(double newValue) {}
}

En mis primeras implementaciones, me gustaba mantenerlo así de simple. Nada raro, nada complejo.

Creación de un Subject real: AvailableBalanceObservable

Ahora sí, tu caso típico: manejar el saldo disponible de un usuario.

abstract class Observer {
  void notifyChange(double newValue) {}
}

Ahora creamos una Clase AvailableBalanceObservable que se encarga de manejar los eventos de saldo disponible. En esta clase implementamos el Observable. Esto nos obliga a implementar los métodos addObserver, removeObserver y notificarObservers.

Para dar de alta a los observadores, debemos guardarlos en memoria, para ello creamos una variable _amountObserverList donde tenemos la lista de nuestros Observadores. Del mismo modo para eliminar un observador.

Cuando vamos a generar un cambio y vamos a avisar a todos, lo que hacemos es que avisamos a cada uno de los Observadores que tenemos.

Ahora creamos la función para cambiar el saldo disponible. Cuando actualizamos la cantidad, también notificamos a los Observadores.

import 'package:design_patterns/design_patterns/observer/observable/observable.dart';
import 'package:design_patterns/design_patterns/observer/observable/observer.dart';

class AvailableBalanceObservable implements Observable {
  final List<Observer> _amountObserverList = [];
  double _amount = 0.0;

  void changeAmount(double newValue) {
    _amount = newValue;
    notifyObservers(_amount);
  }

  @override
  void addObserver(Observer observer) {
    _amountObserverList.add(observer);
  }

  @override
  void removeObserver(Observer observer) {
    _amountObserverList.remove(observer);
  }

  @override
  void notifyObservers(double newValue) {
    for (var observer in _amountObserverList) {
      observer.notifyChange(newValue);
    }
  }
}

Aquí se ve clarísimo cómo funciona: actualizas, notificas y cada observador hace lo suyo, la idea en general, es llamar al notifyChange o ir agregando/removiendo observadores para aplicar los cambios.

Cómo se suscriben y notifican los observadores

Un observador podría verse así:

class BalanceWidgetObserver implements Observer {
 @override
 void notifyChange(double newValue) {
   print("Nuevo saldo: $newValue");
 }
}

Ejemplo completo con actualización de valores

void main() {
 final balance = AvailableBalanceObservable();
 final widgetObserver = BalanceWidgetObserver();
 balance.addObserver(widgetObserver);
 balance.changeAmount(150.50);
 balance.changeAmount(200.00);
}

Entonces, se divide en dos apartados, por una parte, implementar la lógica para el observador y luego implementar los escucha u obseradores como mostramos antes.

Observer vs otras alternativas en Flutter

Observer vs Provider

  • Provider está optimizado para widgets y reconstrucciones específicas.
  • Observer es más liviano, crudo y flexible.
  • Para proyectos pequeños: Observer.
  • Para apps medianas/grandes: Provider o similares.

Observer vs Streams

  • Streams sirven para flujos de datos asíncronos.
  • Observer es sincrónico, inmediato y más predictivo.
  • Si vas a recibir datos en tiempo real (chat, sockets), Streams te dan más control.

Observer vs ChangeNotifier / ValueNotifier

  • ChangeNotifier es básicamente una evolución del Observer adaptada al mundo Flutter.
  • ValueNotifier es ideal para valores simples.
  • Observer sigue siendo útil para aprender el diseño subyacente.

Ventajas y desventajas del patrón Observer

Beneficios

  • Acoplamiento mínimo.
  • Comunicación uno-a-muchos inmediata.
  • Ideal para código limpio y extensible.
  • Reduce lógica repetitiva.

Inconvenientes

  • Actualizaciones inesperadas si no controlas bien quién escucha.
  • Puede crecer en complejidad si no organizas tus observadores.
  • No es la mejor opción para aplicaciones grandes.

Buenas prácticas para implementar Observer en Flutter

  • Gestión de memoria y evitar fugas
    • Siempre elimina observadores cuando el widget se destruye. Un dispose() bien usado te ahorra problemas.
  • Organización del código para escalar
    • Separa el Observable del código UI. Mantén tu Subject en modelos o controladores.
  • Ejemplos de mejoras en rendimiento
    • Evitar anidar notificaciones.
  • Hacer copias de la lista antes de iterar cuando hay muchas suscripciones.
    • Notificar solo cuando el valor realmente cambió.

Preguntas frecuentes sobre el patrón Observer en Flutter

  • ¿Es una buena opción para apps grandes?
    • No para manejar todo el estado, pero sí para piezas aisladas que cambian constantemente.
  • ¿Puedo mezclarlo con otros gestores de estado?
    • Sí, sin problema. Puedes usar Observer para casos específicos y Provider/Bloc para el resto.
  • ¿Cuándo evitar Observer?
    • Cuando tu aplicación necesita una arquitectura mantenible a largo plazo, preferiblemente con herramientas modernas.

Conclusión

El patrón Observer sigue vivo y tiene todo el sentido del mundo dentro de Flutter, sobre todo cuando empiezas a comprender la naturaleza reactiva del framework. Para mí fue una herramienta clave al inicio: simple, clara y perfecta para entender cómo se comunican los cambios dentro de una app.

Si hoy tuviera que explicarle a alguien cómo empezar con la reactividad en Flutter, empezaría por aquí.

El siguiente paso, aprende a usar ha heredar un StatefulWidget de Flutter, muy útil en la modulaoización de nuestra app.

Acepto recibir anuncios de interes sobre este Blog.

El patron Observer, permite definir entidades que envía eventos y hay otra entidad que se suscribe a esos eventos, veamos como usarlos en Flutter, veremos el concepto y ejemplos de uso y recomendaciones.

| 👤 Andrés Cruz

🇺🇸 In english