# Streams en Dart
Un **Stream** es una secuencia de eventos asíncronos. Es como una tubería (pipe) por donde fluyen datos a lo largo del tiempo.
Si un `Future` representa un valor que estará listo en el futuro (una caja que llegará por correo), un `Stream` representa una serie de valores (una cinta transportadora).
## Tipos de Streams
### 1. Single Subscription (Suscripción Única)
- Solo permite un oyente (listener).
- Si intentas escuchar dos veces, lanza error.
- Común para: Lectura de archivos, peticiones HTTP (respuesta por chunks).
### 2. Broadcast (Difusión)
- Permite múltiples oyentes.
- Común para: Eventos de UI, cambios de estado, WebSockets.
## Creando Streams
### Stream.fromIterable
```dart
Stream countStream(int max) async* {
for (int i = 1; i <= max; i++) {
yield i; // Emite un valor
await Future.delayed(Duration(seconds: 1));
}
}
void main() async {
// Usando await for (bucle asíncrono)
await for (final value in countStream(3)) {
print(value);
}
}
```
### StreamController
Para control manual total.
```dart
import 'dart:async';
void main() {
final controller = StreamController();
// Escuchar
controller.stream.listen(
(data) => print('Recibido: $data'),
onError: (err) => print('Error: $err'),
onDone: () => print('Stream cerrado'),
);
// Emitir eventos
controller.add('Hola');
controller.add('Mundo');
controller.addError('Algo salió mal');
// Cerrar
controller.close();
}
```
## Transformando Streams
Los Streams tienen métodos poderosos similares a las Listas (`map`, `where`, etc.).
```dart
Stream numbers = Stream.fromIterable([1, 2, 3, 4, 5]);
void main() {
numbers
.where((n) => n % 2 == 0) // Filtrar pares: 2, 4
.map((n) => n * 10) // Multiplicar: 20, 40
.listen(print);
}
```
## StreamBuilder en Flutter
Es el widget fundamental para consumir Streams en la UI.
```dart
Stream _counterStream() async* {
while (true) {
await Future.delayed(Duration(seconds: 1));
yield DateTime.now().second;
}
}
class ClockWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _counterStream(),
builder: (context, snapshot) {
if (snapshot.hasError) return Text('Error');
if (!snapshot.hasData) return CircularProgressIndicator();
return Text('Segundos: ${snapshot.data}');
},
);
}
}
```
## Manejo de Errores
```dart
stream.listen(
(data) { ... },
onError: (error) { print('Manejando error: $error'); },
cancelOnError: false, // Seguir escuchando después de un error
);
```
## RxDart (Reactive Extensions)
Aunque Dart tiene streams potentes, el paquete `rxdart` añade operadores avanzados muy útiles para arquitecturas como BLoC.
- `debounceTime`: Esperar a que el usuario deje de escribir.
- `combineLatest`: Combinar varios streams (ej. email y password válidos).
- `switchMap`: Cancelar peticiones anteriores si llega una nueva.
## Conclusión
- Usa **Streams** para datos que llegan en múltiples momentos (eventos, sockets, timers).
- Usa **StreamController** para crear tus propios streams.
- Usa **StreamBuilder** para actualizar la UI reactivamente.
- Recuerda siempre cancelar las suscripciones (`subscription.cancel()`) si no usas StreamBuilder, para evitar fugas de memoria.
Este apartado profundiza en los conceptos clave, proporcionando ejemplos prácticos y mejores prácticas para su aplicación en proyectos reales.
Al comprender estos detalles, podrás diseñar soluciones más robustas, mantenibles y escalables en tus aplicaciones Flutter y Dart.