utd_chat_kit 0.9.2 copy "utd_chat_kit: ^0.9.2" to clipboard
utd_chat_kit: ^0.9.2 copied to clipboard

WhatsApp-style 1:1 & group chat for Flutter — independent UTD-Stream kit.

utd_chat_kit #

WhatsApp-style 1:1 & group chat for Flutter — an independent UTD-Stream kit. Local-first (Drift) chat with an optimistic outbox, a signalling WebSocket, group roles, presence, typing indicators, media attachments, and (v0.9) background message push + deep-linking.

Usage #

final client = await UTDChat.init(
  UTDChatConfig(
    appId: 'your_app_id',
    appKey: 'your_app_key',
    userId: 'user-123',
    userName: 'Sara',
    // push is on by default; pass UTDPushConfig(enabled: false) to opt out.
  ),
);

// Conversation list / chat thread, already scoped:
Navigator.of(context).push(
  MaterialPageRoute(builder: (_) => UTDChat.conversations(client)),
);

Capabilities & billing #

Chat is the paid chat capability (persistent messaging + history + receipts), billed separately from signaling (calls). Both are sold via a subscription plan (Free / Signaling / Pro / Enterprise) with per-plan limits; billing is MAU-based — there is no per-message charge.

The session mint surfaces the chat flag + plan limits on the client (all nullable — null when the engine didn't report it, on an older engine):

// Proactive pre-check (set on connect):
client.chatEnabled.value; // bool? — null = unknown, false = show an upsell

// Enforce caps client-side for UX + show history retention:
final limits = client.planLimits;      // UTDPlanLimits?
final maxMembers = limits?.maxGroupMembers; // cap the group-member picker
final historyDays = limits?.historyDays;    // "messages are kept N days"

UTDChatClient.messagingUnavailable remains the reactive, after-a-forbidden-send signal; chatEnabled is the proactive pre-check. Absent flags never hard-break the kit — it degrades to server-authoritative enforcement.

Media attachments (v0.6) #

The chat thread (UTDChatScreen) ships with an attach button that lets users send photos (gallery/camera), videos, and arbitrary files. Attachments are uploaded to the engine's R2 storage via the presign endpoint (POST /api/v1/signal/uploads) and sent as a message of type image/file/audio/video with metadata.url = object_url.

Required host-app configuration #

Media pickers/camera require platform permissions declared by your app:

iOS — add to ios/Runner/Info.plist:

<key>NSCameraUsageDescription</key>
<string>Take photos to send in chat</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Attach photos and videos to messages</string>
<key>NSMicrophoneUsageDescription</key>
<string>Record audio/video to send in chat</string>

Androidimage_picker and file_picker declare what they need; for camera capture add to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />

Engine prerequisite #

The engine project's R2/object-storage env must be provisioned. If it is not, POST /api/v1/signal/uploads returns 503, surfaced as a UTDStreamException with code == UTDErrorCode.uploadsUnavailable (and a friendly snackbar in the default UI).

Headless use #

To send media without the default UI, drive UTDUploadController directly:

final uploads = UTDUploadController(
  client: chatClient,
  conversationId: conversationId,
  peerIdentity: peerIdentity, // null for a group
);
await uploads.pickImage(); // or captureImage / pickVideo / pickFile

The kit registers this device's FCM token with the engine after connect() (POST /api/v1/devices/push-token, identity-bound to the minted bearer), keeps it fresh on onTokenRefresh, and unregisters it on dispose(). Incoming {type:'message', conversation_id, ...} pushes are handled on all three FCM surfaces:

  • Foreground (onMessage) — shows a local-notification banner unless the user is currently viewing that conversation (then it's suppressed).
  • Background tap (onMessageOpenedApp) and cold start (getInitialMessage) — deep-link to the chat screen.
  • Terminated/background data messages — a top-level utdChatFirebaseBackgroundHandler renders a banner; tapping it deep-links.

Push is on by default; set UTDChatConfig(push: UTDPushConfig(enabled: false)) to disable all FCM wiring.

Required host-app configuration #

Push requires your app to set up Firebase and wire two hooks. The kit does NOT bundle a Firebase config (that is per-app).

1. Add Firebase to your app (Android + iOS) with the FlutterFire CLI:

dart pub global activate flutterfire_cli
flutterfire configure   # writes firebase_options.dart + native config

This drops android/app/google-services.json and ios/Runner/GoogleService-Info.plist and configures the Gradle/CocoaPods plugins. (iOS additionally needs the Push Notifications capability and an APNs key/cert uploaded to the Firebase project.)

2. Initialize Firebase and register the background handler before runApp:

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:utd_chat_kit/utd_chat_kit.dart';
import 'firebase_options.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  // MUST be a top-level function, registered before runApp.
  FirebaseMessaging.onBackgroundMessage(utdChatFirebaseBackgroundHandler);
  runApp(const MyApp());
}

3. Supply a navigation handler so a notification tap opens the right thread. The router queues the deep-link until the client is connected, so register it once after UTDChat.init:

client.notificationRouter.setHandler((link) {
  navigatorKey.currentState?.push(MaterialPageRoute(
    builder: (_) => UTDChat.chat(
      client,
      conversationId: link.conversationId,
      peerIdentity: link.senderIdentity ?? '',
    ),
  ));
});

iOS — add to ios/Runner/Info.plist if you want the banner while the app is foregrounded, and ensure the Push Notifications + Background Modes (Remote notifications) capabilities are enabled in Xcode.

Androidflutter_local_notifications posts on the channel the kit creates (utd_chat_messages by default; override via UTDPushConfig). On Android 13+ the kit requests the POST_NOTIFICATIONS permission at connect time.

Engine prerequisite (server-side gate) #

The engine project's FCM/APNs env must be provisioned. Until then, token registration still succeeds (the token is stored) but the engine sends no pushes — there is nothing to runtime-verify on the device until ops enables it.

Additional information #

This kit is independent of utd_stream_sdk. The two-host transport (mint host + engine host), the no-backend X-App-Key mint, and the device-id model match the sibling UTD-Stream kits.