WebSockets et Communication en Temps Réel avec Flutter
Sommaire
- 1- Objectif
- 2- API WebSockets
- 2.1- Qu’est-ce qu’un WebSocket ?
- 2.2- Cas d’utilisation
- 2.3- Avantages des WebSockets
- 2.4- Protocole WebSocket vs HTTP
- 2.5- Implémentation des WebSockets en Flutter
- 3- Gestion d'état avec les WebSockets
- 3.1- Pattern BLoC avec WebSockets
- 3.2- Provider avec WebSockets
- 4- Applications pratiques
- 4.1- Application de chat en temps réel
- 5- Gestion d'état avec les WebSockets
- 5.1- Pattern BLoC avec WebSockets
- 5.2- Provider avec WebSockets
- 6- Applications pratiques
- 6.1- Application de chat en temps réel
- 7- Tableau de bord en temps réel
- 7.1- Service pour les données en temps réel
- 7.2- Widget pour afficher les données
- 7.3- Écran du tableau de bord
- 8- Bonnes pratiques et optimisation
- 8.1- Gestion de la reconnexion
- 8.2- Compression des messages
- 8.3- Sécurisation des connexions
- 9- Exercices pratiques
- 9.1- Exercice 1: Application de notifications en temps réel
- 9.2- Exercice 2: Tableau de bord de cryptomonnaies
- 9.3- Exercice 3: Jeu multi-joueur simple
- 10- Ressources pour s'entraîner
- 10.1.1- Cours Flutter
WebSockets et Communication en Temps Réel avec Flutter
-
Objectif
- Permettre à l’apprenant de maîtriser la consommation d’API REST et GraphQL dans une application Flutter afin d’interagir efficacement avec des services distants (lecture, création, mise à jour et suppression de données).
-
API WebSockets
-
Qu’est-ce qu’un WebSocket ?
- Un WebSocket est un protocole de communication qui établit une connexion full-duplex (bidirectionnelle) sur un seul socket TCP. Contrairement à HTTP qui suit un modèle requête-réponse, WebSocket permet une communication continue entre le client et le serveur.
-
Cas d’utilisation
- Applications de chat en temps réel
- Tableaux de bord avec mises à jour en direct
- Jeux multi-joueurs
- Notifications push
- Collaboration en temps réel (éditeurs de texte, tableaux blancs)
-
Avantages des WebSockets
- Communication bidirectionnelle : le serveur peut envoyer des données sans requête préalable du client.
- Faible latence : pas de surcharge de connexion pour chaque message.
- Efficace : moins de bande passante utilisée comparé aux requêtes HTTP répétées.
-
Protocole WebSocket vs HTTP
- HTTP (Request-Response)
- WebSocket (Full-Duplex)
- Comparaison détaillée
-
Implémentation des WebSockets en Flutter
- Flutter utilise le package
web_socket_channel
pour gérer les connexions WebSocket. - Établissement d'une connexion
- Gestionnaire de WebSocket réutilisable
- Gestion des différents formats de données
-
Gestion d'état avec les WebSockets
-
Pattern BLoC avec WebSockets
-
Provider avec WebSockets
-
Applications pratiques
-
Application de chat en temps réel
-
Gestion d'état avec les WebSockets
-
Pattern BLoC avec WebSockets
-
Provider avec WebSockets
-
Applications pratiques
-
Application de chat en temps réel
-
Tableau de bord en temps réel
-
Service pour les données en temps réel
-
Widget pour afficher les données
-
Écran du tableau de bord
-
Bonnes pratiques et optimisation
-
Gestion de la reconnexion
-
Compression des messages
-
Sécurisation des connexions
-
Exercices pratiques
-
Exercice 1: Application de notifications en temps réel
- Créez une application qui affiche des notifications en temps réel en utilisant WebSockets.
- Fonctionnalités:
- Connexion à un serveur WebSocket de notifications
- Affichage des notifications en temps réel
- Marquer les notifications comme lues
- Historique des notifications
-
Exercice 2: Tableau de bord de cryptomonnaies
- Développez un tableau de bord qui affiche les prix des cryptomonnaies en temps réel.
- Fonctionnalités:
- Connexion à une API WebSocket de prix de cryptomonnaies
- Affichage des prix en temps réel avec graphiques
- Alertes de prix personnalisées
- Portfolio de suivi
-
Exercice 3: Jeu multi-joueur simple
- Créez un jeu multi-joueur simple utilisant WebSockets pour la synchronisation.
- Fonctionnalités:
- Connexion à un serveur de jeu WebSocket
- Salles de jeu multi-joueurs
- Synchronisation en temps réel des positions des joueurs
- Chat intégré pour les joueurs
-
Ressources pour s'entraîner
- WebSocket Echo Server:
ws://echo.websocket.org
- Cryptocurrency WebSocket APIs: Binance, Coinbase, Kraken
- WebSocket Test Servers: https://www.piesocket.com/websocket-tester
// Modèle traditionnel
client -> GET /data -> serveur
client <- Response <- serveur
// Nouvelle requête nécessaire pour obtenir des updates
// Connexion initiale
client -> HTTP Upgrade -> serveur
client <- HTTP 101 Switching Protocols <- serveur
// Communication continue
client <-> Messages bidirectionnels <-> serveur
Aspect | HTTP | WebSocket |
---|---|---|
Modèle de communication | Request-Response | Full-Duplex |
Latence | Élevée (nouvelle connexion par requête) | Faible (connexion persistante) |
Overhead | Headers HTTP pour chaque requête | Headers minimaux après handshake |
Usage optimal | Données statiques, API REST | Données temps réel, streaming |
Support serveur | Universel | Nécessite un serveur WebSocket |
# pubspec.yaml
dependencies:
web_socket_channel: ^2.4.0
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/io.dart';
// Connexion à un serveur WebSocket
final channel = IOWebSocketChannel.connect(
'ws://echo.websocket.org', // URL du serveur WebSocket
);
// Écoute des messages
channel.stream.listen(
(message) {
print('Message reçu: $message');
},
onError: (error) {
print('Erreur WebSocket: $error');
},
onDone: () {
print('Connexion WebSocket fermée');
},
);
// Envoi de messages
channel.sink.add('Hello WebSocket!');
// Fermeture de la connexion
channel.sink.close();
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/status.dart' as status;
class WebSocketService {
WebSocketChannel? _channel;
Function(dynamic)? _onMessage;
Function()? _onConnected;
Function()? _onDisconnected;
void connect(
String url, {
Function(dynamic)? onMessage,
Function()? onConnected,
Function()? onDisconnected,
}) {
_onMessage = onMessage;
_onConnected = onConnected;
_onDisconnected = onDisconnected;
try {
_channel = IOWebSocketChannel.connect(url);
_channel!.stream.listen(
_handleMessage,
onError: _handleError,
onDone: _handleDone,
);
_onConnected?.call();
} catch (e) {
print('Erreur de connexion WebSocket: $e');
_onDisconnected?.call();
}
}
void _handleMessage(dynamic message) {
print('Message WebSocket reçu: $message');
_onMessage?.call(message);
}
void _handleError(error) {
print('Erreur WebSocket: $error');
_onDisconnected?.call();
}
void _handleDone() {
print('Connexion WebSocket fermée');
_onDisconnected?.call();
}
void send(dynamic message) {
if (_channel != null && _channel!.closeCode == null) {
_channel!.sink.add(message);
} else {
print('Impossible d\'envoyer le message: WebSocket non connecté');
}
}
void disconnect() {
_channel?.sink.close(status.goingAway);
_channel = null;
}
bool get isConnected => _channel != null && _channel!.closeCode == null;
}
// Envoi et réception de données JSON
void sendJson(Map data) {
final jsonString = json.encode(data);
send(jsonString);
}
void handleJsonMessage(String message) {
try {
final data = json.decode(message);
// Traiter les données JSON
} catch (e) {
print('Erreur de parsing JSON: $e');
}
}
// Envoi et réception de données binaires
void sendBinary(Uint8List data) {
channel.sink.add(data);
}
void handleBinaryMessage(dynamic message) {
if (message is Uint8List) {
// Traiter les données binaires
}
}
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
// Événements
abstract class WebSocketEvent {}
class WebSocketConnect extends WebSocketEvent {
final String url;
WebSocketConnect(this.url);
}
class WebSocketSendMessage extends WebSocketEvent {
final dynamic message;
WebSocketSendMessage(this.message);
}
class WebSocketDisconnect extends WebSocketEvent {}
// États
abstract class WebSocketState {}
class WebSocketInitial extends WebSocketState {}
class WebSocketConnecting extends WebSocketState {}
class WebSocketConnected extends WebSocketState {}
class WebSocketDisconnected extends WebSocketState {}
class WebSocketMessageReceived extends WebSocketState {
final dynamic message;
WebSocketMessageReceived(this.message);
}
class WebSocketError extends WebSocketState {
final String error;
WebSocketError(this.error);
}
// BLoC
class WebSocketBloc extends Bloc {
WebSocketChannel? _channel;
StreamSubscription? _subscription;
WebSocketBloc() : super(WebSocketInitial());
@override
Stream mapEventToState(WebSocketEvent event) async* {
if (event is WebSocketConnect) {
yield* _mapConnectToState(event);
} else if (event is WebSocketSendMessage) {
yield* _mapSendMessageToState(event);
} else if (event is WebSocketDisconnect) {
yield* _mapDisconnectToState();
}
}
Stream _mapConnectToState(WebSocketConnect event) async* {
yield WebSocketConnecting();
try {
_channel = IOWebSocketChannel.connect(event.url);
_subscription = _channel!.stream.listen(
(message) => add(WebSocketMessageReceived(message)),
onError: (error) => add(WebSocketError(error.toString())),
onDone: () => add(WebSocketDisconnect()),
);
yield WebSocketConnected();
} catch (e) {
yield WebSocketError('Erreur de connexion: $e');
}
}
Stream _mapSendMessageToState(WebSocketSendMessage event) async* {
if (_channel != null) {
_channel!.sink.add(event.message);
}
}
Stream _mapDisconnectToState() async* {
await _subscription?.cancel();
await _channel?.sink.close();
_channel = null;
_subscription = null;
yield WebSocketDisconnected();
}
@override
Future close() {
_subscription?.cancel();
_channel?.sink.close();
return super.close();
}
}
import 'package:flutter/foundation.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
class WebSocketProvider with ChangeNotifier {
WebSocketChannel? _channel;
bool _isConnected = false;
List<dynamic> _messages = [];
bool get isConnected => _isConnected;
List<dynamic> get messages => _messages;
Future<void> connect(String url) async {
try {
_channel = IOWebSocketChannel.connect(url);
_isConnected = true;
_channel!.stream.listen(
(message) {
_messages.add(message);
notifyListeners();
},
onError: (error) {
_isConnected = false;
notifyListeners();
},
onDone: () {
_isConnected = false;
notifyListeners();
},
);
notifyListeners();
} catch (e) {
_isConnected = false;
notifyListeners();
throw Exception('Erreur de connexion WebSocket: $e');
}
}
void sendMessage(dynamic message) {
if (_isConnected && _channel != null) {
_channel!.sink.add(message);
}
}
void disconnect() {
_channel?.sink.close();
_channel = null;
_isConnected = false;
notifyListeners();
}
@override
void dispose() {
disconnect();
super.dispose();
}
}
// Modèle de message
class ChatMessage {
final String id;
final String sender;
final String text;
final DateTime timestamp;
ChatMessage({
required this.id,
required this.sender,
required this.text,
required this.timestamp,
});
factory ChatMessage.fromJson(Map<String, dynamic> json) {
return ChatMessage(
id: json['id'],
sender: json['sender'],
text: json['text'],
timestamp: DateTime.parse(json['timestamp']),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'sender': sender,
'text': text,
'timestamp': timestamp.toIso8601String(),
};
}
}
// Service de chat WebSocket
class ChatWebSocketService {
final WebSocketChannel channel;
final ValueChanged<ChatMessage> onMessageReceived;
final VoidCallback onConnected;
final ValueChanged<String> onError;
ChatWebSocketService({
required this.channel,
required this.onMessageReceived,
required this.onConnected,
required this.onError,
}) {
channel.stream.listen(
(message) {
try {
final data = json.decode(message);
final chatMessage = ChatMessage.fromJson(data);
onMessageReceived(chatMessage);
} catch (e) {
onError('Erreur de parsing du message: $e');
}
},
onError: (error) => onError('Erreur WebSocket: $error'),
onDone: () => onError('Connexion WebSocket fermée'),
);
}
void sendMessage(ChatMessage message) {
final jsonMessage = json.encode(message.toJson());
channel.sink.add(jsonMessage);
}
void dispose() {
channel.sink.close();
}
}
// UI pour l'application de chat
class ChatScreen extends StatefulWidget {
final String username;
const ChatScreen({Key? key, required this.username}) : super(key: key);
@override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final TextEditingController _messageController = TextEditingController();
final List<ChatMessage> _messages = [];
late ChatWebSocketService _chatService;
@override
void initState() {
super.initState();
_connectToChat();
}
void _connectToChat() {
final channel = IOWebSocketChannel.connect('ws://your-chat-server.com/chat');
_chatService = ChatWebSocketService(
channel: channel,
onMessageReceived: _handleMessageReceived,
onConnected: () => print('Connecté au chat'),
onError: _handleError,
);
}
void _handleMessageReceived(ChatMessage message) {
setState(() {
_messages.add(message);
});
}
void _handleError(String error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error)),
);
}
void _sendMessage() {
if (_messageController.text.isNotEmpty) {
final message = ChatMessage(
id: DateTime.now().millisecondsSinceEpoch.toString(),
sender: widget.username,
text: _messageController.text,
timestamp: DateTime.now(),
);
_chatService.sendMessage(message);
_messageController.clear();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Chat - ${widget.username}')),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: _messages.length,
itemBuilder: (context, index) {
final message = _messages[index];
return ListTile(
title: Text(message.sender),
subtitle: Text(message.text),
trailing: Text(
DateFormat('HH:mm').format(message.timestamp),
),
);
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _messageController,
decoration: InputDecoration(
hintText: 'Tapez votre message...',
),
onSubmitted: (_) => _sendMessage(),
),
),
IconButton(
icon: Icon(Icons.send),
onPressed: _sendMessage,
),
],
),
),
],
),
);
}
@override
void dispose() {
_chatService.dispose();
_messageController.dispose();
super.dispose();
}
}
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
// Événements
abstract class WebSocketEvent {}
class WebSocketConnect extends WebSocketEvent {
final String url;
WebSocketConnect(this.url);
}
class WebSocketSendMessage extends WebSocketEvent {
final dynamic message;
WebSocketSendMessage(this.message);
}
class WebSocketDisconnect extends WebSocketEvent {}
// États
abstract class WebSocketState {}
class WebSocketInitial extends WebSocketState {}
class WebSocketConnecting extends WebSocketState {}
class WebSocketConnected extends WebSocketState {}
class WebSocketDisconnected extends WebSocketState {}
class WebSocketMessageReceived extends WebSocketState {
final dynamic message;
WebSocketMessageReceived(this.message);
}
class WebSocketError extends WebSocketState {
final String error;
WebSocketError(this.error);
}
// BLoC
class WebSocketBloc extends Bloc {
WebSocketChannel? _channel;
StreamSubscription? _subscription;
WebSocketBloc() : super(WebSocketInitial());
@override
Stream mapEventToState(WebSocketEvent event) async* {
if (event is WebSocketConnect) {
yield* _mapConnectToState(event);
} else if (event is WebSocketSendMessage) {
yield* _mapSendMessageToState(event);
} else if (event is WebSocketDisconnect) {
yield* _mapDisconnectToState();
}
}
Stream _mapConnectToState(WebSocketConnect event) async* {
yield WebSocketConnecting();
try {
_channel = IOWebSocketChannel.connect(event.url);
_subscription = _channel!.stream.listen(
(message) => add(WebSocketMessageReceived(message)),
onError: (error) => add(WebSocketError(error.toString())),
onDone: () => add(WebSocketDisconnect()),
);
yield WebSocketConnected();
} catch (e) {
yield WebSocketError('Erreur de connexion: $e');
}
}
Stream _mapSendMessageToState(WebSocketSendMessage event) async* {
if (_channel != null) {
_channel!.sink.add(event.message);
}
}
Stream _mapDisconnectToState() async* {
await _subscription?.cancel();
await _channel?.sink.close();
_channel = null;
_subscription = null;
yield WebSocketDisconnected();
}
@override
Future close() {
_subscription?.cancel();
_channel?.sink.close();
return super.close();
}
}
import 'package:flutter/foundation.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
class WebSocketProvider with ChangeNotifier {
WebSocketChannel? _channel;
bool _isConnected = false;
List _messages = [];
bool get isConnected => _isConnected;
List get messages => _messages;
// Connexion au serveur WebSocket
Future connect(String url) async {
try {
_channel = IOWebSocketChannel.connect(url);
_isConnected = true;
_channel!.stream.listen(
(message) {
_messages.add(message);
notifyListeners();
},
onError: (error) {
_isConnected = false;
notifyListeners();
},
onDone: () {
_isConnected = false;
notifyListeners();
},
);
notifyListeners();
} catch (e) {
_isConnected = false;
notifyListeners();
throw Exception('Erreur de connexion WebSocket: $e');
}
}
// Envoi d'un message
void sendMessage(dynamic message) {
if (_isConnected && _channel != null) {
_channel!.sink.add(message);
}
}
// Déconnexion
void disconnect() {
_channel?.sink.close();
_channel = null;
_isConnected = false;
notifyListeners();
}
@override
void dispose() {
disconnect();
super.dispose();
}
}
// Modèle de message
class ChatMessage {
final String id;
final String sender;
final String text;
final DateTime timestamp;
ChatMessage({
required this.id,
required this.sender,
required this.text,
required this.timestamp,
});
factory ChatMessage.fromJson(Map json) {
return ChatMessage(
id: json['id'],
sender: json['sender'],
text: json['text'],
timestamp: DateTime.parse(json['timestamp']),
);
}
Map toJson() {
return {
'id': id,
'sender': sender,
'text': text,
'timestamp': timestamp.toIso8601String(),
};
}
}
// Service de chat WebSocket
class ChatWebSocketService {
final WebSocketChannel channel;
final ValueChanged onMessageReceived;
final VoidCallback onConnected;
final ValueChanged onError;
ChatWebSocketService({
required this.channel,
required this.onMessageReceived,
required this.onConnected,
required this.onError,
}) {
channel.stream.listen(
(message) {
try {
final data = json.decode(message);
final chatMessage = ChatMessage.fromJson(data);
onMessageReceived(chatMessage);
} catch (e) {
onError('Erreur de parsing du message: $e');
}
},
onError: (error) => onError('Erreur WebSocket: $error'),
onDone: () => onError('Connexion WebSocket fermée'),
);
}
void sendMessage(ChatMessage message) {
final jsonMessage = json.encode(message.toJson());
channel.sink.add(jsonMessage);
}
void dispose() {
channel.sink.close();
}
}
// UI pour l'application de chat
class ChatScreen extends StatefulWidget {
final String username;
const ChatScreen({Key? key, required this.username}) : super(key: key);
@override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State {
final TextEditingController _messageController = TextEditingController();
final List _messages = [];
late ChatWebSocketService _chatService;
@override
void initState() {
super.initState();
_connectToChat();
}
void _connectToChat() {
final channel = IOWebSocketChannel.connect('ws://your-chat-server.com/chat');
_chatService = ChatWebSocketService(
channel: channel,
onMessageReceived: _handleMessageReceived,
onConnected: () => print('Connecté au chat'),
onError: _handleError,
);
}
void _handleMessageReceived(ChatMessage message) {
setState(() {
_messages.add(message);
});
}
void _handleError(String error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error)),
);
}
void _sendMessage() {
if (_messageController.text.isNotEmpty) {
final message = ChatMessage(
id: DateTime.now().millisecondsSinceEpoch.toString(),
sender: widget.username,
text: _messageController.text,
timestamp: DateTime.now(),
);
_chatService.sendMessage(message);
_messageController.clear();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Chat - ${widget.username}')),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: _messages.length,
itemBuilder: (context, index) {
final message = _messages[index];
return ListTile(
title: Text(message.sender),
subtitle: Text(message.text),
trailing: Text(
DateFormat('HH:mm').format(message.timestamp),
),
);
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _messageController,
decoration: InputDecoration(
hintText: 'Tapez votre message...',
),
onSubmitted: (_) => _sendMessage(),
),
),
IconButton(
icon: Icon(Icons.send),
onPressed: _sendMessage,
),
],
),
),
],
),
);
}
@override
void dispose() {
_chatService.dispose();
_messageController.dispose();
super.dispose();
}
}
class RealtimeDashboardService {
final WebSocketChannel channel;
final ValueChanged
class RealtimeDataCard extends StatelessWidget {
final String title;
final dynamic value;
final Color color;
const RealtimeDataCard({
Key? key,
required this.title,
required this.value,
this.color = Colors.blue,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
color: color,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(height: 8),
Text(
value.toString(),
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
),
);
}
}
class DashboardScreen extends StatefulWidget {
@override
_DashboardScreenState createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State {
final Map _data = {};
late RealtimeDashboardService _dashboardService;
@override
void initState() {
super.initState();
_connectToDashboard();
}
void _connectToDashboard() {
final channel = IOWebSocketChannel.connect(
'ws://your-dashboard-server.com/dashboard',
);
_dashboardService = RealtimeDashboardService(
channel: channel,
onDataUpdate: _handleDataUpdate,
onConnected: () {
// Demander les données initiales
_dashboardService.requestData('all');
},
onError: _handleError,
);
}
void _handleDataUpdate(Map newData) {
setState(() {
_data.addAll(newData);
});
}
void _handleError(String error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error)),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Tableau de bord en temps réel')),
body: GridView.count(
crossAxisCount: 2,
children: [
RealtimeDataCard(
title: 'Utilisateurs en ligne',
value: _data['onlineUsers'] ?? 0,
color: Colors.green,
),
RealtimeDataCard(
title: 'Commandes aujourd\'hui',
value: _data['todayOrders'] ?? 0,
color: Colors.blue,
),
RealtimeDataCard(
title: 'Revenu aujourd\'hui',
value: '€${_data['todayRevenue'] ?? 0}',
color: Colors.purple,
),
RealtimeDataCard(
title: 'Taux de conversion',
value: '${_data['conversionRate'] ?? 0}%',
color: Colors.orange,
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => _dashboardService.requestData('all'),
child: Icon(Icons.refresh),
),
);
}
@override
void dispose() {
_dashboard_service.dispose();
super.dispose();
}
}
class ReconnectWebSocket {
final String url;
final Duration reconnectInterval;
final int maxReconnectAttempts;
WebSocketChannel? _channel;
StreamSubscription? _subscription;
int _reconnectAttempts = 0;
Timer? _reconnectTimer;
ReconnectWebSocket({
required this.url,
this.reconnectInterval = const Duration(seconds: 5),
this.maxReconnectAttempts = 10,
});
Future connect() async {
try {
_channel = IOWebSocketChannel.connect(url);
_reconnectAttempts = 0;
_subscription = _channel!.stream.listen(
_onMessage,
onError: _onError,
onDone: _onDone,
);
print('WebSocket connecté avec succès');
} catch (e) {
print('Échec de connexion WebSocket: $e');
_scheduleReconnect();
}
}
void _onMessage(dynamic message) {
// Traiter le message
}
void _onError(error) {
print('Erreur WebSocket: $error');
_scheduleReconnect();
}
void _onDone() {
print('Connexion WebSocket fermée');
_scheduleReconnect();
}
void _scheduleReconnect() {
if (_reconnectAttempts < maxReconnectAttempts) {
_reconnectTimer = Timer(reconnectInterval, () {
_reconnectAttempts++;
print('Tentative de reconnexion $_reconnectAttempts/$maxReconnectAttempts');
connect();
});
} else {
print('Nombre maximum de tentatives de reconnexion atteint');
}
}
void send(dynamic message) {
if (_channel != null) {
_channel!.sink.add(message);
}
}
void disconnect() {
_reconnectTimer?.cancel();
_subscription?.cancel();
_channel?.sink.close();
}
}
import 'dart:convert';
import 'package:archive/archive.dart';
class CompressedWebSocket {
final WebSocketChannel channel;
CompressedWebSocket(this.channel);
// Compression d'un message JSON
String compressMessage(Map message) {
final jsonString = json.encode(message);
final bytes = utf8.encode(jsonString);
final compressed = GZipEncoder().encode(bytes);
return base64.encode(compressed!);
}
// Décompression d'un message
Map decompressMessage(String compressedMessage) {
final compressedBytes = base64.decode(compressedMessage);
final decompressedBytes = GZipDecoder().decodeBytes(compressedBytes);
final jsonString = utf8.decode(decompressedBytes);
return json.decode(jsonString);
}
void sendCompressed(Map message) {
final compressed = compressMessage(message);
channel.sink.add(compressed);
}
Stream
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/io.dart';
class SecureWebSocketService {
WebSocketChannel connectSecure(
String url, {
Map? headers,
Duration? pingInterval,
}) {
// Utilisation de wss:// pour les connexions sécurisées
if (!url.startsWith('wss://')) {
throw ArgumentError('URL must use wss:// for secure connections');
}
return IOWebSocketChannel.connect(
url,
headers: headers,
pingInterval: pingInterval,
);
}
// Ajout de tokens d'authentification
WebSocketChannel connectWithAuth(
String url,
String authToken, {
Duration? pingInterval,
}) {
return IOWebSocketChannel.connect(
url,
headers: {'Authorization': 'Bearer $authToken'},
pingInterval: pingInterval,
);
}
}