flutter_realtime_client 0.1.2
flutter_realtime_client: ^0.1.2 copied to clipboard
A production-ready Flutter/Dart realtime client library with robust connection management, persistence, ordered delivery, typing indicators, presence, and read receipts.
Realtime Client for Flutter/Dart #
A production-ready Flutter/Dart realtime client library with robust connection management, persistence, ordered delivery, and advanced features for building reliable realtime applications.
π₯ What You Get (Main Uses) #
1. Live Chat & Messaging #
Instant messages, typing indicators, read receipts β reliable delivery even with flaky mobile networks.
2. Real-time Order / Status Updates #
Food orders, delivery tracking, warehouse picking status β clients get accurate, ordered updates.
3. Presence & Notifications (in-app) #
Who's online, agent availability, live support routing.
4. Collaborative Apps #
Live cursors, shared documents, collaborative dashboards with ordered events.
5. Telemetry & Live Metrics #
Push live KPIs, sensor data, device telemetry with guaranteed delivery and resume after reconnect.
6. Command-and-Control / IoT #
Send commands to devices and ensure at-least-once delivery with ack and resume.
7. Gaming / Low-Latency Events (non-competitive) #
Chat, matchmaking, lobby status, non-hard realtime multiplayer features.
8. Audit / Compliance Workflows #
Exactly-ordered logs of actions (seq numbers + server timestamps) for audits.
9. Enterprise Apps (ERP, CRM) #
Live updates for Sales Order changes, Delivery Note status, stock alerts integrated with ERPNext.
β Problems It Solves (Concrete) #
| Problem | Solution |
|---|---|
| Connection instability | Automatic reconnect with exponential backoff + jitter β robust on mobile |
| Message loss during disconnects | Persistent unsent queue stored in SQLite/Hive β messages survive app restarts |
| Duplicate messages after reconnect | seq + ack + idempotency keys + resume protocol β de-duplication |
| Out-of-order delivery | Monotonic seq + server-side reordering / authoritative timestamps β preserve order |
| Auth interruptions | Pluggable token-refresh handler β seamless reauth & retry |
| App killed / background | Persistent queue + reconnect on network regained β sync when app restarts |
| High throughput | Batching, throttling, and background isolates β avoids UI jank |
π― Key Features & Why Each Matters #
- Exponential backoff + jitter β Avoids thundering herd when server down
- Heartbeat (ping/pong) β Detect dead connections quickly and reconnect
- Persistent queue β No user data loss; good UX for offline-first apps
- Ack & resume protocol β Server and client agree on what's delivered/needed
- Configurable delivery semantics β Pick at-most-once or at-least-once per use-case
- Network-awareness β Respect connectivity changes; save battery by pausing when offline
- Metrics & lifecycle events β Observability for production debugging
- Pluggable token refresh & secure storage β Secure mobile auth flows
- Typing indicators β Real-time typing status for chat apps
- Read receipts β Track message read status
- Presence management β Track online/offline/away/busy status
π Quick Start #
Installation #
Add to your pubspec.yaml:
dependencies:
flutter_realtime_client:
path: ./path/to/flutter_realtime_client
Basic Usage #
import 'package:flutter_realtime_client/flutter_realtime_client.dart';
// Initialize client
final client = RealtimeClient(
config: RealtimeConfig(
url: 'http://localhost:3000', // Socket.IO
// or 'ws://localhost:3001' for raw WebSocket
deliveryStrategy: DeliveryStrategy.atLeastOnce,
),
queueStorage: SqliteQueueStorage(),
);
// Connect
await client.connect();
// Listen for messages
client.onMessage.listen((msg) {
print('Received: ${msg.event} - ${msg.payload}');
});
// Send message
await client.sendEvent('chat.message', {
'text': 'Hello World',
'room': 'general',
});
// Disconnect
await client.disconnect();
π Advanced Features #
Typing Indicators #
// Send typing indicator
await client.sendTypingIndicator(channelId: 'room-123', isTyping: true);
// Listen for typing
client.typingManager.typingStream.listen((typingMap) {
final users = client.typingManager.getTypingUsers(channelId: 'room-123');
print('Users typing: $users');
});
// Stop typing
await client.sendTypingIndicator(channelId: 'room-123', isTyping: false);
Presence Management #
// Update your presence
await client.updatePresence(PresenceStatus.online, metadata: {
'device': 'mobile',
'location': 'New York',
});
// Listen for presence updates
client.presenceManager.presenceStream.listen((presenceMap) {
presenceMap.forEach((userId, info) {
print('$userId is ${info.status.name}');
});
});
// Check if user is online
final isOnline = client.presenceManager.isOnline('user-123');
Read Receipts #
// Send read receipt
await client.sendReadReceipt('message-id-123');
// Listen for read receipts
client.readReceiptManager.receiptsStream.listen((receiptsMap) {
final receipts = client.readReceiptManager.getReceipts('message-id-123');
print('Read by ${receipts.length} users');
});
// Check if specific user read message
final hasRead = client.readReceiptManager.hasUserRead('message-id-123', 'user-456');
Metrics & Observability #
// Listen to metrics
client.metricsStream.listen((metrics) {
print('Total reconnects: ${metrics.totalReconnects}');
print('Messages sent: ${metrics.totalMessagesSent}');
print('Messages acked: ${metrics.totalMessagesAcked}');
print('Queue size: ${metrics.currentQueueSize}');
print('Avg reconnect time: ${metrics.averageReconnectTime}');
});
// Get current metrics
final metrics = client.metrics;
print(metrics.toJson());
Connection State Monitoring #
client.connectionState.listen((state) {
switch (state) {
case ConnectionState.connecting:
print('Connecting...');
break;
case ConnectionState.connected:
print('Connected!');
break;
case ConnectionState.disconnected:
print('Disconnected');
break;
case ConnectionState.reconnecting:
print('Reconnecting...');
break;
case ConnectionState.failed:
print('Connection failed');
break;
}
});
Authentication with Token Refresh #
final authDelegate = SecureStorageAuthDelegate(
refreshTokenCallback: () async {
// Call your refresh endpoint
final response = await http.post(
Uri.parse('https://api.example.com/refresh'),
body: {'refresh_token': 'your-refresh-token'},
);
final newToken = jsonDecode(response.body)['access_token'];
return newToken;
},
);
final client = RealtimeClient(
config: config,
queueStorage: SqliteQueueStorage(),
authDelegate: authDelegate,
);
π Real-World Examples #
A. Chat App with Message Reliability #
// User types β client assigns seq=101, stores in persistent queue
await client.sendEvent('chat.message', {
'text': 'Hello!',
'room': 'general',
});
// Shows UI "sending"
// Client sends message; server replies ack with ack_seq=101
client.onAck.listen((ack) {
print('Message ${ack.ackSeq} delivered!');
// Update UI to show "delivered"
});
// Network drops; user writes 5 messages β all queued
// On reconnect client sends resume with last_ack_seq
// Server ignores duplicates and accepts missed ones
// UI updates to show all messages delivered
B. Delivery Tracking for Courier #
// Server pushes event: delivery.status with seq
client.onMessage.listen((msg) {
if (msg.event == 'delivery.status') {
final status = msg.payload!['status'];
final location = msg.payload!['location'];
// Update map with current location
updateDeliveryMap(location);
// If client missed messages (offline), after resume
// server sends missing events so app shows correct
// current status and step-by-step timeline
}
});
C. ERPNext Integration for Sales Orders #
// Sales order updated in backend β realtime event to Sales Reps
client.onMessage.listen((msg) {
if (msg.event == 'sales_order.updated') {
final orderId = msg.payload!['order_id'];
final status = msg.payload!['status'];
// Update local cache
updateSalesOrder(orderId, status);
// If rep's app offline, queued events resend on resume
// so they see consistent order of changes and don't miss approvals
}
});
π¦ When to Use This (vs Polling) #
β Use Realtime Client When: #
- Low-latency updates matter (chat, orders, presence)
- Polling cost is high / inefficient
- You need ordered delivery and/or guaranteed delivery semantics
- Mobile network is unreliable
- Offline-first experience is important
β Avoid If: #
- Your updates are rare and occasional polling is simpler
- Strict ultra-low latency competitive gaming β you'll need more specialized networking
- You don't need delivery guarantees
βοΈ Trade-offs & Considerations #
| Aspect | Consideration |
|---|---|
| Complexity | More code, server support required (ack/resume) |
| Server responsibility | Server must implement ack/resume, dedup, and persistence for missed messages |
| Resource usage | Persistent connections use resources; scale server accordingly |
| Mobile background limits | iOS background restrictions may limit realtime while app is suspended (use push notifications as fallback) |
π Architecture #
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β RealtimeClient β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Connection β β Message β β Feature β β
β β Management β β Queue β β Managers β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β β β β
β βΌ βΌ βΌ β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Transport β β SQLite β β Presence β β
β β (WS/Socket.IOβ β Storage β β Typing β β
β β ) β β β β Receipts β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Components #
- RealtimeClient: Main entry point, handles state machine
- Transport: Abstract interface for
WebSocketTransportandSocketIOTransport - QueueStorage: Persistent storage for offline messages (SQLite)
- AuthDelegate: Handles token storage and refresh
- PresenceManager: Tracks user online/offline status
- TypingIndicatorManager: Manages typing indicators with auto-expiry
- ReadReceiptManager: Tracks message read receipts
- Metrics: Observability and monitoring
π§ Configuration Options #
final config = RealtimeConfig(
url: 'ws://localhost:3001',
deliveryStrategy: DeliveryStrategy.atLeastOnce, // atMostOnce, bestEffortOrdered
initialRetryDelay: Duration(seconds: 1),
maxRetryDelay: Duration(seconds: 30),
jitter: 0.2, // 20% jitter
maxRetries: -1, // -1 for infinite
heartbeatInterval: Duration(seconds: 25),
heartbeatTimeout: Duration(seconds: 10),
useTls: true, // Use wss:// or https://
batchSize: 0, // 0 to disable batching
batchInterval: Duration(milliseconds: 50),
logLevel: Level.info,
connectTimeout: Duration(seconds: 10),
);
π§ͺ Testing #
Run unit tests:
cd flutter_realtime_client
flutter test
Run integration tests (requires server):
# Terminal 1: Start server
cd server-example
npm install
npm run start:io # or start:ws
# Terminal 2: Run tests
cd ..
flutter test test/integration_test.dart
π₯ Server Setup #
Protocol Specification #
All messages are JSON with this structure:
{
"type": "event" | "ack" | "resume" | "resume_response" | "meta" | "error",
"seq": 123,
"client_id": "uuid",
"idempotency_key": "uuid",
"event": "chat.message",
"payload": { ... },
"timestamp": "2025-11-21T14:48:02Z"
}
Running Example Servers #
Socket.IO Server (Port 3000)
cd server-example
npm install
npm run start:io
WebSocket Server (Port 3001)
cd server-example
npm install
npm run start:ws
Server Requirements #
Your server must implement:
- ACK messages: Reply with
{type: "ack", ack_seq: N}when receiving events - Resume protocol: Handle
{type: "resume", last_ack_seq: N}and respond with missed messages - Idempotency: Use
idempotency_keyto deduplicate messages - Sequence tracking: Track
seqnumbers per client
π± Running the Example App #
cd example
flutter pub get
flutter run
Note for Android Emulator: Use http://10.0.2.2:3000 instead of localhost:3000
π Security Best Practices #
- Always use TLS in production (
wss://orhttps://) - Store tokens securely using
flutter_secure_storage - Implement token refresh to handle expiry
- Validate server certificates (enabled by default)
- Use CORS properly on server
- Implement rate limiting on server
- Sanitize user input before sending
π Performance Tips #
- Batch messages when sending many at once (configure
batchSize) - Use isolates for heavy JSON parsing if needed
- Limit queue size to prevent memory issues
- Monitor metrics to detect issues early
- Use connection pooling on server
- Implement backpressure if queue grows too large
π Troubleshooting #
Connection keeps dropping #
- Check network stability
- Increase
heartbeatIntervalandheartbeatTimeout - Verify server is responding to heartbeats
Messages not being delivered #
- Check
metricsStreamfor failed send attempts - Verify server is sending ACKs
- Check queue size with
pendingCount
High memory usage #
- Clear old messages from queue periodically
- Reduce
maxRetriesto prevent infinite queue growth - Monitor
currentQueueSizemetric
Auth errors #
- Ensure
authDelegate.refreshAccessToken()is implemented correctly - Check token expiry times
- Verify server accepts refreshed tokens
π License #
MIT License - see LICENSE file for details
π€ Contributing #
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new features
- Ensure all tests pass
- Submit a pull request
π Support #
For issues, questions, or feature requests, please open an issue on GitHub.
Built with β€οΈ for reliable realtime communication in Flutter