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
- 3- Comment fonctionne le WebSocket ?
- 3.1- Cas d'une connexion HTTP
- 3.2- Cas d'une connexion WebSocket
- 3.3- Exemple
- 4- Implémentation des WebSockets en Flutter
- 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- Gestion d'état avec les WebSockets
- 7.1- Pattern BLoC avec WebSockets
- 7.2- Provider avec WebSockets
- 8- Applications pratiques
- 8.1- Application de chat en temps réel
- 9- Tableau de bord en temps réel
- 9.1- Service pour les données en temps réel
- 9.2- Widget pour afficher les données
- 9.3- Écran du tableau de bord
- 10- Bonnes pratiques et optimisation
- 10.1- Gestion de la reconnexion
- 10.2- Compression des messages
- 10.3- Sécurisation des connexions
- 11- Exercices pratiques
- 11.1- Exercice 1: Application de notifications en temps réel
- 11.2- Exercice 2: Tableau de bord de cryptomonnaies
- 11.3- Exercice 3: Jeu multi-joueur simple
- 12- Ressources pour s'entraîner
- 12.1.1- Cours Flutter
WebSockets et Communication en Temps Réel avec Flutter
-
Objectif
-
API WebSockets
-
Qu’est-ce qu’un WebSocket ?
- Un WebSocket est un protocole qui permet une communication bidirectionnelle et persistante (en temps réel) entre un client, comme un navigateur web, et un serveur, via une seule connexion TCP.
- Contrairement au protocole HTTP classique, WebSocket autorise le serveur à envoyer des données au client à tout moment, sans nécessiter une requête préalable du client, ce qui est idéal pour les applications interactives comme la messagerie instantanée ou les jeux en ligne.
- Le protocole WebSocket est un protocole réseau basé sur le protocole TCP. Celui-ci définit la façon dont les données sont échangées entre les réseaux. En raison de sa fiabilité et de son efficacité, il est utilisé par presque tous les clients. TCP établit une connexion entre deux points finaux de communication qu’on appelle des sockets. Ainsi, une communication bidirectionnelle s’établit entre les données.
- Dans le cas d’une connexion bidirectionnelle comme avec le protocole WebSocket (parfois écrit web socket), les données circulent simultanément dans les deux sens. L’avantage : le chargement des données est beaucoup plus rapide. Le WebSocket est spécialement conçu pour permettre d’établir une communication directe entre une application Web et un serveur WebSocket. Concrètement, cela signifie que : vous cliquez sur un site Web et celui-ci s’affiche en « temps réel ».
-
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
-
Comment fonctionne le WebSocket ?
-
Cas d'une connexion HTTP
- Comment fonctionne la consultation d’un site Web sans WebSocket ? Sur Internet, la transmission de site Web se fait en général par le biais d’une connexion HTTP. Le protocole est utilisé pour transmettre les données et permet d’afficher le site Web dans le navigateur. Dans ce contexte, votre client envoie à chaque action (par exemple chaque clic) une requête au serveur.
- En HTTP, pour consulter un site Web, le client doit d’abord envoyer une requête au serveur. Celui-ci peut ensuite répondre et transmettre le contenu désiré. Il s’agit d’un simple modèle de requête et de réponse, qui finit par occasionner un délai important entre la requête et la réponse.
-
Cas d'une connexion WebSocket
- Une connexion WebSocket est initialisée en utilisant le protocole HTTP : chaque connexion à une WebSocket débute par une requête HTTP qui utilise l'option upgrade dans son en-tête. Cette option permet de préciser que le client souhaite que la connexion utilise un autre protocole, en l'occurrence le protocole WebSocket. Cette requête HTTP s'appelle handshake dans le cas de l'utilisation d'une WebSocket.
- Le protocole HTTP n'est utilisé que pour établir la connexion d'une WebSocket : une fois la connexion établie le protocole HTTP n'est plus utilisé au profit du protocole WebSocket.
- C'est toujours le client qui initie une demande de connexion : le serveur ne peut pas initier de connexions mais il est à l'écoute des clients qui le contacte pour créer une connexion.
- Lorsque le serveur répond, la connexion est établie et le client et le serveur peuvent envoyer et recevoir des messages.
- L’utilisation d’un WebSocket permet la consultation dynamique en temps réel d’un site Web. Avec le protocole WebSocket, il suffit au client d’établir la connexion avec un serveur Web. La connexion entre le client et le serveur s’établit grâce à la phase de handshake du protocole WebSocket. Dans ce cas, le client envoie toutes les identifications nécessaires à l’échange de données au serveur.
- Une fois établi, le canal de communication reste semi-ouvert. Le serveur peut s’activer de lui-même et transmettre au client toutes les informations sans que le client ne les demande. Les notifications push des sites Web fonctionnent sur ce principe. Si de nouvelles informations sont disponibles sur le serveur, le serveur les communique au client sans que celui-ci n’ait à émettre de requête.
-
Exemple
- Avec le WebSocket, le client commence par envoyer une requête classique, comme dans le cadre du protocole HTTP, mais dans ce cas, le processus de connexion s’effectue via une connexion TCP permanente. La phase de handshake entre le client et le serveur se déroule de la manière suivante :
- Le client envoie la requête :
- La réponse du serveur est la suivante :
-
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 |
GET /chatService HTTP/1.1
Host: server.example.com
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: superchat
# 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,
);
}
}