ConvoKit Flutter SDK

Flutter SDK for adding ConvoKit conversations, messages, realtime events, and media uploads to a Flutter app.

The SDK talks to your ConvoKit backend over REST and uses Supabase Realtime for live messaging events. Your ConvoKit client secret should stay on your server; mobile and web clients should only receive short-lived user tokens from your own token endpoint.

Features

  • Configure a ConvoKit app client and connect an app user.
  • Create, fetch, archive, unarchive, and leave conversations.
  • Send and paginate messages with text, image, and file media.
  • Subscribe to realtime message, typing, read receipt, and presence events.
  • Upload user avatars, conversation images, and message attachments through signed Cloudinary upload URLs.
  • Use typed Dart models for Conversation, Message, and Participant.

Installation

Add the package to your Flutter app:

flutter pub add convokit_flutter

Then import it:

import 'package:convokit_flutter/convokit_flutter.dart';

Setup

Configure ConvoKit once when your app starts. The tokenProvider must call your own backend and return a ConvoKit JWT for the current app user. Do not embed your ConvoKit client secret in a Flutter app.

import 'dart:convert';

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

Future<void> bootstrapConvoKit() async {
  ConvoKit.configure(
    backendUrl: 'https://api.your-convokit-backend.com',
    clientId: 'your-convokit-client-id',
    tokenProvider: (appUserId) async {
      final response = await http.post(
        Uri.parse('https://your-api.example.com/convokit/token'),
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode({'appUserId': appUserId}),
      );

      if (response.statusCode != 200) {
        throw Exception('Failed to fetch ConvoKit token');
      }

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

Call connectUser after your app has identified the current user:

await ConvoKit.connectUser('app-user-123');

When the user signs out, disconnect realtime subscriptions:

await ConvoKit.disconnectUser();

Conversations

final conversations = await ConvoKit.getConversations(limit: 20);

final conversation = await ConvoKit.createConversation(
  participants: ['app-user-123', 'app-user-456'],
  title: 'Product support',
);

await ConvoKit.updateConversationTitle(
  conversationId: conversation.id,
  title: 'Billing support',
);

await ConvoKit.archiveConversation(conversation.id);
await ConvoKit.unarchiveConversation(conversation.id);
await ConvoKit.leaveConversation(conversation.id);

Messages

final message = await ConvoKit.sendMessage(
  conversationId: conversation.id,
  text: 'Hey, can you take a look?',
);

final messages = await ConvoKit.getMessages(
  conversationId: conversation.id,
  limit: 50,
);

To send media, upload the bytes first and pass the returned URL in the message payload:

final url = await ConvoKit.uploadMessageMedia(
  bytes: imageBytes,
  fileName: 'screenshot.png',
);

await ConvoKit.sendMessage(
  conversationId: conversation.id,
  text: 'Screenshot attached',
  media: [
    {
      'type': 'image',
      'url': url,
      'name': 'screenshot.png',
    },
  ],
);

Realtime

Subscribe through ConvoKit.realtime after connectUser completes.

final messageSub = ConvoKit.realtime
    .onMessage(conversation.id)
    .listen((message) {
  // Render the incoming message.
});

final typingSub = ConvoKit.realtime
    .onTyping(conversation.id)
    .listen((event) {
  // event.userId and event.isTyping
});

final readSub = ConvoKit.realtime
    .onReadReceipt(conversation.id)
    .listen((event) {
  // event.userId and event.readAt
});

Send typing indicators and read receipts with the REST helpers:

await ConvoKit.sendTyping(
  conversationId: conversation.id,
  isTyping: true,
);

await ConvoKit.markConversationRead(conversation.id);

Presence events are scoped to the ConvoKit app client ID:

final presenceSub = ConvoKit.realtime
    .onPresence(ConvoKit.clientId)
    .listen((event) {
  // event.userId, event.isOnline, event.lastSeenAt
});

await ConvoKit.updatePresence(isOnline: true);

Cancel stream subscriptions from your widget or state manager when they are no longer needed.

Media Helpers

final avatarUrl = await ConvoKit.uploadUserAvatar(
  userId: ConvoKit.currentUserId,
  imageBytes: avatarBytes,
  fileName: 'avatar.png',
);

final conversationImageUrl = await ConvoKit.uploadConversationImage(
  conversationId: conversation.id,
  imageBytes: imageBytes,
  fileName: 'group.png',
);

await ConvoKit.deleteUserAvatar(userId: ConvoKit.currentUserId);
await ConvoKit.deleteConversationImage(conversationId: conversation.id);

Error Handling

REST helpers throw ConvoKitException when the backend returns a non-success response.

try {
  await ConvoKit.sendMessage(
    conversationId: conversation.id,
    text: 'Hello',
  );
} on ConvoKitException catch (error) {
  // Show a retry state or log error.message.
}

Publishing

Publishing to pub.dev is automated through GitHub Actions when a version tag is pushed.

Before the workflow can publish, the package must already exist on pub.dev. If this is the first release, publish it manually once with dart pub publish or flutter pub publish.

Enable automated publishing from the pub.dev package admin page with:

  • Repository: ConvoKitApp/ConvoKit-Flutter-SDK
  • Tag pattern: v{{version}}
  • GitHub Actions environment: pub.dev

To publish a new version:

  1. Update version in pubspec.yaml.
  2. Update CHANGELOG.md.
  3. Commit and push the changes.
  4. Push a matching version tag:
git tag v0.0.2
git push origin v0.0.2

License

ConvoKit Flutter SDK is licensed under the Apache License, Version 2.0. See LICENSE for details.

Libraries

convokit_flutter