Realtime events are delivered to the SDK through a WebSocket. These events can be subscribed to by registering one or more listeners. MessagingListener and ProfileListener are both abstract classes that ship with empty onEventName implementations, so override only the callbacks you care about.
ComapiConfig config = new ComapiConfig()
.messagingListener(listener /* extends MessagingListener */)
.profileListener(listener /* extends ProfileListener */);val config = ComapiConfig()
.messagingListener(listener) // extends MessagingListener
.profileListener(listener) // extends ProfileListenerclient.addListener(listener /* extends MessagingListener */);
client.addListener(listener /* extends ProfileListener */);client.addListener(listener) // extends MessagingListener
client.addListener(listener) // extends ProfileListenerclient.removeListener(listener /* extends MessagingListener */);
client.removeListener(listener /* extends ProfileListener */);client.removeListener(listener) // extends MessagingListener
client.removeListener(listener) // extends ProfileListener
Also availableThe SDK also exposes
StateListeneron the client, which surfacesonSessionStart,onSocketConnected,onSocketDisconnectedandonSocketStartcallbacks. Use the sameclient.addListener(...) / client.removeListener(...)pattern.
Realtime events are only delivered while the application is in the foreground and the WebSocket is connected. If the app is backgrounded, use FCM to deliver messages to the user. After the app returns to the foreground, reconcile local state via the service APIs.
Android 13+ (API 33) notification permissionIf you rely on FCM to surface messages while the app is backgrounded, your app must request the
POST_NOTIFICATIONSruntime permission on Android 13 and above. See the FCM integration guide for details.
Lifecycle-aware listener registration (recommended)
Listeners that retain a reference to an Activity or Fragment will leak that scope across configuration changes. The recommended pattern on Android is to bind the listener to a lifecycle observer (or hold it inside an application-scoped object such as a ViewModel plus a singleton) and to add/remove it on ON_START / ON_STOP.
public class ChatLifecycleObserver implements DefaultLifecycleObserver {
private final ComapiClient client;
private final MessagingListener listener;
public ChatLifecycleObserver(ComapiClient client, MessagingListener listener) {
this.client = client;
this.listener = listener;
}
@Override
public void onStart(@NonNull LifecycleOwner owner) {
client.addListener(listener);
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
client.removeListener(listener);
}
}
// In your Activity/Fragment:
getLifecycle().addObserver(new ChatLifecycleObserver(client, messagingListener));class ChatLifecycleObserver(
private val client: ComapiClient,
private val listener: MessagingListener
) : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
client.addListener(listener)
}
override fun onStop(owner: LifecycleOwner) {
client.removeListener(listener)
}
}
// In your Activity/Fragment:
lifecycle.addObserver(ChatLifecycleObserver(client, messagingListener))Consuming events as a Kotlin Flow
If you would rather work with coroutines than callbacks, wrap the listener in a callbackFlow. The flow will register the listener on collection and remove it when the collector cancels, which composes cleanly with repeatOnLifecycle(Lifecycle.State.STARTED).
fun ComapiClient.messageSentEvents(): Flow<MessageSentEvent> = callbackFlow {
val listener = object : MessagingListener() {
override fun onMessage(event: MessageSentEvent) {
trySend(event)
}
}
addListener(listener)
awaitClose { removeListener(listener) }
}
// In a Fragment / Activity:
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
client.messageSentEvents().collect { event ->
// handle event
}
}
}Available events
| Event Name | Description |
|---|---|
| ProfileUpdateEvent | Sent when a user's profile is updated. |
| MessageSentEvent | Sent when a new message appears in a conversation. This event is also delivered to the message sender. |
| MessageDeliveredEvent | Sent when one of the participants updates the message status to Delivered. |
| MessageReadEvent | Sent when one of the participants updates the message status to Read. |
| ParticipantAddedEvent | Sent when a participant is added to a conversation. When a conversation is created, this event also fires with the owner's profileId. |
| ParticipantUpdatedEvent | Sent when a participant's role is updated in a conversation. |
| ParticipantRemovedEvent | Sent when a participant is removed from a conversation. |
| ConversationDeleteEvent | Sent when a conversation is deleted. |
| ConversationUpdateEvent | Sent when a conversation's details are updated. |
| ConversationUndeleteEvent | Sent when a conversation is restored. |
| ParticipantTypingEvent (from v1.0.2) | Sent when the isTyping method has been called, informing conversation participants that the user started typing a new message. |
| ParticipantTypingOffEvent (from v1.0.2) | Sent when the isTyping method has been called informing conversation participants that the user stopped typing a new message. |
Common getters on every event
Every event inherits from Event, which exposes:
// Event unique identifier
event.getEventId();
// Event name/type
event.getName();// Event unique identifier
event.eventId
// Event name/type
event.nameProfileUpdateEvent
// Profile unique identifier
event.getProfileId();
// Time when the update event was published
event.getPublishedOn();
// Revision of the profile details on the server
event.getRevision();
// Profile id of the user that performed this update
event.getCreatedBy();
// Raw profile update details (Map<String, Object>)
event.getPayload();
// Typed wrapper around the payload
ComapiProfile profile = event.getProfileDetails();
// Tag specifying server data version
event.getETag();// Profile unique identifier
event.profileId
// Time when the update event was published
event.publishedOn
// Revision of the profile details on the server
event.revision
// Profile id of the user that performed this update
event.createdBy
// Raw profile update details (Map<String, Any>)
event.payload
// Typed wrapper around the payload
val profile: ComapiProfile = event.profileDetails
// Tag specifying server data version
event.eTagMessageSentEvent
Sender, timestamp and conversation id live onMessageContextThe fields describing who sent the message, when, and to which conversation are exposed on
MessageContext, which you obtain by callingevent.getContext(). The previous version of these docs implied those getters were on the event itself — they are not.
// Message unique identifier
event.getMessageId();
// Unique, monotonically increasing event number for this conversation
event.getConversationEventId();
// Custom message metadata (set when the message was sent)
event.getMetadata();
// Parts of the message: data, type, name and size
event.getParts();
// Alert definitions for FCM and APNS push platforms
event.getAlert();
// Sender / timestamp / conversation id are on the MessageContext:
MessageContext ctx = event.getContext();
Sender sender = ctx.getFromWhom(); // sender (id, name, avatarUrl)
String sentBy = ctx.getSentBy(); // server-side sender id (internal)
String sentOn = ctx.getSentOn(); // ISO-8601 timestamp
String conversationId = ctx.getConversationId();// Message unique identifier
event.messageId
// Unique, monotonically increasing event number for this conversation
event.conversationEventId
// Custom message metadata (set when the message was sent)
event.metadata
// Parts of the message: data, type, name and size
event.parts
// Alert definitions for FCM and APNS push platforms
event.alert
// Sender / timestamp / conversation id are on the MessageContext:
val ctx = event.context
val sender = ctx?.fromWhom // Sender(id, name, avatarUrl)
val sentBy = ctx?.sentBy // server-side sender id (internal)
val sentOn = ctx?.sentOn // ISO-8601 timestamp
val conversationId = ctx?.conversationIdMessageDeliveredEvent
// Message unique identifier
event.getMessageId();
// Conversation unique identifier
event.getConversationId();
// Profile id of the user that updated the status
event.getProfileId();
// When the message was marked as delivered (ISO-8601)
event.getTimestamp();
// Unique, monotonically increasing event number for this conversation
event.getConversationEventId();event.messageId
event.conversationId
event.profileId
event.timestamp
event.conversationEventIdMessageReadEvent
MessageReadEvent extends the same base class as MessageDeliveredEvent, so its getters are identical (getMessageId, getConversationId, getProfileId, getTimestamp, getConversationEventId).
ParticipantAddedEvent / ParticipantUpdatedEvent / ParticipantRemovedEvent
All three extend ParticipantEvent and share the same getters.
// Conversation unique identifier
event.getConversationId();
// Profile unique identifier
event.getProfileId();
// Participant role in this conversation (may be null on removal)
String role = event.getRole();event.conversationId
event.profileId
val role: String? = event.roleConversationUpdateEvent
// Conversation unique identifier
event.getConversationId();
// Conversation name
event.getConversationName();
// Conversation description
event.getDescription();
// Role definitions for 'owner' and 'participant'
Roles roles = event.getRoles();
// Tag specifying server data version
event.getETag();event.conversationId
event.conversationName
event.description
val roles: Roles = event.roles
event.eTagConversationDeleteEvent
// Conversation unique identifier
event.getConversationId();
// When the conversation was deleted (ISO-8601)
event.getDeletedOn();
// Tag specifying server data version
event.getETag();event.conversationId
event.deletedOn
event.eTagConversationUndeleteEvent
// Conversation unique identifier
event.getConversationId();
// Full details of the restored conversation
ConversationDetails details = event.getConversation();
// Tag specifying server data version
event.getETag();event.conversationId
val details: ConversationDetails = event.conversation
event.eTagParticipantTypingEvent
// Conversation unique identifier
event.getConversationId();
// Profile id of the participant who started typing
event.getProfileId();event.conversationId
event.profileIdParticipantTypingOffEvent
// Conversation unique identifier
event.getConversationId();
// Profile id of the participant who stopped typing
event.getProfileId();event.conversationId
event.profileId