Introducción a la programación reactiva en Flutter. Streams

Cada día está más de moda, o es más usual crear desarrollos basados en los llamados patrones reactivos. Este tipo de patrones se basan en la relación que se mantiene entre un Publisher y Subscriber

Básicamente, y hablando de forma muy generalizada, esto consiste en que tengo algo que emite eventos/notificaciones/datos y tengo  un suscriptor de esos eventos/notificaciones que las recibe en el momento de producirse y actúa en consecuencia.

Todo este trasiego de información se realiza de forma asíncrona y en la mayoría de los lenguajes existe algún framework que nos posibilita este tipo de arquitecturas, o es el propio lenguaje el que lo trae de serie, como es el caso de Swift con el recientemente framework lanzado de nombre Combine.

Rxjava, RxSwift, RxDart, React Native, todos son librerías o frameworks que beben de esta arquitectura.

En Dart, y por ende en Flutter, existen mecanismos para trabajar con este tipo de arquitecturas, y por suerte están disponibles tanto en forma de librerías de terceros, RxDart, o nativas del propio lenguaje, utilizando los Streams, StreamController.

Hoy vamos a ver una breve introducción a los Streams, que aunque realmente no tengan la potencia de Rx sí que nos permite adentrarnos en la programación reactiva de una forma bastante interesante.

¿Qué es un Stream?

Un Stream no es más, ni menos, que un flujo de datos asíncrono entre dos objetos. Este flujo de datos puede ser finito o infinito. Es decir, podemos estar enviando datos de uno a otro de forma continua, enviarlos de forma periódica, mantener la comunicación entre ambos e ir enviando datos conforme nos convenga o podemos establecer la comunicación, enviar el dato y finalizar la misma.

El publicador de eventos o de datos, puede tener uno o muchos suscriptores los cuales recibirán todos las notificaciones al unísono cuando el publicador las envíe.

 Además cualquiera de estos suscriptores puede darse de baja de la suscripción y no recibir más mensajes mientras el resto puede seguir recibiendolos.

Esto es fácil.

¿Cómo utilizar los Streams en Flutter?

En Flutter tenemos la clase StreamController para poder trabajar con este tipo de datos y la forma de hacerlo es relativamente sencilla.

StreamController streamController = StreamController<String>();

Con esta línea de código estamos definiendo un Stream y a partir de aquí podemos añadir tanto suscriptores como publicadores de eventos.

streamController.stream.listen((event) {
    print("Esto es el dato enviado:\n$event");
  }, onDone: () {
    print("Llamada al método onDone");
  }, onError: (error) {
    print("Llamada al método onError");
  });

Con este código, acabamos de añadir un suscriptor a nuestro stream de datos, que en nuestro caso, va a emitir objetos de tipo String. 

El suscriptor “escucha” el stream y recibirá el dato cuando se publique. Además escucha los posibles errores que se puedan producir y también es capaz de recibir un mensaje del emisor cuando ha terminado de emitir los datos o cuando se cierra el stream. 

El utilizar los métodos onDone y onError son opcionales aunque muy recomendables y podríamos simplemente hacer:

streamController.stream.listen((event) {
    print("Esto es el dato enviado:\n$event");
  });

Ahora nos falta la parte del publicador de datos:

/** añadimos el dato al stream de datos */
  streamController.add("Esto es el dato que envío");

En el momento que ejecuta el método add el stream enviará este dato a su suscriptor.

Creando múltiples suscriptores a un stream en Flutter

Hemos visto como se puede crear un stream de datos, un  suscriptor y un emisor, pero como al inicio decíamos, es posible que un solo publicador tenga muchos suscriptores. Para este caso, el objeto StreamController nos da esta opción:

StreamController streamControllerBroadcast =
      StreamController<String>.broadcast();

Con esto lo que estamos creando es un canal que es capaz de ser escuchado por múltiples oyentes. Ahora podemos tener cuantos suscriptores queramos asociados a ese stream de datos:

streamControllerBroadcast.stream.listen((event) {
    print("listener 1:\n$event");
  });

  streamControllerBroadcast.stream.listen((event) {
    print("listener 2:\n$event");
  });

  streamControllerBroadcast.add("Dato para múltiples suscriptores");
  streamControllerBroadcast.close();

StreamBuilder , un Widget al rescate para la vistas

En Flutter, además de contar con las clases que hemos visto, existe un Widget para poder trabajar con streams, StreamBuilder, todo las vistas son Widgets en Flutter, no lo olvidéis.

StreamBuilder, es un widget al cual le seteamos un stream como propiedad y él nos devolverá los valores que vaya recibiendo del stream de datos a través del método builder.

Cada vez que reciba un valor este widget nos devuelve el dato y se repitan todos los componentes que tengamos anidados hacia abajo. Esto depende un poco más de cómo tengamos nuestra vista creada pero el funcionamiento básico es ese. 

class _MyHomePageState extends State<MyHomePage> {
  StreamController<int> _streamController = StreamController();

  int _counter = 0;

  void _incrementCounter() {
    _counter += 1;
    _streamController.add(_counter);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: StreamBuilder<int>(
          builder: (BuildContext context, snapShot) {
            if (!snapShot.hasError && snapShot.hasData) {
              return _buildBody(snapShot.data);
            } else {
              return Text("Error recibiendo datos");
            }
          },
          stream: _streamController.stream,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  Widget _buildBody(int value) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text('You have pushed the button this many times:'),
        Text(
          '$value',
          style: Theme.of(context).textTheme.headline4,
        ),
      ],
    );
  }

  @override
  void deactivate() {
    _streamController.close();
    super.deactivate();
  }
}

Podemos crear incluso un stream desde un Future con:

 Stream.fromFuture(fetchData()).listen((event) {
    print("dato desde Futuro:\n$event");
  });


Future<String> fetchData() {
  return Future.value("hola Futuro");
}

Así, con todo estas herramientas, por ejemplo podríamos tener una llamada a. un api rest que nos devuelva un futuro en la capa de data y desde ahí convertirlo a un stream conectado con la vista que reaccionará con los cambios que se produzcan en función de los datos que lleguen de la API.  

Con cada nuevo valor que se añada a ese caudal de datos o stream, la vista reaccionará y cambiará. 

Todo el código lo tenéis en Github.

Comparte si lo consideras interesante
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.