DMarlon Studio

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.

Dart · StreamController y eventos en DMarlon Studio: el código del ejemplo y su salida en la terminal

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

  • StreamController permite emitir eventos cuando usted quiera.
  • add emite un valor, addError un error y close cierra el stream.
  • .broadcast() admite varios oyentes a la vez.
  • listen recibe cada evento, y además onError y onDone.
  • Exponga solo stream y 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.