Courier is a library for creating long running connections using MQTT which is the industry standard for IoT Messaging. Long running connection is a persistent connection established between client & server for bi-directional communication. A long running connection is maintained for as long as possible with the help of keepalive packets for instant updates. This also saves battery and data on mobile devices.
Find the detailed documentation here - https://gojek.github.io/courier-flutter/
End-to-end courier example - https://gojek.github.io/courier/docs/Introduction
Setup Courier to subscribe, send, and receive message with bi-directional long running connection between iOS device and broker.
A sample application is added here which makes Courier connection with a HiveMQ public broker. It demonstrates multiple functionalities of Courier like Connect, Disconnect, Publish, Subscribe and Unsubscribe.
Running sample app
- Clone the project from GitHub
- Open
courier_dart_sdk_demo
folder - Run
flutter run
Run this command:
With Flutter:
$ flutter pub add courier_flutter
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):
dependencies:
courier_flutter: 0.0.18
Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:courier_dart_sdk/courier_client.dart';
Add the following snippet to Podfile in your project's iOS folder:
pod 'CourierCore', '0.0.19', :modular_headers => true
pod 'CourierMQTT', '0.0.19', :modular_headers => true
CourierClient
is the class that we use for any MQTT tasks such as connection, subscription, send and receive message. To initialize an instance of CourierClient we can use the static method like so.
final CourierClient courierClient = CourierClient.create(
authProvider: DioAuthProvider(
dio: Dio(),
tokenApi: apiUrl,
authResponseMapper: CourierResponseMapper()),
config: CourierConfiguration(
authRetryPolicy: DefaultAuthRetryPolicy(),
readTimeoutSeconds: 60,
messageAdapters: const <MessageAdapter>[
JSONMessageAdapter(),
BytesMessageAdapter(),
StringMessageAdapter()
])
);
This is an interface containing method to fetchConnectOptions used by the Client to connect to broker
- authRetryPolicy: Retry policy used to handle retry when tokenAPI URL fails
- messageAdapters: List of MessageAdapter used to encode model to bytes and decode bytes to object. Prioritization will be based on the order of the adapter in the list. We provide built-in adapters such as:
JSONMessageAdapter
,BytesMessageAdapter
, &StringMessageAdapter
. Optionally, you can also depends oncourier_protobuf
lib to useProtobufMessageAdapter
.
To fetch ConnectionCredential (host, port, etc) from HTTP endpoint, you can use DioAuthProvider
passing these params.
-
Dio : We use dio package for making HTTP request. This will provide you flexibility to use your own Dio instance in case you have various custom headers need to be sent to the server (e.g Authentication, etc).
-
tokenApi: An endpoint URL that returns JSON containing credential for mapping to
CourierConnectOptions
. -
authResponseMapper: Instance of
AuthResponseMapper
for mapping JSON returned bytokenAPI
URL toCourierConnectOptions
.
To connect to MQTT broker you need to provide an endpoint URL that returns JSON containing these payload.
{
"clientId": "randomcourier1234567",
"username": "randomcourier1234567",
"password": "randomcourier4321",
"host": "broker.mqttdashboard.com",
"port": 1883,
"cleanSession": true,
"keepAlive": 45
}
You need to create and implement AuthResponseMapper
to map the JSON to the CourierConnectOptions
instance.
class CourierResponseMapper implements AuthResponseMapper {
@override
CourierConnectOptions map(Map<String, dynamic> response) => CourierConnectOptions(
clientId: response["clientId"],
username: response["username"],
host: response["host"],
port: response["port"],
cleanSession: response["cleanSession"],
keepAliveSeconds: response["keepAlive"],
password: response['password']
);
}
In case you want to fetch the connect options using your own implementation, you can implement AuthProvider interface like so.
// Example of fetching connectOptions locally without making remote HTTP API Call.
class LocalAuthProvider implements AuthProvider {
final CourierConnectOptions connectOptions;
LocalAuthProvider({required this.connectOptions});
@override
Future<CourierConnectOptions> fetchConnectOptions() {
return Future<CourierConnectOptions>.value(connectOptions);
}
}
final CourierClient courierClient = CourierClient.create(
authProvider: LocalAuthProvider(
connectOptions: CourierConnectOptions(
clientId: const Uuid().v4(),
username: "randomcourier1234567",
host: "broker.mqttdashboard.com",
port: 1883,
cleanSession: true,
keepAliveSeconds: 45,
password: "1234")),
config: CourierConfiguration(
authRetryPolicy: DefaultAuthRetryPolicy(),
readTimeoutSeconds: 60,
),
//...
);
To connect to the broker, you simply need to invoke connect
method
courierClient.connect();
To disconnect, you just need to invoke disconnect
method
courierClient.disconnect();
As MQTT supports QoS 1 and QoS 2 message to ensure deliverability when there is no internet connection and user reconnected back to broker, we also persists those message in local cache. To disconnect and remove all of this cache, you can invoke.
courierClient.destroy();
Courier internally handles reconnection in case of bad/lost internet connection.
The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message. There are 3 QoS levels in MQTT:
- At most once (0)
- At least once (1)
- Exactly once (2).
When you talk about QoS in MQTT, you need to consider the two sides of message delivery:
- Message delivery form the publishing client to the broker.
- Message delivery from the broker to the subscribing client.
You can read more about the detail of QoS in MQTT from HiveMQ site.
To subscribe to a topic from the broker, invoke susbscribe
method on CourierClient passing the topic string and QoS.
courierClient.subscribe("chat/user1", QoS.one);
After you have subscribed to the topic, you need to listen to a message stream passing the associated topic. courierMessageStream
will loop message adapters trying to decode the data into specified type, the first one that is able to decode, will be used. You will need pass a decoder parameter to return instance of your object given 1 dynamic parameter depending on the adapter (JSONMessageAdapter pass you Map<String, dynamic>
, BytesMessageAdapter pass you Uint8List
)
Optionally you can pass MessageAdapter
If this is passed, it will use it decode the data to `T`` type, otherwise it will use the messageAdapters list passed when initializing CourierClient.
/// This uses BytesMessageAdapter passed when initializing CourierClient and constructor tear-offs TestData.fromBytes
courierClient
.courierMessageStream<TestData>(
"orders/6b57d4e5-0fce-4917-b343-c8a1c77405e5/update",
decoder: TestData.fromBytes)
.listen((event) {
print("Message received testData: ${event.textMessage}");
});
/// This uses passed JSONMessageAdapter and constructor tear-offs Person.fromJson
courierClient
.courierMessageStream<Person>(
"person/6b57d4e5-0fce-4917-b343-c8a1c77405e5/update",
decoder: Person.fromJson,
adapter: const JSONMessageAdapter())
.listen((person) {
print("Message received person: ${person.name}");
After you have subscribed to the topic, you need to listen to a message stream passing the associated topic. The type of the parameter in the courierBytesStream
listen callback is byte array UInt8List
.
courierClient.courierBytesStream("chat/user1").listen((message) {
print("Message received: ${event}");
});
To unsubscribe from a topic, simply invoke unsubscribe
passing the topic string.
courierClient.unsubscribe("chat/user/1");
To publish message to the broker, you need can pass your object instance, it will try to loop all message adapters to encode the instance into Uint8List, the first one that is able to encode, will be used. You can also pass an optional, encoder function that will pass your instance as dynamic
which you can use to call your own method to encode to Uint8List
You need to initalize CourierMessage
instance passing the payload
, topic
string, and qos
like so. Finally, you need to invoke publishCourierMessage
on CourierClient
passing the message.
Optionally you can pass MessageAdapter
If this is passed, it will use it encode the data to Uint8List, otherwise it will use the messageAdapters list passed when initializing CourierClient.
/// This used JSONMessageAdapter which use dart jsonEncode to invoke toJson on object implicitly
courierClient.publishCourierMessage(CourierMessage(
payload: Person(name: textMessage),
topic: "person/6b57d4e5-0fce-4917-b343-c8a1c77405e5/update",
qos: QoS.one));
/// For this TestData without toJson method, you can provide your own encode to convert to Uint8List/bytes
courierClient.publishCourierMessage(
CourierMessage(
payload: testData,
topic: "orders/6b57d4e5-0fce-4917-b343-c8a1c77405e5/update",
qos: QoS.one),
encoder: (testData) => testData.toBytes());
Courier provides ability to register and listen to library events emitted by the Client (connect attempt/success/failure, message send/receive, subscribe/unsubscribe). All you need to do is listen to courierEventStream
from CourieClient
.
courierClient.courierEventStream().listen((event) {
print("Event received: ${event}");
});
The type of the parameter is an abstract class CourierEvent
with these public interfaces:
- name: Name of the event.
- connectionInfo Connection Info tied to the event
- getEventPropertiesMap: A key value map containing the properties related to the event.
You can try to Cast the CourierEvent
to concrete implementation that provides additional properties such as:
- MQTTConnectAttemptEvent
- MQTTConnectSuccessEvent
- MQTTConnectFailureEvent
- ...
This can be used to inspects all the outgoing or incoming packets for an underlying Courier MQTT connection. It intercepts all the packets, persisting them and providing a UI for accessing all the MQTT packets sent or received. It also provides multiple other features like search, share, and clear data.
Uses the native Android Notification and launchable Activity Intent from the notification banner. You need to pass enableMQTTChuck
flag as true
to CourierConfiguration
when initializing CourierClient
instance. Make sure you also request permission notification and it is granted by the user.
Uses embedded flutter host native view, it uses SwiftUI under the hood and require minimum version of iOS 15. You can simply import MQTTChuckView
and use it in your Flutter App.
import 'package:courier_dart_sdk/chuck/mqtt_chuck_view.dart';
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const MQTTChuckView()),
);
Read our contribution guide to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to Courier iOS library.
All Courier modules are MIT Licensed.