Stream Controllers
Parte de la sección Programación Orientada a Objetos del Journey de Dart de Coddy — lección 78 de 110.
Si bien los generadores async* son excelentes para crear streams con datos predefinidos, a veces necesitas más control, como añadir datos a un stream desde diferentes partes de tu código. Ahí es donde entra en juego StreamController.
Un StreamController actúa como un gestor para un stream. Te proporciona un "sink" para añadir datos y un "stream" al que los suscriptores pueden suscribirse:
import 'dart:async';
void main() {
var controller = StreamController<String>();
// Escuchar el stream
controller.stream.listen((data) {
print('Received: $data');
});
// Agregar datos a través del sink
controller.add('Hello');
controller.add('World');
controller.close(); // Cerrar siempre al terminar
}El método add() inserta nuevos valores en el flujo, y cualquier escucha los recibe inmediatamente. Siempre debes llamar a close() cuando hayas terminado para liberar recursos.
Por defecto, un stream solo puede tener un listener. Para múltiples listeners, usa un broadcast controller:
import 'dart:async';
void main() {
var controller = StreamController<int>.broadcast();
controller.stream.listen((n) => print('Listener 1: $n'));
controller.stream.listen((n) => print('Listener 2: $n'));
controller.add(42);
controller.close();
}Los StreamControllers son esenciales en la POO para construir clases orientadas a eventos, como notificar a múltiples partes de su aplicación cuando los datos cambian, manejar acciones del usuario o gestionar actualizaciones en tiempo real dentro de sus objetos.
Desafío
Fácil¡Vamos a construir un sistema de centro de mensajes que permita a múltiples suscriptores recibir mensajes en tiempo real! Utilizarás un StreamController para gestionar el flujo de mensajes y demostrar cómo los flujos de difusión (broadcast streams) permiten múltiples oyentes.
Organizarás tu código en dos archivos:
message_hub.dart: Crea una claseMessageHubque gestione la distribución de mensajes utilizando un StreamController. Tu centro debe:- Usar un
StreamController<String>de tipo broadcast internamente para que múltiples oyentes puedan suscribirse - Proporcionar un getter llamado
messagesque devuelva el flujo para que los oyentes se suscriban - Tener un método
broadcast(String message)que añada un mensaje al flujo - Tener un método
shutdown()que cierre el controlador
- Usar un
main.dart: Importa tu centro de mensajes y configura un escenario con múltiples suscriptores escuchando el mismo flujo:- Crea una instancia de
MessageHub - Añade un primer oyente que imprima los mensajes en el formato:
Subscriber A: [message] - Añade un segundo oyente que imprima los mensajes en el formato:
Subscriber B: [message] - Transmite tres mensajes:
Hello everyone,Welcome to the hub, yGoodbye - Cierra el centro de mensajes
- Crea una instancia de
Recuerda importar dart:async en tu archivo del centro de mensajes para acceder a StreamController. Observa cómo ambos suscriptores reciben cada mensaje que se transmite: ¡ese es el poder de los flujos de difusión!
Salida esperada:
Subscriber A: Hello everyone
Subscriber B: Hello everyone
Subscriber A: Welcome to the hub
Subscriber B: Welcome to the hub
Subscriber A: Goodbye
Subscriber B: GoodbyeHoja de referencia
Un StreamController gestiona un stream, proporcionando control sobre cuándo y cómo se añaden los datos. Te ofrece un "sink" para añadir datos y un "stream" al que los oyentes pueden suscribirse.
Uso básico:
import 'dart:async';
var controller = StreamController<String>();
// Escuchar el stream
controller.stream.listen((data) {
print('Received: $data');
});
// Añadir datos a través del sink
controller.add('Hello');
controller.add('World');
controller.close(); // Cerrar siempre al terminar
El método add() envía nuevos valores al stream. Los oyentes los reciben inmediatamente. Llama siempre a close() cuando hayas terminado para liberar recursos.
Por defecto, un stream solo puede tener un oyente. Para múltiples oyentes, utiliza un controlador de difusión (broadcast controller):
var controller = StreamController<int>.broadcast();
controller.stream.listen((n) => print('Listener 1: $n'));
controller.stream.listen((n) => print('Listener 2: $n'));
controller.add(42);
controller.close();
Los StreamControllers son esenciales para construir clases orientadas a eventos, notificar a múltiples partes de tu aplicación cuando los datos cambian, manejar acciones del usuario o gestionar actualizaciones en tiempo real.
Pruébalo tú mismo
import 'message_hub.dart';
void main() {
// TODO: Crea una instancia de MessageHub
// TODO: Añade el primer listener que imprima: Subscriber A: [message]
// TODO: Añade el segundo listener que imprima: Subscriber B: [message]
// TODO: Transmite tres mensajes:
// - "Hello everyone"
// - "Welcome to the hub"
// - "Goodbye"
// TODO: Cierra el hub
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO
Archivos externosLibrerías e importacionesIntroducción a la POOClases vs. ObjetosLa palabra clave thisMétodosVariables de instanciaConceptos básicos de constructoresResumen - Calculadora sencilla4Seguridad de nulos
Introducción a la seguridad de nulosNulables vs No nulablesLos operadores ? y !Palabra clave Late y Null SafetyOperadores Null-awareNull Safety en clasesResumen - Sistema de perfil de usuario7Clases abstractas e interfaces
Clases abstractasMétodos abstractosInterfaces en DartInterfaces implícitasImplementar vs. ExtenderInterfaces múltiplesRepaso - Calculadora de figuras10Colecciones y Genéricos
Visión general de List, Set y MapColecciones con seguridad de tiposClases genéricasMétodos genéricosRestricciones genéricasIterable e IteratorResumen - Almacenamiento genérico2Constructores en Dart
Constructor por defectoConstructores nombradosListas de inicializaciónConstructores constantesConstructores de fábricaConstructores de redireccionamientoResumen - Constructor de formas5Encapsulamiento
Miembros públicos vs. privadosLa convención del prefijo _Privacidad a nivel de libreríaProfundidad en Getters y SettersOcultamiento de informaciónResumen - Registros de estudiantes8Mixins
Introducción a los MixinsCreación de MixinsUso de múltiples MixinsPalabra clave on en MixinsMixin vs HerenciaMixin vs InterfazResumen - Sistema Animal11Métodos especiales
Sobrescritura de toString()Sobrescritura de hashCode y ==Interfaz ComparableMétodo call()Sobrescritura de noSuchMethodResumen - Colección personalizada14Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy3Propiedades de clase
Miembros de instancia vs. estáticosCampos Final y ConstVariables LateMétodos y campos estáticosGetters y SettersResumen - Gestor de cuentas bancarias6Herencia
Herencia básicaLa palabra clave superSobrescritura de métodosLa anotación @overrideLa palabra clave final en clasesConstructores y herenciaResumen - Jerarquía de empleados9Polimorfismo
Fundamentos del polimorfismoPolimorfismo mediante interfacesComprobación de tipos en tiempo de ejecuciónLos operadores is y asPalabra clave covariantResumen - Procesador de pagos12POO Asíncrona
Futures y async/awaitFundamentos de StreamsStream ControllersConstructores asíncronosAsincronía en métodos de claseRepaso - Data Fetcher15Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository