art_adk 1.0.2 copy "art_adk: ^1.0.2" to clipboard
art_adk: ^1.0.2 copied to clipboard

Flutter SDK for ART realtime messaging — WebSocket channels, presence tracking, end-to-end encrypted channels, and CRDT-backed shared objects.

pub.dev License: MIT Platform

Flutter SDK for ART — A Realtime Tech communication,, a realtime messaging platform providing WebSocket-based channels, presence tracking, end-to-end encrypted messaging, and CRDT-backed shared objects.

Features #

  • WebSocket connection management — connect, pause, resume, and auto-reconnect with exponential backoff
  • Channel subscriptions — default, targeted, group, secure (encrypted), and shared-object (CRDT) channels
  • Push messages — send structured payloads with optional per-user targeting
  • Event listening — receive every message with listen() or bind to named events via emitter.on()
  • Presence tracking — observe users online on a channel in real time
  • End-to-end encryption — crypto applied transparently on secure channels
  • Interceptors — hook into the message pipeline to resolve or reject payloads
  • Shared objects (CRDT) — collaborative state with automatic conflict resolution

Installation #

Add art_adk to your pubspec.yaml:

dependencies:
  art_adk: ^1.0.2

Fetch packages:

flutter pub get

Import the SDK where you need it:

import 'package:art_adk/art_adk.dart';

Configuration #

1. Create assets/adk-services.json #

Generate client credentials from the ART Live Dashboard and save them under assets/adk-services.json:

{
  "Client-ID": "xxxxxxxx",
  "Client-Secret": "xxxxxxxxxxxxxxxx",
  "Org-Title": "YOUR_ORG",
  "ProjectKey": "YOUR_PROJECT_KEY",
  "Environment": "YOUR_ENV"
}

Register the asset in your app's pubspec.yaml:

flutter:
  assets:
    - assets/adk-services.json

See Workspace and Project for how to obtain ProjectKey and Environment.

2. Load the credentials #

import 'dart:convert';
import 'package:flutter/services.dart' show rootBundle;
import 'package:art_adk/art_adk.dart';

Future<CredentialStore> loadCredentials() async {
  final raw = await rootBundle.loadString('assets/adk-services.json');
  final json = jsonDecode(raw) as Map<String, dynamic>;
  return CredentialStore(
    environment:  json['Environment']   as String,
    projectKey:   json['ProjectKey']    as String,
    orgTitle:     json['Org-Title']     as String,
    clientID:     json['Client-ID']     as String,
    clientSecret: json['Client-Secret'] as String,
  );
}

3. Get a user passcode #

The art_adk package authenticates with ART using a short-lived passcode issued for a single user:

import 'dart:convert';
import 'package:http/http.dart' as http;

Future<String> fetchPasscode(CredentialStore creds) async {
  final response = await http.post(
    Uri.parse('https://dev.arealtimetech.com/ws/v1/connect/passcode'),
    headers: <String, String>{
      'Client-Id':     creds.clientID,
      'Client-Secret': creds.clientSecret,
      'X-Org':         creds.orgTitle,
      'Environment':   creds.environment,
      'ProjectKey':    creds.projectKey,
      'Content-Type':  'application/json',
    },
    body: jsonEncode(<String, dynamic>{
      'username':   'jane_doe',
      'first_name': 'Jane',
      'last_name':  'Doe',
    }),
  );

  final decoded = jsonDecode(response.body) as Map<String, dynamic>;
  final data    = decoded['data'] as Map<String, dynamic>;
  return data['passcode'] as String;
}

Quick start #

import 'package:art_adk/art_adk.dart';

Future<void> main() async {
  final creds    = await loadCredentials();                 // from asset JSON
  final passcode = await fetchPasscode(creds);              // from your auth service
  final updated  = creds.copyWith(accessToken: passcode);

  final adk = Adk(
    adkConfig: AdkConfig(
      uri:            'ws.arealtimetech.com',
      authToken:      passcode,
      getCredentials: () => updated,
    ),
  );

  adk.on('connection', (dynamic data) {
    if (data is ConnectionDetail) {
      debugPrint('connected → ${data.connectionId}');
    }
  });
  adk.on('close', (dynamic reason) => debugPrint('closed: $reason'));

  await adk.connect();

  final sub = await adk.subscribe(channel: 'room-42');
  sub.emitter.on('message', (dynamic data) {
    debugPrint('received: $data');
  });

  await sub.push(
    event: 'message',
    data:  <String, dynamic>{'text': 'Hello ART!'},
  );
}

Connecting #

final adk = Adk(
  adkConfig: AdkConfig(
    uri:            'ws.arealtimetech.com', // scheme optional; wss:// is added automatically
    authToken:      passcode,
    getCredentials: () => updatedCreds,
  ),
);

await adk.connect();

For short-lived workflows, wrap connect / disconnect in try / finally:

try {
await adk.connect();
// your logic here
} finally {
await adk.disconnect();
}

Read the current state at any time:

adk.getState(); // 'connected' | 'retrying' | 'paused' | 'stopped'

Subscribing to a channel #

adk.subscribe returns a BaseSubscription. For default / secure / targeted / group channels it is a Subscription; for shared-object channels it is a LiveObjSubscription.

final BaseSubscription sub = await adk.subscribe(channel: 'room-42');

if (sub is LiveObjSubscription) {
  // Shared-object (CRDT) channel — use sub.state(), sub.query(), sub.flush().
}

Unsubscribe when you no longer need the channel:

await sub.unsubscribe();

Pushing messages #

await sub.push(
  event: 'message',
  data:  <String, dynamic>{'text': 'Hello'},
);

Target a specific user (required for secure and targeted channels — exactly one recipient):

await sub.push(
  event:   'message',
  data:    <String, dynamic>{'text': 'Hi Bob'},
  options: const PushConfig(to: <String>['bob']),
);

Attempting to target zero or more than one recipient on a secure / targeted channel throws ARTError.serverError.

Receiving messages #

Bind to a specific event on the subscription's emitter:

sub.emitter.on('message', (dynamic data) {
  debugPrint('got: $data');
});

Or, on default channels, cast to Subscription and stream every event:

if (sub is Subscription) {
  sub.listen((Map<String, dynamic> data) {
    debugPrint("event=${data['event']} content=${data['content']}");
  });
}

Unbind a specific listener:

if (sub is Subscription) {
  sub.remove(event: 'message');
}

Presence #

Track who is online on a channel:

final cancelPresence = await sub.fetchPresence(
  callback: (List<String> users) {
    debugPrint('online: $users');
  },
);

// later:
await cancelPresence();

Presence tracking must be enabled on the channel in the ART dashboard.

Encrypted channels #

Secure channels apply end-to-end NaCl-box encryption per message. After connecting, generate (or set) a key pair — the SDK publishes the public half automatically:

await adk.generateKeyPair();          // creates + uploads a fresh KeyPair
// or
await adk.setKeyPair(existingPair);   // adopt an externally-managed KeyPair

Then push as usual. Encryption and decryption happen transparently:

final secure = await adk.subscribe(channel: 'YOUR_SECURE_CHANNEL');

await secure.push(
  event:   'message',
  data:    <String, dynamic>{'text': 'This is private'},
  options: const PushConfig(to: <String>['bob']), // secure requires exactly one recipient
);

secure.emitter.on('message', (dynamic data) {
  debugPrint('decrypted: $data');
});

Interceptors never see encrypted channel traffic — ART preserves message privacy end-to-end.

Shared object channels (CRDT) #

Shared-object channels expose a JSON-like document that every subscriber edits concurrently; conflicts are resolved automatically via the SDK's CRDT engine.

final sub = await adk.subscribe(channel: 'YOUR_CRDT_CHANNEL');

if (sub is LiveObjSubscription) {
  // 1. Write
  sub.state()['document']['title'].set('My Doc');
  await sub.flush();

  // 2. Read once
  final snapshot = await sub.query(path: 'document').execute();
  debugPrint('doc: $snapshot');

  // 3. Observe
  final dispose = await sub.query(path: 'document').listen((dynamic data) {
    debugPrint('document updated: $data');
  });

  // later:
  dispose();
}

Array operations #

final items = sub.state()['items']; // CRDTProxy

items.push('alpha');                // append
items.unshift('zero');              // prepend
items.pop();                        // remove last
items.removeAt(2);                  // remove by index
items.splice(start: 1, deleteCount: 2, insert: <dynamic>['x', 'y']);

await sub.flush();
debugPrint('length: ${items.length}');

Pending local writes are auto-batched and flushed every 50 ms; call flush() to force an immediate merge.

Interceptors #

Interceptors let you plug custom logic into the server-side message pipeline. Each intercepted message must be resolved (forwarded) or rejected.

await adk.intercept(
  interceptor: 'profanity-filter',
  fn: (
    Map<String, dynamic> payload,
    void Function(dynamic data) resolve,
    void Function(String error) reject,
  ) {
    final content = payload['data'];
    if (content is Map && '${content['text']}'.contains('anyword')) {
      reject('blocked by profanity filter');
      return;
    }
    resolve(payload);
  },
);

Encrypted channels are never routed through interceptors.

Connection lifecycle #

// Life-cycle listeners
adk.on('connection', (dynamic data) {
if (data is ConnectionDetail) {
debugPrint('connectionId=${data.connectionId}');
}
});
adk.on('close', (dynamic reason) => debugPrint('closed: $reason'));

// Manual controls
adk.pause();               // tear the socket down, keep subscriptions
await adk.resume();        // bring it back
await adk.disconnect();    // full teardown

Reconnection is automatic — up to 5 attempts with a 3–5 s backoff, after which the state becomes stopped and you must call connect() manually.

Common codes: notConnected, authenticationFailed, serverError, encryptionError, decryptionError, timeout, channelNotFound, forbidden.

API reference #

Class / type Purpose
Adk Top-level facade — connect, subscribe, intercept, key management
AdkConfig Connection configuration (URI, credentials, auth token)
CredentialStore Immutable holder for org / project / client credentials
BaseSubscription Base type for all channel subscriptions (push, fetchPresence, unsubscribe)
Subscription Default / secure / targeted / group channel — listen(), bind(), emitter.on()
LiveObjSubscription CRDT-backed shared-object channel — state(), query(), flush()
CRDTProxy Chainable accessor for reading / mutating CRDT state
PushConfig Push options — to: List<String> for targeted delivery
KeyPairType KeyPairType for secure channels
ConnectionDetail Emitted on 'connection' — contains connectionId, instanceId, tenant info

Documentation #

Full documentation is available at docs.arealtimetech.com/docs/adk.

Topic Link
Overview ADK Overview
Installation Flutter Installation
Publish & Subscribe Pub/Sub Docs
Connection Management Connection Docs
User Presence Presence Docs
Encrypted Channels Encryption Docs
Shared Object Channels Shared Object Docs
Interceptors Interceptor Docs

License #

Released under the MIT License.

4
likes
160
points
259
downloads

Documentation

Documentation
API reference

Publisher

verified publisherarealtimetech.com

Weekly Downloads

Flutter SDK for ART realtime messaging — WebSocket channels, presence tracking, end-to-end encrypted channels, and CRDT-backed shared objects.

Homepage

Topics

#websocket #realtime #messaging #pubsub #flutter

License

MIT (license)

Dependencies

flutter, http, pinenacl, web_socket_channel

More

Packages that depend on art_adk