Trabajando con WebSockets

Además de las peticiones normales HTTP, podemos conectarnos a servidores usando WebSockets. WebSockets permite una comuncación de dos vias con el servidor sin “polling”.

En este ejemplo, conectaremos a un servidor de pruebas proporcionado por websocket.org. Este servidor simplemente nos devolverá el mismo mensaje que le enviemos!

Instrucciones

  1. Conecta a un servidor de WebSocket
  2. Escucha mensajes desde el servidor
  3. Envía datos al servidor
  4. Cierra la conexión al WebSocket

1. Conecta a un servidor de WebSocket

El paquete web_socket_channel proporciona las herramientas que necesitaremos para conectarnos al servidor WebSocket.

El paquete proporciona un WebSocketChannel que nos permite tanto escuchar mensajes desde el servidor como enviar mensajes al servidor.

En Flutter, podemos crear un WebSocketChannel que se conecta al servidor en una línea:

final channel = IOWebSocketChannel.connect('ws://echo.websocket.org');

2. Escucha mensajes desde el servidor

Ahora que hemos establecido una conexión, podemos escuchar mensajes desde nuestro servidor.

Después mandamos un mensaje al servidor de pruebas, este enviará el mensaje de vuelta.

¿Cómo vamos a escuchar por mensajes y mostrarlos? En este ejemplo, usaremos un widget StreamBuilder para escuchar nuevos mensajes y un widget Text para mostrarlo.

StreamBuilder(
  stream: widget.channel.stream,
  builder: (context, snapshot) {
    return Text(snapshot.hasData ? '${snapshot.data}' : '');
  },
);

¿Cómo funciona esto?

El WebSocketChannel proporciona un Stream de mensajes desde el servidor.

La clase Stream es una parte fundamental del paquete dart:async. Este proporciona una manera de escuchar eventos asíncronos desde una fuente de datos. Al contrario que Future, el cual devuelve una única respuesta asíncrona, la clase Stream puede entregar muchos eventos a lo largo del tiempo.

El widget StreamBuilder conectará a un Stream y pide a Flutter hacer rebuild cada vez que recibe un evento usando la función builder proporcionada!

3. Envía datos al servidor

Para enviar datos al servidor, agregaremos mensajes con el método add al receptor sink proporcionada por el WebSocketChannel.

channel.sink.add('Hello!');

¿Cómo funciona esto?

El WebSocketChannel proporciona una clase StreamSink para enviar (push) mensajes al servidor.

La clase StreamSink proporciona una manera general para añadir eventos síncronos o asíncronos a una fuente de datos.

4. Cierra la conexión al WebSocket

Después de que hayamos usado el WebSocket, queremos cerrar la conexión! Para hacerlo, podemos cerrar el sink.

channel.sink.close();

Ejemplo completo

import 'package:flutter/foundation.dart';
import 'package:web_socket_channel/io.dart';
import 'package:flutter/material.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final title = 'WebSocket Demo';
    return MaterialApp(
      title: title,
      home: MyHomePage(
        title: title,
        channel: IOWebSocketChannel.connect('ws://echo.websocket.org'),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;
  final WebSocketChannel channel;

  MyHomePage({Key key, @required this.title, @required this.channel})
      : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  TextEditingController _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Form(
              child: TextFormField(
                controller: _controller,
                decoration: InputDecoration(labelText: 'Send a message'),
              ),
            ),
            StreamBuilder(
              stream: widget.channel.stream,
              builder: (context, snapshot) {
                return Padding(
                  padding: const EdgeInsets.symmetric(vertical: 24.0),
                  child: Text(snapshot.hasData ? '${snapshot.data}' : ''),
                );
              },
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _sendMessage,
        tooltip: 'Send message',
        child: Icon(Icons.send),
      ), // Esta coma final hace al auto-formateo más agradable a los métodos de compilación.
    );
  }

  void _sendMessage() {
    if (_controller.text.isNotEmpty) {
      widget.channel.sink.add(_controller.text);
    }
  }

  @override
  void dispose() {
    widget.channel.sink.close();
    super.dispose();
  }
}

Web Sockets Demo