In-Session Communication

The EnableX Flutter SDK provides methods for text messaging (public and private), file sharing, screen sharing, custom application-level signalling, and whiteboard annotation. All methods are static calls on the EnxRtc class; callbacks are assigned as properties before use.

Chat Messaging

EnxRtc.sendMessage() sends text messages between session participants during a live room. You can broadcast a message to everyone connected to the room, or send it privately to one or more specific participants by supplying their clientId values. The method is non-blocking — delivery confirmation arrives through the onAcknowledgeSendData callback, and incoming messages are delivered via onMessageReceived.

Use sendMessage() whenever you need user-visible text communication — in-session chat panels, status announcements, or participant prompts. For structured application-level data (polls, control commands, state sync), use sendUserData() instead — covered in the Custom Signalling section below.

Method Signature

static Future<void> sendMessage(
  String message,
  bool isBroadcast,
  List<dynamic> recipientIDs
)
Parameter Type Description
message String The text message to send.
isBroadcast bool true to send to all participants; false for private or group messaging.
recipientIDs List List of clientId values to receive the message. Ignored when isBroadcast is true.
// Broadcast message to everyone in the room
EnxRtc.sendMessage('Hello everyone!', true, []);

// Private message to a specific participant
EnxRtc.sendMessage('Can you hear me?', false, ['clientId_001']);

// Receive messages — assign this before calling joinRoom()
EnxRtc.onMessageReceived = (Map<dynamic, dynamic> map) {
  print('Message from ${map['from']}: ${map['message']}');
};

// Confirmation that your message was delivered
EnxRtc.onAcknowledgeSendData = (Map<dynamic, dynamic> map) {
  print('Message delivered: $map');
};
Assign callbacks before joinRoom(). The onMessageReceived and onAcknowledgeSendData handlers should be registered in initState() — before you call EnxRtc.joinRoom() — so that no early messages are missed when the room becomes active.
File Sharing

Participants can share files during a session. The SDK opens a native file picker, uploads the selected file to EnableX storage, and then notifies all (or specified) room participants that the file is available for download. The complete workflow has three phases: upload, discover, and download — each with its own set of callbacks.

Uploading a File — EnxRtc.sendFile()

Call sendFile() to open the file picker and begin the upload. The SDK handles the upload internally; your app receives progress through the callbacks listed below. You can target the file to all participants or restrict it to a specific group by providing their clientId values.

// Share a file with everyone in the room
EnxRtc.sendFile(true, []);

// Share a file with specific participants only
EnxRtc.sendFile(false, ['clientId_001', 'clientId_002']);

Callbacks fired during the upload lifecycle:

Callback Fires when
onInitFileUpload The upload process has been initiated (sender only).
onFileUploaded The file was successfully uploaded to EnableX storage (sender only).
onFileUploadFailed The upload failed (sender only). Error codes: 5089 (storage access denied), 1182 (upload failed), 1185 (file too large).
onFileUploadStarted A file upload has begun — recipients are notified that a file is being shared.
onFileAvailable The file is ready to download — recipients receive this with file metadata.
EnxRtc.onFileUploaded = (Map<dynamic, dynamic> map) {
  print('File uploaded successfully: $map');
};

EnxRtc.onFileAvailable = (Map<dynamic, dynamic> map) {
  // Show a download prompt to participants
  print('New file available: $map');
};

EnxRtc.onFileUploadFailed = (Map<dynamic, dynamic> map) {
  print('Upload failed: ${map['msg']}');
};

Cancelling Uploads

You can cancel a single in-progress upload by its job ID, or cancel all active uploads at once. The onFileUploadCancelled callback confirms the cancellation.

// Cancel a specific upload by job ID
EnxRtc.cancelFileUpload(jobId);

// Cancel all ongoing uploads
EnxRtc.cancelAllUploads();

EnxRtc.onFileUploadCancelled = (Map<dynamic, dynamic> map) {
  print('Upload cancelled: $map');
};

Getting Available Files — EnxRtc.getAvailableFiles()

Call getAvailableFiles() to retrieve a list of all files currently available in the session. This is particularly useful on reconnect — when a participant rejoins mid-session, you can call this immediately after onRoomConnected to surface any files shared before they arrived.

List<dynamic> files = EnxRtc.getAvailableFiles();
print('Available files: $files');

Downloading a File — EnxRtc.downloadFile()

Pass the file metadata map (received from onFileAvailable or getAvailableFiles()) and an autoSave flag. When autoSave is true, the SDK saves the file automatically to device storage and the callback returns the saved file path. When false, the callback returns the raw Base64 data for your app to handle manually.

Parameter Type Description
fileInfo Map File metadata map received from onFileAvailable or getAvailableFiles().
autoSave bool true to save automatically to device storage; false to receive Base64 raw data.
// Download and save the file automatically to device storage
EnxRtc.downloadFile(fileInfo, true);

EnxRtc.onFileDownloaded = (Map<dynamic, dynamic> map) {
  // Contains the saved file path (autoSave=true) or Base64 data (autoSave=false)
  print('File downloaded: $map');
};

EnxRtc.onFileDownloadFailed = (Map<dynamic, dynamic> map) {
  print('Download failed: ${map['msg']}');
};

Cancelling Downloads

// Cancel a specific download by job ID
EnxRtc.cancelFileDownload(jobId);

// Cancel all active downloads
EnxRtc.cancelAllDownloads();

EnxRtc.onFileDownloadCancelled = (Map<dynamic, dynamic> map) {
  print('Download cancelled: $map');
};
Screen Sharing

A participant can broadcast their device screen as a separate stream within the room. This is useful for demos, presentations, code walkthroughs, or any scenario where participants need to see exactly what is on the presenter's screen in real time.

Screen sharing requires the room to be created with "screen_share": true in the room configuration — this is set server-side when creating the room via the EnableX API. The screen share stream uses the fixed stream ID 101; other participants subscribe to stream ID 101 to render the shared screen.

Starting Screen Share

EnxRtc.startScreenShare();

// Acknowledgement fired for the presenter
EnxRtc.onStartScreenShareACK = (Map<dynamic, dynamic> map) {
  print('Screen sharing started (you): $map');
};

// Notification fired on all other participants in the room
EnxRtc.onScreenSharedStarted = (Map<dynamic, dynamic> map) {
  // Subscribe to the screen share stream to render it
  EnxRtc.subscribe('101');
  print('Screen share now available: $map');
};
The screen share stream always uses stream ID 101. When onScreenSharedStarted fires on a participant's device, call EnxRtc.subscribe('101') and render the stream in an EnxPlayerWidget with streamId: '101' to display the shared screen.

Stopping Screen Share

EnxRtc.stopScreenShare();

// Acknowledgement fired for the presenter
EnxRtc.onStoppedScreenShareACK = (Map<dynamic, dynamic> map) {
  print('Screen sharing stopped (you): $map');
};

// Notification fired on all other participants in the room
EnxRtc.onScreenSharedStopped = (Map<dynamic, dynamic> map) {
  // Remove the screen share panel from your UI
  print('Screen share ended: $map');
};
Custom Signalling

EnxRtc.sendUserData() sends arbitrary application-level data during a session. Unlike sendMessage() — which is designed for user-visible chat text — sendUserData() is for structured data exchanged between your app's logic layer. Use it to trigger polls, send control commands, synchronise shared state, spotlight participants, or coordinate any other app-defined behaviour without surfacing it as a chat message.

The payload shape is entirely defined by your application. The recipient's app receives the data through onUserDataReceived and can dispatch on the contents however it needs to.

Method Signature

static Future<void> sendUserData(
  Map<String, dynamic> message,
  bool isBroadcast,
  List<dynamic> recipientIDs
)
// Broadcast a custom event to all participants
EnxRtc.sendUserData(
  {'action': 'poll_started', 'pollId': 'poll_001'},
  true,
  [],
);

// Send a targeted control signal to a specific participant
EnxRtc.sendUserData(
  {'action': 'spotlight_on'},
  false,
  ['moderator_client_id'],
);

// Receive custom signals on the remote side
EnxRtc.onUserDataReceived = (Map<dynamic, dynamic> map) {
  print('Custom signal from ${map['from']}: ${map['message']}');
};

// Confirmation that your signal was delivered
EnxRtc.onAcknowledgeSendData = (Map<dynamic, dynamic> map) {
  print('Signal delivered: $map');
};
onAcknowledgeSendData is shared by both sendMessage() and sendUserData(). If your app uses both methods, include a type field in your sendUserData() payload so you can distinguish acknowledgement responses from the same callback.
Annotation

The annotation feature lets participants draw collaboratively on top of a shared stream in real time — useful for code reviews, design walkthroughs, document mark-ups, or any scenario where visual feedback during a live session adds value. When one participant starts annotation, all participants in the room are notified and the drawing canvas becomes visible to everyone.

Annotation requires the room to be created with "canvas": true in the room settings — this is set server-side when creating the room via the EnableX API. Before starting annotation from your Flutter app, add the EnxToolbarWidget to your widget tree so the annotation drawing toolbar is available to the user.

Adding the Annotation Toolbar

The EnxToolbarWidget renders the annotation controls — colour picker, pen, eraser, and clear. Place it in your widget tree before the user can initiate annotation:

EnxToolbarWidget(width: 100, height: 50)

Starting Annotation — EnxRtc.startAnnotation()

Pass the stream ID of the stream you want to annotate. This is typically the screen share stream ID ('101') or the stream ID of a remote participant you want to mark up.

EnxRtc.startAnnotation(streamId);

// Acknowledgement fired for the annotator
EnxRtc.onStartAnnotationAck = (Map<dynamic, dynamic> map) {
  print('Annotation started (you): $map');
};

// Notification fired on all participants in the room
EnxRtc.onAnnotationStarted = (Map<dynamic, dynamic> map) {
  print('Annotation is now active in the room: $map');
};

Stopping Annotation — EnxRtc.stopAnnotation()

EnxRtc.stopAnnotation();

// Acknowledgement fired for the annotator
EnxRtc.onStoppedAnnotationAck = (Map<dynamic, dynamic> map) {
  print('Annotation stopped (you): $map');
};

// Notification fired on all participants in the room
EnxRtc.onAnnotationStopped = (Map<dynamic, dynamic> map) {
  print('Annotation ended in the room: $map');
};
Annotation is landscape-mode only on mobile devices. Error code 5112 is thrown if annotation is attempted while the device is in portrait orientation. Ensure your app's orientation is locked to landscape — for example, using SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]) — before calling startAnnotation().