Files
Xpress-rider/lib/utils/mqtt_service.dart

196 lines
6.1 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_server_client.dart';
import 'package:nearle/views/helpers/constants/mqtt_constants.dart';
import 'package:shared_preferences/shared_preferences.dart';
class NearleMqttService {
static final NearleMqttService _instance = NearleMqttService._internal();
factory NearleMqttService() {
return _instance;
}
NearleMqttService._internal();
MqttServerClient? _client;
bool _isConnected = false;
bool _isConnecting = false; // Prevents concurrent connection attempts
String? _currentRiderId;
// Unique per isolate startup — prevents client ID collision between
// main isolate and background foreground-service isolate.
static final String _sessionSuffix =
DateTime.now().millisecondsSinceEpoch.toRadixString(36);
bool get isConnected => _isConnected;
Future<void> connect() async {
if (_isConnected || _isConnecting) return;
_isConnecting = true;
try {
final prefs = await SharedPreferences.getInstance();
final riderIdLong = prefs.getInt('userId') ?? prefs.getInt('userid');
if (riderIdLong == null || riderIdLong <= 0) {
debugPrint('[MQTT] Cannot connect: No rider ID found in preferences.');
return;
}
_currentRiderId = riderIdLong.toString();
const host = MqttConstants.brokerHost;
final clientId = 'rider_${_currentRiderId}_$_sessionSuffix';
_client = MqttServerClient(host, clientId);
_client!.port = MqttConstants.brokerPort;
_client!.keepAlivePeriod = 30;
_client!.autoReconnect = true;
_client!.logging(on: false);
final lwtTopic =
MqttConstants.topicRiderStatus.replaceAll('{riderId}', _currentRiderId!);
_client!.onDisconnected = _onDisconnected;
_client!.onConnected = _onConnected;
_client!.onAutoReconnect = _onAutoReconnect;
_client!.onSubscribed = _onSubscribed;
final connMessage = MqttConnectMessage()
.withClientIdentifier(clientId)
.authenticateAs(MqttConstants.username, MqttConstants.passwordString)
.withWillTopic(lwtTopic)
.withWillMessage(MqttConstants.statusOffline)
.withWillQos(MqttQos.atLeastOnce)
.withWillRetain()
.startClean();
_client!.connectionMessage = connMessage;
debugPrint('[MQTT] Connecting to $host as $clientId...');
await _client!.connect();
} catch (e) {
debugPrint('[MQTT] Connection failed: $e');
_cleanDisconnect();
} finally {
_isConnecting = false;
}
}
/// Cleanly disconnects and resets all state. Call on logout or duty end.
void disconnect() {
_publishStatus(MqttConstants.statusOffline);
_cleanDisconnect();
debugPrint('[MQTT] Disconnected and state cleared.');
}
void _cleanDisconnect() {
_client?.disconnect();
_client = null;
_isConnected = false;
_currentRiderId = null;
}
void _onConnected() {
_isConnected = true;
debugPrint('[MQTT] Connected successfully.');
_publishStatus(MqttConstants.statusOnline);
}
void _onDisconnected() {
_isConnected = false;
debugPrint('[MQTT] Disconnected from broker.');
}
void _onAutoReconnect() {
debugPrint('[MQTT] Auto-reconnecting...');
}
void _onSubscribed(String topic) {
debugPrint('[MQTT] Subscribed to topic: $topic');
}
void _publishStatus(String status) {
if (!_isConnected || _currentRiderId == null) return;
final topic =
MqttConstants.topicRiderStatus.replaceAll('{riderId}', _currentRiderId!);
final builder = MqttClientPayloadBuilder();
builder.addString(status);
_client!.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!,
retain: true);
debugPrint('[MQTT] Published status: $status to $topic');
}
// --- PUBLIC API ---
void updateStatus(String status) {
_publishStatus(status);
}
void publishProfile(Map<String, dynamic> profileData) {
if (!_isConnected || _currentRiderId == null) return;
final topic =
MqttConstants.topicRiderProfile.replaceAll('{riderId}', _currentRiderId!);
final builder = MqttClientPayloadBuilder();
builder.addString(jsonEncode(profileData));
_client!.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!,
retain: true);
debugPrint('[MQTT] Published profile to $topic');
}
void publishLocation(Map<String, dynamic> locationData) {
if (!_isConnected || _currentRiderId == null) return;
final topic =
MqttConstants.topicRiderLocation.replaceAll('{riderId}', _currentRiderId!);
final builder = MqttClientPayloadBuilder();
builder.addString(jsonEncode(locationData));
_client!.publishMessage(topic, MqttQos.atMostOnce, builder.payload!);
}
void publishTelemetry(Map<String, dynamic> telemetryData) {
if (!_isConnected || _currentRiderId == null) return;
final topic =
MqttConstants.topicRiderTelemetry.replaceAll('{riderId}', _currentRiderId!);
final builder = MqttClientPayloadBuilder();
builder.addString(jsonEncode(telemetryData));
_client!.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!);
debugPrint('[MQTT] Published telemetry data.');
}
void publishLog(String eventName, Map<String, dynamic> data) {
if (!_isConnected || _currentRiderId == null) return;
final topic =
'${MqttConstants.topicRiderLogs.replaceAll('{riderId}', _currentRiderId!)}/$eventName';
final builder = MqttClientPayloadBuilder();
builder.addString(jsonEncode(data));
_client!.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!);
debugPrint('[MQTT] Published log: $eventName');
}
void publish(String subTopic, dynamic data) {
if (!_isConnected || _currentRiderId == null) return;
final topic = 'nearle/riders/$_currentRiderId/$subTopic';
final builder = MqttClientPayloadBuilder();
if (data is String) {
builder.addString(data);
} else {
builder.addString(jsonEncode(data));
}
_client!.publishMessage(topic, MqttQos.atLeastOnce, builder.payload!);
debugPrint('[MQTT] Published to $topic');
}
}