gojek / courier-flutter

Dart port of our popular courier library
43 stars 8 forks source link

Build and Smoketest Status

Documentation
</a>
<a href="https://github.com/gojek/courier-flutter/graphs/commit-activity">
    <img alt="Maintenance" src="https://img.shields.io/badge/maintained-yes-green.svg" target="_blank" />
</a>

GitHub Release Date GitHub last commit Discord : Gojek Courier

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

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

Setup CourierClient with DioAuthProvider

To fetch ConnectionCredential (host, port, etc) from HTTP endpoint, you can use DioAuthProvider passing these params.

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:

When you talk about QoS in MQTT, you need to consider the two sides of message delivery:

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:

You can try to Cast the CourierEvent to concrete implementation that provides additional properties such as:

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.