courier-flutter
courier-flutter copied to clipboard
Dart port of our popular courier library
Overview
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.
Detailed Documentation
Find the detailed documentation here - https://gojek.github.io/courier-flutter/
End-to-end courier example - https://gojek.github.io/courier/docs/Introduction
Getting Started
Setup Courier to subscribe, send, and receive message with bi-directional long running connection between iOS device and broker.
Sample App
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_demofolder - Run
flutter run
Installation
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.
Import it
Now in your Dart code, you can use:
import 'package:courier_dart_sdk/courier_client.dart';
To integrate iOS Courier SDK
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
Create CourierClient
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()
])
);
AuthProvider
This is an interface containing method to fetchConnectOptions used by the Client to connect to broker
Required Configs
- 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_protobuflib to useProtobufMessageAdapter.
Setup CourierClient with DioAuthProvider
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
AuthResponseMapperfor mapping JSON returned bytokenAPIURL toCourierConnectOptions.
Providing Token API URL with JSON Credential Response
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
}
Map JSON to CourierConnectOptions
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']
);
}
Setup CourierClient with your own AuthProvider
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,
),
//...
);
Managing Connection Lifecycle in CourierClient
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.
QoS level in MQTT
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.
Subscribe to topics from Broker
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);
Received Message from Subscribed Topic
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}");
Receive Bytes(Uint8List) from Subscribed Topic
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}");
});
Unsubscribe from topics
To unsubscribe from a topic, simply invoke unsubscribe passing the topic string.
courierClient.unsubscribe("chat/user/1");
Send Message to Broker
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());
Listening to Courier System Events
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
- ...
MQTT Chuck
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.
Android MQTT Chuck
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.
iOS MQTT Chuck
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()),
);
Contribution Guidelines
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.
License
All Courier modules are MIT Licensed.