pulse_mqtt 1.0.1
pulse_mqtt: ^1.0.1 copied to clipboard
Production-ready Flutter MQTT client with command-based API, retry policies, health monitoring, and auto-reconnect. Dart port of Zomato pulse-droid.
pulse_mqtt #
A robust, production-ready Flutter MQTT client library — a faithful pure-Dart
port of Zomato's pulse-droid Kotlin
library. Built on top of mqtt_client,
it provides a command-based architecture, retry policies, health monitoring, and
automatic reconnection — the same API you know from pulse-droid, just in Dart.
This is the MQTT engine used for live-tracking-style workloads (rider/order location streams, live updates), now usable directly from Flutter with no native code.
Getting started #
Install #
Add pulse_mqtt from pub.dev:
dependencies:
pulse_mqtt: ^1.0.0
Or from GitHub:
dependencies:
pulse_mqtt:
git:
url: https://github.com/Arnoldaditya17/pulse-mqtt-livetracking.git
For local development:
dependencies:
pulse_mqtt:
path: ../pulse-mqtt-livetracking
Then fetch dependencies:
flutter pub get
Platform setup #
connectivity_plus (used for network monitoring) needs the standard platform
permissions:
- Android: the plugin adds
ACCESS_NETWORK_STATEautomatically — no action needed for most apps. - iOS/macOS: no extra setup for connectivity; if you connect over plain
tcp://(non-TLS) you may need to allow it via App Transport Security inInfo.plist.
Import #
import 'package:pulse_mqtt/pulse_mqtt.dart';
Quick start (connect → subscribe → publish) #
final kit = PulseMqttKit();
kit.initialize(MyMqttBridge());
kit.addListener(MyListener());
// 1. Connect
kit.submitCommand(
ConnectCommand(
connectionOptions: ConnectionOptions(
serverUri: 'tcp://broker.hivemq.com:1883',
clientId: 'pulse-flutter-demo',
automaticReconnect: true,
),
retryPolicy: RetryPolicy.exponential(maxRetries: 3, baseDelayMillis: 2000),
),
);
// 2. Subscribe (runs after connect via a dependency)
kit.submitCommand(
SubscribeCommand(
topicConfigs: {
'pulse/demo/location': TopicTypeConfig<String>(qosLevel: QOSLevel.qos0),
},
),
);
// 3. Publish
kit.submitCommand(
PublishCommand(
message: ZMqttMessage(topic: 'pulse/demo/location', payload: 'hi'),
qos: QOSLevel.qos0,
),
);
// 4. Clean up when done
await kit.shutDown();
The full runnable version is in example/lib/main.dart.
1. Initialization #
Implement the PulseMqttKitBridge to provide app-specific dependencies, then
initialize the kit:
final pulseMqttKit = PulseMqttKit();
class MyMqttBridge implements PulseMqttKitBridge {
@override
Logger? getLogger() => MyLogger();
@override
bool get enableJsonDeserialization => true;
@override
HealthMonitoringConfig? getHealthMonitoringConfig() =>
HealthMonitoringConfig(monitoringFreqSeconds: 30);
@override
NetworkMonitoringConfig getNetworkConfig() =>
const NetworkMonitoringConfig(enabled: true);
}
pulseMqttKit.initialize(MyMqttBridge());
Note (vs. Android): the Kotlin
initialize(context, bridge)took an AndroidContext. Flutter has no equivalent, so this takes only the bridge. The bridge also no longer supplies a Gson instance or a coroutine scope — JSON is handled withdart:convert, and the Dart event loop replaces coroutines.
2. Command-Based API #
For every MQTT action, you submit a command:
- Connect:
ConnectCommand(connectionOptions: ..., retryPolicy: ...) - Publish:
PublishCommand(message: ..., qos: ...) - Subscribe:
SubscribeCommand(topicConfigs: ...) - Unsubscribe:
UnsubscribeCommand(topics: ...) - Disconnect:
DisconnectCommand()
pulseMqttKit.submitCommand(command);
Command Dependencies #
Each command supports a dependencies list. A dependent command runs only if
all its dependencies succeed:
final connectCommand = ConnectCommand(
connectionOptions: options,
retryPolicy: RetryPolicy.exponential(),
);
final subscribeCommand = SubscribeCommand(
topicConfigs: {'my/topic': TopicTypeConfig<String>(qosLevel: QOSLevel.qos1)},
dependencies: [connectCommand],
);
pulseMqttKit.submitCommand(subscribeCommand);
Retry Policies #
- Sequential: fixed retry intervals —
RetryPolicy.sequential(...) - Exponential: exponential backoff —
RetryPolicy.exponential(...) - Jitter: adds random jitter —
RetryPolicy.jitter(...) - None: no retry —
RetryPolicy.none()
Exclude specific MQTT exception codes from retrying:
final retryPolicy = RetryPolicy.exponential(
excludedExceptionCodes: {
MqttExceptionCode.reasonCodeNotAuthorized,
MqttExceptionCode.connectAlreadyInProgress,
},
);
3. Auto Subscription #
ConnectionOptions.autoSubscriptionConfig restores subscriptions after a
reconnect automatically:
final options = ConnectionOptions(
serverUri: 'tcp://broker.hivemq.com:1883',
clientId: 'my-client',
automaticReconnect: true,
autoSubscriptionConfig: AutoSubscriptionConfig(
enabled: true,
subscriptionStore: {
'driver/notification':
TopicTypeConfig<String>(qosLevel: QOSLevel.qos2, typeLabel: 'notif'),
'driver/alerts':
TopicTypeConfig<String>(qosLevel: QOSLevel.qos1, typeLabel: 'alert'),
},
),
);
4. Listening for Events #
class MyListener with MqttUpdatesListener {
@override
void onCommandSuccess(MqttCommand command, Success result) { /* ... */ }
@override
void onMqttMessageReceived(String? topic, String? payload,
[TopicMessage? topicMessage]) {
if (topicMessage is Deserialized) {
final data = topicMessage.data; // typed
}
}
}
pulseMqttKit.addListener(MyListener());
// ...
pulseMqttKit.removeListener(MyListener());
Each command result carries number of attempts and total execution time.
5. Type-safe Deserialization #
The Kotlin library used Gson + Class<T> reflection. Dart has no runtime
reflection, so you supply a deserializer function (usually a fromJson
factory) and a typeLabel:
class RiderLocation {
RiderLocation(this.lat, this.lon);
final double lat;
final double lon;
factory RiderLocation.fromJson(Object? json) {
final m = json as Map<String, dynamic>;
return RiderLocation((m['lat'] as num).toDouble(), (m['lon'] as num).toDouble());
}
}
final config = TopicTypeConfig<RiderLocation>(
typeLabel: 'RiderLocation',
deserializer: RiderLocation.fromJson,
qosLevel: QOSLevel.qos0,
);
When no deserializer is set (or enableJsonDeserialization is false), messages
arrive as Plain(String).
6. Health Monitoring #
pulseMqttKit.startHealthMonitoring();
// ...
pulseMqttKit.stopHealthMonitoring();
Note (vs. Android): the Kotlin library backed health checks with WorkManager and AlarmManager. Flutter has no portable in-app equivalent, so both
HealthMonitoringTypes use a periodicTimerthat runs while the app process is alive. For execution when the app is killed, driveHealthMonitoringConfig.healthCheckfrom a background plugin such asworkmanagerorflutter_background_service.
7. Shutting Down #
await pulseMqttKit.shutDown();
Unsubscribes from all topics, disconnects, stops monitoring, and clears listeners.
Porting notes (Kotlin → Dart) #
| pulse-droid (Kotlin) | pulse_mqtt (Dart) |
|---|---|
| Eclipse Paho | mqtt_client |
Gson + Class<T> |
TopicTypeConfig.deserializer + dart:convert |
Kotlin coroutines / CoroutineScope |
Future / async (event loop) |
| WorkManager / AlarmManager | periodic Timer (TimerMonitoringProvider) |
Android ConnectivityManager |
connectivity_plus |
initialize(context, bridge) |
initialize(bridge) |
sealed CommandResult subclasses |
Success / Failure / Ignored / None |
The command model, retry semantics, dependency resolution, auto-subscription, and listener callbacks are preserved 1:1.
See example/lib/main.dart for a full connect →
subscribe → publish demo.
Credits #
This package is a Flutter/Dart port of pulse-droid, the original Kotlin MQTT library by Zomato, built on Eclipse Paho. All of the architecture — the command model, retry policies, auto-subscription, health monitoring, and listener/bridge design — originates from that project. Full credit for the design and API goes to the Zomato team and the pulse-droid authors.
- Parent project: https://github.com/Zomato/pulse-droid
- License: see the parent project's
LICENSES/directory.
This Dart port additionally builds on the excellent
mqtt_client and
connectivity_plus packages.