Web socket em Flutter

Tempo de leitura: 3 minutes

Para comunicação bidirecional, como mensagens em tempo real, rastreamento por GPS, chamadas de vídeo, etc., costumamos usar o soquete da web. Vamos nos aprofundar em como funciona e qual é a implementação e as melhores práticas para isso.

PS : Você receberá muitas dicas profissionais durante a leitura.

Web socket funciona no modelo Pub/Sub

 

O que é o modelo Pub/Sub?

Mensagens Pub/Sub significa modelo de mensagens Publish/Subscribe. Temos que assinar os dados e o socket vai começar a dar a atualização. Em palavras muito simples, inscreva-se para obter o fluxo de dados e cancele a inscrição para parar de receber dados.

Depois de conectar o websocket, temos que mantê-lo conectado por comunicação bidirecional. Se alguma das maneiras parar de enviar dados, a conexão será perdida e você terá que conectá-la novamente. Vamos começar nossa implementação para o Flutter. Para isso, usaremos o plug-in fornecido abaixo:

1. Adicione-o em seu pubspec.yaml

web_socket_channel: ^2.4.0

Adicione esta linha nas dependências de pubspec.yaml e execute flutter pub get.

Importe-o em seu arquivo

import 'package:web_socket_channel/io.dart';

Dica profissional 1: torne sua classe auxiliar uma classe singleton e mantenha todos os métodos importantes aqui.

2. Conecte ao soquete

IOWebSocketChannel _client;
WebSocket.connect(_endPoint).then((ws) {
  _client = IOWebSocketChannel(ws);
  if (_client != null) {
    _client.sink.add("Socket added");
  }
}).onError((error, stackTrace) {
  
});

Aqui estamos estabelecendo conexão com o soquete. Endpoint será seu url começa com ws ou wss. Após a conexão, obteremos um cliente de soquete. Mantenha-o como IOWebSocketChannel. IOWebSocketChannel.sink.add será usado para enviar os dados.

 

3. Inscreva-se em um tópico

_client.sink.add(subscriptionText);

Aqui, o texto da assinatura será a mensagem que você deseja enviar ao back-end para iniciar a assinatura.

 

4. Comece a ouvir a mensagem

_client.stream.listen((message){
    //parse your message here
 },
 onDone: () {
   
 },
);

IOWebSocketChannel.stream começará a enviar os dados que podem ser ouvidos pelo método acima. Você pode analisar sua mensagem e usá-la em seu aplicativo.

onDone : Chamará na desconexão do socket.

Dica profissional 2: Sempre envie e receba dados em bytes ou use ProtoBuf

 

4. Cancele a inscrição no tópico

_client.sink.add(unSubscriptionText);

Envie a mensagem de cancelamento de assinatura conforme mencionado no back-end. Isso interromperá o fluxo de dados.

 

5. Desconecte o soquete

_client?.sink?.close(status.goingAway);

IOWebSocketChannel.sink.close fechará a conexão. Aqui, status.goingAway é o status definido na biblioteca. Você também pode usar seu texto personalizado.

 

Melhores Práticas

  1. Evite enviar mensagens de inscrição várias vezes.
  2. Enviar heartbeat a cada 10 segundos. Heartbeat é um texto que mantém a conexão viva. É o texto mencionado no servidor back-end.
  3. A cada conexão/desconexão da internet, você deve conectar sua tomada e assinar novamente todos os tópicos.
  4. Mantenha uma fila de buffer para salvar as assinaturas se você perder a conexão com a Internet no meio. Você pode enviar esta assinatura na reconexão da internet.
  5. Mantenha um mapa para recuperar os dados previamente assinados.
  6. Se você receber um erro ou desconectar devido à Internet, tente novamente por pelo menos 3 vezes (ou continue tentando).
  7. Não se esqueça de cancelar a inscrição no tópico se não for necessário.
  8. Conecte o soquete na retomada do aplicativo e desconecte na pausa do aplicativo.

Dica profissional 3: tente fazer o menor número possível de assinaturas de uma só vez.

 

Aqui está o código de trabalho. Você pode modificá-lo de acordo com seu caso de uso.

import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/status.dart' as status;


class HydraSocketClient {
  static HydraSocketClient _instance;

  IOWebSocketChannel _client;
  bool _isConnected = false;
  final _heartbeatInterval = 10;
  final _reconnectIntervalMs = 5000;
  int _reconnectCount = 120;
  final _sendBuffer = Queue();
  Timer heartBeatTimer, _reconnectTimer;

  static HydraSocketClient getInstance() {
    if (_instance == null) {
      _instance = HydraSocketClient();
    }
    return _instance;
  }

  connectToSocket() async {
    if (!_isConnected) {
      final _endPoint = await HydraConst.getEndPoint();
      WebSocket.connect(_endPoint).then((ws) {
        _client = IOWebSocketChannel(ws);
        if (_client != null) {
          _reconnectCount = 120;
          _reconnectTimer?.cancel();
          _client.sink.add("Socket added");
          _listenToMessage();
          _isConnected = true;
          _startHeartBeatTimer();
          while (_sendBuffer.isNotEmpty) {
            String text = _sendBuffer.first;
            _sendBuffer.remove(text);
            _push(text);
          }
        }
      }).onError((error, stackTrace) {
        disconnect();
        _reconnect();
      });
    }
  }

  _reconnect() async {
    if ((_reconnectTimer == null || !_reconnectTimer.isActive) &&
        _reconnectCount > 0) {
      _reconnectTimer = Timer.periodic(Duration(seconds: _reconnectIntervalMs),
          (Timer timer) async {
        if (_reconnectCount == 0) {
          _reconnectTimer?.cancel();
          return;
        }
        await connectToSocket();
        _reconnectCount--;
      });
    }
  }

  _listenToMessage() {
    _client.stream.listen(
      (message) {
        //parse message here
      },
      onDone: () {
        disconnect();
        _reconnect();
      },
    );
  }

  bool subscribe(String text, {bool unsubscribeAllSubscribed = false}) {
    _push(text);
  }

  _startHeartBeatTimer() {
    heartBeatTimer =
        Timer.periodic(Duration(seconds: _heartbeatInterval), (Timer timer) {
      _client.sink?.add(_heartbeatData);
    });
  }

  _push(String text) {
    if (_isConnected) {
      _client.sink.add(text);
    } else
      _sendBuffer.add(text);
  }

  disconnect() {
    _client?.sink?.close(status.goingAway);
    heartBeatTimer?.cancel();
    _reconnectTimer?.cancel();
    _isConnected = false;
  }

  int _fromBytesToInt32(List<int> elements) {
    ByteBuffer buffer = new Int8List.fromList(elements).buffer;
    ByteData byteData = new ByteData.view(buffer);
    return byteData.getInt32(0);
  }

  int _fromBytesToInt64(List<int> elements) {
    ByteBuffer buffer = new Int8List.fromList(elements).buffer;
    ByteData byteData = new ByteData.view(buffer);
    return byteData.getInt64(0);
  }
}

 

Isso é tudo por hoje. Obrigado por ficar até o final. Sugestões são bem vindas. Codificação feliz.