Integrate Meeting Place for Dart
By the end of this guide, two SDK instances representing different users will have established a DIDComm channel and exchanged a text message.
The Meeting Place SDK handles DID generation, mediator transport, and the consent-gated connection flow. This guide covers individual (peer-to-peer) DIDComm messaging using meeting_place_core and meeting_place_chat. For Matrix group chat or audio and video calling, see the Matrix SDK reference.
If you want to explore the full feature set without writing code, run the Flutter reference app instead.
Prerequisites
- Dart SDK
>=3.9.0. - A running DIDComm Mediator. See Mediator deployment options. Note its DID value: you will use it as
<YOUR-MEDIATOR-DID>in the config. - A running Control Plane API. See Control Plane deployment options. Note its DID value: you will use it as
<YOUR-CONTROL-PLANE-DID>in the config.
Required packages
| Package | Purpose |
|---|---|
meeting_place_core | Connection setup, offer publishing, and DIDComm transport. |
meeting_place_chat | High-level messaging API built on Core SDK. |
ssi | Wallet and DID management. |
Install all three:
dart pub add meeting_place_core meeting_place_chat ssiIntegration walkthrough
The steps below follow a two-party flow: Alice publishes a connection offer and Bob claims it. Both parties end with an active DIDComm channel and a text message exchanged.
Initialise the Core SDK
Create one MeetingPlaceCoreSDK instance per user. Replace InMemoryKeyStore and InMemoryStorage with your persistent implementations in production.
When connecting to the Affinidi-hosted Control Plane API, use
PersistentWalletwithp256keys.
import 'package:meeting_place_core/meeting_place_core.dart';
import 'package:ssi/ssi.dart';
import 'storage/in_memory_storage.dart';
final aliceWallet = PersistentWallet(InMemoryKeyStore());
final aliceStorage = InMemoryStorage();
final aliceSDK = await MeetingPlaceCoreSDK.create(
wallet: aliceWallet,
repositoryConfig: RepositoryConfig(
connectionOfferRepository: ConnectionOfferRepositoryImpl(storage: aliceStorage),
groupRepository: GroupRepositoryImpl(storage: aliceStorage),
channelRepository: ChannelRepositoryImpl(storage: aliceStorage),
keyRepository: KeyRepositoryImpl(storage: aliceStorage),
),
config: Config(
mediatorDid: '<YOUR-MEDIATOR-DID>:.well-known',
controlPlaneDid: '<YOUR-CONTROL-PLANE-DID>',
),
);
// Flutter mobile apps: register the OS-native device token.
await aliceSDK.registerForPushNotifications('<DEVICE-TOKEN>');
// Dart services or tests: register via the mediator instead.
// final notification = await aliceSDK.registerForDIDCommNotifications();Repeat this setup for Bob with his own wallet and storage.
Publish a connection offer (Alice)
Alice publishes an offer so Bob can discover and request to connect with her. The SDK returns a mnemonic passphrase that Alice shares with Bob out-of-band: for example, as a QR code or deep link.
final publishOfferResult = await aliceSDK.publishOffer(
offerName: 'Connect with Alice',
offerDescription: 'Alice\'s personal Meeting Place invitation.',
type: SDKConnectionOfferType.meetingPlaceInvitation,
contactCard: ContactCard(
did: 'did:example:alice',
type: 'individual',
contactInfo: {'fn': 'Alice'},
),
);
// Share this mnemonic with Bob out-of-band (QR code, deep link, etc.).
final mnemonic = publishOfferResult.connectionOffer.mnemonic;Set up a listener on controlPlaneEventsStream to approve Bob’s connection request when it arrives. The InvitationAccept event fires after Bob’s acceptance has been processed.
aliceSDK.controlPlaneEventsStream.listen((event) async {
if (event.type == ControlPlaneEventType.InvitationAccept) {
await aliceSDK.approveConnectionRequest(
channel: event.channel,
);
}
});Call processControlPlaneEvents() each time a mediator message arrives: from your push notification handler on mobile, or from a mediator stream listener in a Dart service.
await aliceSDK.processControlPlaneEvents();Find the offer (Bob)
Bob looks up Alice’s offer using the mnemonic passphrase she shared.
final offerResult = await bobSDK.findOffer(mnemonic: mnemonic);Accept the offer (Bob)
Bob accepts the offer, attaching his contact card. The notification to Alice is sent automatically inside acceptOffer. There is no separate notifyAcceptance call.
final offerAcceptedResult = await bobSDK.acceptOffer(
connectionOffer: offerResult.connectionOffer!,
contactCard: ContactCard(
did: 'did:example:bob',
type: 'individual',
contactInfo: {'fn': 'Bob'},
),
senderInfo: 'Bob',
);Listen for OfferFinalised on Bob’s controlPlaneEventsStream. It fires once Alice approves and event processing runs.
bobSDK.controlPlaneEventsStream.listen((event) async {
if (event.type == ControlPlaneEventType.OfferFinalised) {
// Channel is established — proceed to Step 5.
}
});
// Trigger processing from your push handler or mediator stream listener.
await bobSDK.processControlPlaneEvents();Start a chat session (both parties)
Once both streams have fired, the Channel object is available. Create a MeetingPlaceChatSDK instance for each party and start the session.
// Alice's chat session — Bob mirrors this with his own channel and coreSDK.
final aliceChatSDK = await MeetingPlaceChatSDK.initialiseChatFromChannel(
channel,
coreSDK: aliceSDK,
chatRepository: chatRepository, // ChatRepositoryDrift from meeting_place_drift_repository
options: MeetingPlaceChatSDKOptions(presenceExpiresInSeconds: 3),
card: ContactCard(did: 'did:example:alice', type: 'individual', contactInfo: {'fn': 'Alice'}),
);
await aliceChatSDK.startChatSession();Send and receive messages (both parties)
With the session running, use sendTextMessage to send and chatStreamSubscription to receive.
// Bob sends a message.
await bobChatSDK.sendTextMessage('Hi Alice!');
// Alice listens for incoming messages.
final chatStream = await aliceChatSDK.chatStreamSubscription;
// Use chatStream to receive incoming messages and chat events.For raw DIDComm messages with a custom type, such as application-level commands or protocol extensions, use sendMessage on the Core SDK directly:
await bobSDK.sendMessage(
message,
senderDid: channel.permanentChannelDid!,
recipientDid: channel.otherPartyPermanentChannelDid!,
);What’s next
Glad to hear it! Please tell us how we can improve more.
Sorry to hear that. Please tell us how we can improve.
Thank you for sharing your feedback so we can improve your experience.