Matrix SDK
The Matrix SDK provides the Matrix transport implementation for the Meeting Place SDK. It extends MeetingPlaceCoreSDK to route individual and group chats over a Matrix homeserver rather than the default DIDComm transport. Use it as a drop-in replacement for MeetingPlaceCoreSDK wherever you need Matrix room features.
Use meeting_place_matrix when you need any of the following:
- Individual or group Matrix chats with room history.
- Native Matrix end-to-end encryption backed by
vodozemac. - Rich messaging: image, video, file, and voice attachments, reactions, message editing and deletion, typing indicators, and delivery receipts.
- Optional audio/video calling via a LiveKit SFU.
For applications that need only DIDComm-based messaging without Matrix room features, use meeting_place_chat directly.
Install Dependency
Package: meeting_place_matrix
dart pub add meeting_place_matrixCheck the latest version on the pub.dev registry or view the source on GitHub.
Requirements
- Dart SDK
^3.8.0. - A running Matrix homeserver.
- The
vodozemacencryption runtime initialised before the first Matrix login. See Initialise the encryption runtime. - For audio/video calling: a LiveKit JWT service and a LiveKit SFU.
Initialise the encryption runtime
The Matrix SDK uses native end-to-end encryption backed by the vodozemac library. Initialise vodozemac once at application startup, before creating a MeetingPlaceMatrixSDK instance. Matrix client creation will fail if vodozemac has not been initialised first.
Flutter apps: add flutter_vodozemac to your pubspec.yaml and call init() from main():
import 'package:flutter/widgets.dart';
import 'package:flutter_vodozemac/flutter_vodozemac.dart' as fvod;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await fvod.init();
// ... create MeetingPlaceMatrixSDK
}Pure-Dart apps: build the vodozemac native library for your target platform (see the vodozemac README) and initialise it directly:
import 'package:vodozemac/vodozemac.dart' as vod;
Future<void> main() async {
await vod.init(libraryPath: '/path/to/vodozemac/dylib/dir');
// ... create MeetingPlaceMatrixSDK
}Classes and Methods
MatrixConfig
MatrixConfig carries the configuration required to connect MeetingPlaceMatrixSDK to a Matrix homeserver. Pass an instance to MeetingPlaceMatrixSDK.create().
Constructor
mediatorDid
String
RequiredcontrolPlaneDid
String
Requiredhomeserver
Uri
Requiredhttps://matrix.example.com).databaseFactory
MatrixDatabaseFactory
RequireddeviceId
String
RequiredserverName
String
Optional@hash:<serverName>). Defaults to homeserver.host. Set this explicitly when a tunnel or reverse proxy hostname differs from the Synapse server_name, to ensure all clients derive consistent user IDs.livekitServiceUrl
Uri?
OptionallivekitSfuUrl
Uri?
OptionalMeetingPlaceMatrixSDK.create() throws MeetingPlaceLiveKitCallMisconfiguredException when this is omitted and livekitServiceUrl is set. Also overrides the SFU URL from the JWT token response, which is useful in local development when the container-internal hostname is not reachable from the device.outgoingCallTimeout
Duration
OptionalExample
import 'package:meeting_place_matrix/meeting_place_matrix.dart';
final config = MatrixConfig(
mediatorDid: 'did:web:mediator.example.com:.well-known',
controlPlaneDid: 'did:web:controlplane.example.com',
homeserver: Uri.parse('https://matrix.example.com'),
databaseFactory: matrixDatabaseFactory,
deviceId: 'device-id-1',
);MeetingPlaceMatrixSDK
MeetingPlaceMatrixSDK implements MeetingPlaceCoreSDK backed by a Matrix homeserver. It delegates all core behaviour to an inner MeetingPlaceCoreSDK instance and intercepts Matrix-specific transport calls. Use it anywhere a MeetingPlaceCoreSDK instance is accepted. The additional properties and methods on this class expose audio/video call management.
create
A static factory that creates an instance of MeetingPlaceMatrixSDK. Audio/video calling is enabled when livekitServiceUrl, rtcDelegate, and roomFactory are all provided.
Parameters
wallet
Wallet
RequiredA digital wallet to manage cryptographic keys for signing and verification.
Requires the ssi package.
repositoryConfig
RepositoryConfig
Requiredconfig
MatrixConfig
Requiredoptions
MeetingPlaceMatrixSdkOptions
Optionallogger
MeetingPlaceCoreSDKLogger
OptionalrtcDelegate
WebRTCDelegate
OptionalroomFactory and MatrixConfig.livekitServiceUrl. Flutter apps should use FlutterMatrixRTCDelegate from meeting_place_livekit_flutter.roomFactory
LiveKitRoomFactory
OptionalrtcDelegate and MatrixConfig.livekitServiceUrl. Flutter apps should use FlutterLiveKitRoom from meeting_place_livekit_flutter.Example
import 'package:meeting_place_matrix/meeting_place_matrix.dart';
import 'package:ssi/ssi.dart';
final matrixSDK = await MeetingPlaceMatrixSDK.create(
wallet: Bip32Wallet.fromSeed(seed),
repositoryConfig: RepositoryConfig(
connectionOfferRepository: ConnectionOfferRepositoryImpl(storage: storage),
groupRepository: GroupRepositoryImpl(storage: storage),
channelRepository: ChannelRepositoryImpl(storage: storage),
keyRepository: KeyRepositoryImpl(storage: storage),
),
config: MatrixConfig(
mediatorDid: 'did:web:mediator.example.com:.well-known',
controlPlaneDid: 'did:web:controlplane.example.com',
homeserver: Uri.parse('https://matrix.example.com'),
databaseFactory: matrixDatabaseFactory,
deviceId: 'device-id-1',
),
);Properties
isCallSupported
bool
Optionalfalse when livekitServiceUrl was not set, or rtcDelegate and roomFactory were not passed to create().incomingCalls
Stream<IncomingAudioVideoCallEvent>
OptionalcancelledCalls
Stream<IncomingAudioVideoCallEvent>
OptionalactiveCallSession
LiveKitCallSession?
Optionalnull when no call is in progress.startCall
Starts an outbound audio or video call to another participant. Returns a live AudioVideoCallSession handle.
Parameters
otherPartyChannelDid
String
RequiredmediaType
CallMediaType
RequiredCallMediaType.audio or CallMediaType.audioVideo.Throws MeetingPlaceLiveKitCallOperationException when no call plugin is configured. Check isCallSupported before calling.
Example
if (matrixSDK.isCallSupported) {
final session = await matrixSDK.startCall(
otherPartyChannelDid: 'did:example:bob-channel',
mediaType: CallMediaType.audioVideo,
);
}acceptCall
Accepts an incoming call.
Parameters
callId
String
RequiredThrows MeetingPlaceLiveKitCallOperationException when no call plugin is configured.
Example
matrixSDK.incomingCalls.listen((event) async {
await matrixSDK.acceptCall(callId: event.callId);
});declineCall
Declines an incoming call.
Parameters
callId
String
RequiredThrows MeetingPlaceLiveKitCallOperationException when no call plugin is configured.
Example
matrixSDK.incomingCalls.listen((event) async {
await matrixSDK.declineCall(callId: event.callId);
});leaveCurrentCall
Leaves the currently active call. No-op when no call is in progress or no call plugin is configured.
Parameters
No parameters required.
Example
await matrixSDK.leaveCurrentCall();ringGroupMember
Sends a targeted call-invite notification to a single group member, leaving other members undisturbed.
Parameters
groupChannelDid
String
RequiredmemberDid
String
RequiredmediaType
CallMediaType
RequiredCallMediaType.audio or CallMediaType.audioVideo.Throws MeetingPlaceLiveKitCallOperationException when no call plugin is configured.
Example
await matrixSDK.ringGroupMember(
groupChannelDid: 'did:example:group-channel',
memberDid: 'did:example:member-alice',
mediaType: CallMediaType.audio,
);MeetingPlaceMatrixChatSDK
MeetingPlaceMatrixChatSDK is the Matrix-backed chat SDK for individual and group conversations. It implements MeetingPlaceChatSDK and provides rich messaging features: attachments, reactions, message editing and deletion, typing indicators, and delivery receipts.
This class only supports channels with transport == ChannelTransport.matrix. For DIDComm channels, use ChatSDK.initialiseFromChannel() from meeting_place_chat.
initialiseFromChannel
A static factory that creates a MeetingPlaceChatSDK instance from a Matrix channel. Returns a group or individual Matrix chat implementation based on the channel type.
Parameters
channel
Channel
Requiredtransport == ChannelTransport.matrix.coreSDK
MeetingPlaceCoreSDK
RequiredMeetingPlaceMatrixSDK or any MeetingPlaceCoreSDK backed by the Matrix transport.chatRepository
ChatRepository
Requiredoptions
MeetingPlaceChatSDKOptions
Requiredcard
ContactCard
Optionallogger
MeetingPlaceMatrixSDKLogger
OptionalExample
import 'package:meeting_place_matrix/meeting_place_matrix.dart';
final chatSDK = await MeetingPlaceMatrixChatSDK.initialiseFromChannel(
channel,
coreSDK: matrixSDK,
chatRepository: chatRepository,
options: const MeetingPlaceMatrixChatSdkOptions(),
card: myContactCard,
);startChatSession
Starts a Matrix chat session. Subscribes to the Matrix room, loads persisted messages, and fetches missed room history since the last session marker. Returns a Chat object immediately with the locally persisted snapshot. Matrix auth and history backfill run in the background and push updates through chat.stream.
Parameters
No parameters required.
Example
final chat = await chatSDK.startChatSession();
// Render the initial snapshot
for (final message in chat.messages) {
print(message);
}
// Listen for new messages and events
chat.stream.listen((data) {
print('New event: ${data.chatItem}');
});sendTextMessage
Sends a text message, optionally with attachments.
Parameters
text
String
Requiredattachments
List<ChatAttachment>
OptionalExample
// Plain text
await chatSDK.sendTextMessage('Hello via Matrix!');
// With an image attachment
await chatSDK.sendTextMessage(
'Check this out',
attachments: [
ChatAttachment(
data: imageBytes,
contentType: 'image/jpeg',
filename: 'photo.jpg',
),
],
);reactOnMessage
Toggles a reaction on a message. Adds the reaction if the local user has not already reacted with that emoji. Removes it if they have.
Parameters
message
Message
Requiredreaction
String
RequiredExample
await chatSDK.reactOnMessage(message, reaction: '👍');editTextMessage
Edits the text of a previously sent message. Only the original sender can edit. The message must have been delivered to the server (has a transportId).
Parameters
message
Message
RequirednewText
String
RequiredExample
await chatSDK.editTextMessage(message, 'Corrected message text');deleteMessage
Deletes a previously sent message. Only the original sender can delete. The deletion window is configured via MeetingPlaceChatSDKOptions.deleteMessageWindow.
Parameters
message
Message
RequiredlocalOnly
bool
Optionaltrue, hides the message locally only without sending a Matrix redaction event to the room. Defaults to false.Example
// Delete for all participants
await chatSDK.deleteMessage(message);
// Hide locally only
await chatSDK.deleteMessage(message, localOnly: true);sendChatActivity
Sends a typing indicator to the Matrix room. The indicator clears automatically after MeetingPlaceChatSDKOptions.chatActivityExpiry elapses without a new call.
Parameters
No parameters required.
Example
// Call when the local user starts typing
await chatSDK.sendChatActivity();sendChatDeliveredMessage
Sends an m.read receipt to the Matrix room, marking a message as delivered.
Parameters
messageId
String
RequiredExample
await chatSDK.sendChatDeliveredMessage(message.transportId!);downloadMedia
Downloads a media attachment hosted on the Matrix homeserver. The attachment must have a non-null transportId.
Parameters
attachment
ChatAttachment
RequiredExample
final bytes = await chatSDK.downloadMedia(attachment);sendCustomEvent
Dispatches an arbitrary Matrix room event. The SDK does not persist a ChatItem or push to chatStream for the sender. Receivers handle the event through their incoming routers based on the event type. Use this as a low-level escape hatch for event types not supported by the built-in methods.
Parameters
type
String
Requiredcom.example.my_event).payload
Map<String, dynamic>
RequiredExample
await chatSDK.sendCustomEvent(
type: 'com.example.read_status',
payload: {'status': 'online'},
);end
Ends the current chat session, cancels the Matrix room subscription, and releases resources.
Parameters
No parameters required.
Example
await chatSDK.end();Flutter call rendering
Flutter applications require the companion LiveKit Flutter plugin to render audio and video call tracks. The plugin provides FlutterMatrixRTCDelegate and FlutterLiveKitRoom: the concrete implementations of the rtcDelegate and roomFactory parameters accepted by MeetingPlaceMatrixSDK.create().
Samples
Initialise the SDK and publish a Matrix offer
The following example initialises vodozemac, creates the MeetingPlaceMatrixSDK, and publishes a connection offer using the Matrix transport.
import 'package:meeting_place_core/meeting_place_core.dart';
import 'package:meeting_place_matrix/meeting_place_matrix.dart';
import 'package:ssi/ssi.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
Future<void> main() async {
// 1. Initialise the vodozemac encryption runtime before any Matrix login.
await vod.init(libraryPath: '/path/to/libvodozemac');
// 2. Create the Matrix SDK.
final matrixSDK = await MeetingPlaceMatrixSDK.create(
wallet: Bip32Wallet.fromSeed(seed),
repositoryConfig: RepositoryConfig(
connectionOfferRepository: ConnectionOfferRepositoryImpl(storage: storage),
groupRepository: GroupRepositoryImpl(storage: storage),
channelRepository: ChannelRepositoryImpl(storage: storage),
keyRepository: KeyRepositoryImpl(storage: storage),
),
config: MatrixConfig(
mediatorDid: 'did:web:mediator.example.com:.well-known',
controlPlaneDid: 'did:web:controlplane.example.com',
homeserver: Uri.parse('https://matrix.example.com'),
databaseFactory: matrixDatabaseFactory,
deviceId: 'device-id-1',
),
);
// 3. Register for DIDComm notifications.
await matrixSDK.registerForDIDCommNotifications();
// 4. Publish a connection offer with Matrix transport.
final result = await matrixSDK.publishOffer(
offerName: 'My Matrix offer',
offerDescription: 'Connect with me over Matrix.',
contactCard: ContactCard(
did: 'did:example:alice',
type: 'individual',
contactInfo: const {},
),
type: SDKConnectionOfferType.invitation,
validUntil: DateTime.now().toUtc().add(const Duration(hours: 24)),
transport: ChannelTransport.matrix,
);
print('Share this mnemonic to connect: ${result.connectionOffer.mnemonic}');
}Start a Matrix chat session
Once a channel has been established with ChannelTransport.matrix, use MeetingPlaceMatrixChatSDK to open a chat session, send messages, and listen for incoming events.
import 'package:meeting_place_matrix/meeting_place_matrix.dart';
// matrixSDK and channel (ChannelTransport.matrix) must already be created.
final chatSDK = await MeetingPlaceMatrixChatSDK.initialiseFromChannel(
channel,
coreSDK: matrixSDK,
chatRepository: chatRepository,
options: const MeetingPlaceMatrixChatSdkOptions(),
);
final chat = await chatSDK.startChatSession();
// Render the initial persisted snapshot
for (final message in chat.messages) {
print(message);
}
// Listen for incoming messages and events
chat.stream.listen((data) {
print('Event: ${data.chatItem}');
});
// Send a plain text message
await chatSDK.sendTextMessage('Hello via Matrix!');
// React to a received message
await chatSDK.reactOnMessage(message, reaction: '👍');
// End the session and release resources when done
await chatSDK.end();Enable audio/video calling with LiveKit
Pass LiveKit configuration to MatrixConfig and provide rtcDelegate and roomFactory to MeetingPlaceMatrixSDK.create(). The call plugin is only created when all three are present. For Flutter apps, use FlutterMatrixRTCDelegate and FlutterLiveKitRoom from meeting_place_livekit_flutter.
import 'package:meeting_place_matrix/meeting_place_matrix.dart';
import 'package:ssi/ssi.dart';
final matrixSDK = await MeetingPlaceMatrixSDK.create(
wallet: wallet,
repositoryConfig: repositoryConfig,
config: MatrixConfig(
mediatorDid: 'did:web:mediator.example.com:.well-known',
controlPlaneDid: 'did:web:controlplane.example.com',
homeserver: Uri.parse('https://matrix.example.com'),
databaseFactory: matrixDatabaseFactory,
deviceId: 'device-id-1',
livekitServiceUrl: Uri.parse('https://livekit-jwt.example.com'),
livekitSfuUrl: Uri.parse('wss://livekit.example.com'),
),
rtcDelegate: webRtcDelegate,
roomFactory: liveKitRoomFactory,
);
// Listen for incoming calls
matrixSDK.incomingCalls.listen((event) async {
print('Incoming call: ${event.callId}');
await matrixSDK.acceptCall(callId: event.callId);
});
// Start an outbound audio/video call
if (matrixSDK.isCallSupported) {
final session = await matrixSDK.startCall(
otherPartyChannelDid: 'did:example:bob-channel',
mediaType: CallMediaType.audioVideo,
);
}
// Ring a single group member for a group call
await matrixSDK.ringGroupMember(
groupChannelDid: 'did:example:group-channel',
memberDid: 'did:example:member-alice',
mediaType: CallMediaType.audio,
);
// Leave the active call
await matrixSDK.leaveCurrentCall();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.