Lección 16 · 16 de 20
StreamController y eventos · Dart
Emita sus propios eventos en Dart con StreamController y escúchelos con listen, onError y onDone, con un carrito de la compra de ejemplo.

Cuando los eventos no salen de un bucle sino de acciones sueltas (un toque en un botón, un mensaje que llega), se usa un `StreamController`. Tiene dos caras: una para meter eventos (add, addError, close) y otra, stream, para escucharlos. Cart guarda el controlador en un campo privado y solo expone events, de modo que desde fuera se puede escuchar pero no emitir.
add actualiza las cantidades y emite un texto con el total de artículos; remove emite un evento normal si el artículo estaba y, si no, un error con addError. Un controlador normal admite un único oyente; creado con `.broadcast()`, admite cualquier número, como aquí.
`listen` suscribe una función que recibe cada evento y admite dos más: onError para los errores y onDone para cuando el stream se cierra. El segundo oyente, la insignia del carrito, filtra con where y solo atiende a los añadidos. También lleva onError, aunque esté vacío: en un stream broadcast, un error que llega a un oyente sin onError se considera no capturado y detiene el programa.
Los eventos se entregan de forma asíncrona, pero en el orden en que se emitieron y a los oyentes en el orden en que se suscribieron, así que la salida es la misma en cada ejecución. close() envía la señal de fin a todos y devuelve una Future que main espera; por eso cart closed es lo último. Este patrón, una clase que expone un stream de cambios, es la base de arquitecturas como BLoC, muy usadas en Flutter.
Código · main.dart
import 'dart:async';class Cart { final Map<String, int> _items = {}; // broadcast: any number of listeners can subscribe. final _events = StreamController<String>.broadcast(); Stream<String> get events => _events.stream; void add(String item) { _items.update(item, (qty) => qty + 1, ifAbsent: () => 1); _events.add('added $item, total ${_items.values.fold(0, (a, b) => a + b)}'); } void remove(String item) => _items.remove(item) == null ? _events.addError(StateError('$item is not in the cart')) : _events.add('removed $item'); Future<void> close() => _events.close(); // sends "done" to every listener}Future<void> main() async { final cart = Cart(); cart.events.listen((e) => print('log: $e'), onError: (Object e) => print('log: ERROR ${(e as StateError).message}'), onDone: () => print('log: cart closed')); cart.events.where((e) => e.startsWith('added')).listen((e) => print('badge: $e'), onError: (_) {}); cart..add('Coffee beans')..add('Milk')..remove('Sugar')..remove('Milk') ..add('Coffee beans'); await cart.close();}Salida · dart run main.dart
log: added Coffee beans, total 1 badge: added Coffee beans, total 1 log: added Milk, total 2 badge: added Milk, total 2 log: ERROR Sugar is not in the cart log: removed Milk log: added Coffee beans, total 2 badge: added Coffee beans, total 2 log: cart closed
Para recordar
StreamControllerpermite emitir eventos cuando usted quiera.addemite un valor,addErrorun error yclosecierra el stream..broadcast()admite varios oyentes a la vez.listenrecibe cada evento, y ademásonErroryonDone.- Exponga solo
streamy guarde el controlador como privado.
Ejercicio
Añada un método clear() que vacíe el carrito y emita cleared, y un tercer oyente que cuente los eventos recibidos y escriba el total en onDone.