Web socket em Flutter
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
- Evite enviar mensagens de inscrição várias vezes.
- Enviar heartbeat a cada 10 segundos. Heartbeat é um texto que mantém a conexão viva. É o texto mencionado no servidor back-end.
- A cada conexão/desconexão da internet, você deve conectar sua tomada e assinar novamente todos os tópicos.
- 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.
- Mantenha um mapa para recuperar os dados previamente assinados.
- Se você receber um erro ou desconectar devido à Internet, tente novamente por pelo menos 3 vezes (ou continue tentando).
- Não se esqueça de cancelar a inscrição no tópico se não for necessário.
- 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.