From 3d7feabe3fc98440dc56f956dc503bc381ca1b9c Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 1 Dec 2025 09:34:28 +0000 Subject: [PATCH 1/9] chat: add Jetpack Compose support to documentation - Added examples showcasing the use of Jetpack Compose APIs in the chat SDK. - Updated documentation to include Jetpack Compose methods such as `collectAsPagingMessagesState()`, `collectAsCurrentlyTyping()`, `collectAsStatus()`, and others. - Extended sections with Jetpack Compose-specific code snippets for message reactions, typing events, room management, and more. --- src/data/languages/types.ts | 1 + src/pages/docs/chat/connect.mdx | 34 ++++ src/pages/docs/chat/rooms/history.mdx | 62 ++++++- src/pages/docs/chat/rooms/index.mdx | 84 +++++++-- .../docs/chat/rooms/message-reactions.mdx | 150 +++++++++++++++- src/pages/docs/chat/rooms/messages.mdx | 161 ++++++++++++++++-- src/pages/docs/chat/rooms/occupancy.mdx | 83 ++++++++- src/pages/docs/chat/rooms/presence.mdx | 159 +++++++++++++++-- src/pages/docs/chat/rooms/reactions.mdx | 76 ++++++++- src/pages/docs/chat/rooms/typing.mdx | 107 ++++++++++-- src/pages/docs/chat/setup.mdx | 44 ++++- 11 files changed, 883 insertions(+), 78 deletions(-) diff --git a/src/data/languages/types.ts b/src/data/languages/types.ts index 879ac741c6..e975fe3265 100644 --- a/src/data/languages/types.ts +++ b/src/data/languages/types.ts @@ -26,6 +26,7 @@ export const languageKeys = [ 'css', 'laravel', 'typescript', + 'jetpack', ] as const; export type LanguageKey = (typeof languageKeys)[number]; diff --git a/src/pages/docs/chat/connect.mdx b/src/pages/docs/chat/connect.mdx index 14739608ce..3892a88818 100644 --- a/src/pages/docs/chat/connect.mdx +++ b/src/pages/docs/chat/connect.mdx @@ -28,6 +28,10 @@ Use the [`status`](https://sdk.ably.com/builds/ably/ably-c Use the [`currentStatus`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-react.UseChatConnectionResponse.html#currentStatus) property returned in the response of the [`useChatConnection`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/functions/chat-react.useChatConnection.html) hook to check which status a connection is currently in: + +Use the [`collectAsStatus()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-status.html) composable function to observe the connection status as a State: + + ```javascript const connectionStatus = chatClient.connection.status; @@ -56,6 +60,17 @@ let status = chatClient.connection.status ```kotlin val connectionStatus = chatClient.connection.status ``` + +```jetpack +import com.ably.chat.extensions.compose.collectAsStatus + +@Composable +fun MyComponent(chatClient: ChatClient) { + val connectionStatus by chatClient.connection.collectAsStatus() + + Text("Connection status: $connectionStatus") +} +``` @@ -84,6 +99,10 @@ Listeners can also be registered to monitor the changes in connection status. An Use the [`connection.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Connection.html#onStatusChange)[`connection.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/connection/onstatuschange%28%29-76t7)[`connection.status.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-connection/on-status-change.html) method to register a listener for status change updates: + +In Jetpack Compose, you can use [`collectAsStatus()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-status.html) to observe status changes reactively: + + ```javascript const { off } = chatClient.connection.onStatusChange((change) => console.log(change)); @@ -114,6 +133,21 @@ val (off) = chatClient.connection.onStatusChange { statusChange: ConnectionStatu println(statusChange.toString()) } ``` + +```jetpack +import com.ably.chat.extensions.compose.collectAsStatus + +@Composable +fun MyComponent(chatClient: ChatClient) { + val connectionStatus by chatClient.connection.collectAsStatus() + + LaunchedEffect(connectionStatus) { + println("Connection status changed to: $connectionStatus") + } + + Text("Connection status: $connectionStatus") +} +``` diff --git a/src/pages/docs/chat/rooms/history.mdx b/src/pages/docs/chat/rooms/history.mdx index cef507e3d9..1c07175960 100644 --- a/src/pages/docs/chat/rooms/history.mdx +++ b/src/pages/docs/chat/rooms/history.mdx @@ -8,7 +8,11 @@ The history feature enables users to retrieve messages that have been previously ## Retrieve previously sent messages -Use the [`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#history)[`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/history(withparams:))[`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/history.html) method to retrieve messages that have been previously sent to a room. This returns a paginated response, which can be queried further to retrieve the next set of messages. +Use the [`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#history)[`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/history(withparams:))[`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/history.html)[`collectAsPagingMessagesState()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-paging-messages-state.html) method to retrieve messages that have been previously sent to a room. This returns a paginated response, which can be queried further to retrieve the next set of messages. + + + + Use the [`collectAsPagingMessagesState()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-paging-messages-state.html) method to retrieve messages that have been previously sent to a room. This returns a paginated response, which can be queried further to retrieve the next set of messages. @@ -71,6 +75,28 @@ while (historicalMessages.hasNext()) { println("End of messages") ``` + +```jetpack +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import com.ably.chat.extensions.compose.collectAsPagingMessagesState + +@Composable +fun HistoryComponent(room: Room) { + val pagingMessagesState by room.messages.collectAsPagingMessagesState( + orderBy = OrderBy.NewestFirst + ) + + LazyColumn { + items(pagingMessagesState.messages.size) { index -> + val message = pagingMessagesState.messages[index] + Text("Message: ${message.text}") + } + } +} +``` The following optional parameters can be passed when retrieving previously sent messages: @@ -90,6 +116,10 @@ Users can also retrieve historical messages that were sent to a room before the Use the [`historyBeforeSubscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.MessageSubscriptionResponse.html#historyBeforeSubscribe)[`historyBeforeSubscribe(withParams:)`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messagesubscriptionresponse/historybeforesubscribe%28withparams%3A%29))[`getPreviousMessages()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages-subscription/get-previous-messages.html) function returned as part of a [message subscription](/docs/chat/rooms/messages#subscribe) response to only retrieve messages that were received before the listener was subscribed to the room. This returns a paginated response, which can be queried further to retrieve the next set of messages. + + Use the [`getPreviousMessages()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages-subscription/get-previous-messages.html) function returned as part of a [message subscription](/docs/chat/rooms/messages#subscribe) response to only retrieve messages that were received before the listener was subscribed to the room. This returns a paginated response, which can be queried further to retrieve the next set of messages. + + Use the [`historyBeforeSubscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-react.UseMessagesResponse.html#historyBeforeSubscribe) method available from the response of the `useMessages` hook to only retrieve messages that were received before the listener subscribed to the room. As long as a defined value is provided for the listener, and there are no message discontinuities, `historyBeforeSubscribe()` will return messages from the same point across component renders. If the listener becomes undefined, the subscription to messages will be removed. If you subsequently redefine the listener then `historyBeforeSubscribe()` will return messages from the new point of subscription. This returns a paginated response, which can be queried further to retrieve the next set of messages. @@ -157,7 +187,7 @@ val subscription = room.messages.subscribe { println("New message received") } -var historicalMessages = subscription.historyBeforeSubscribe(limit = 50) +var historicalMessages = subscription.getPreviousMessages(limit = 50) println(historicalMessages.items.toString()) while (historicalMessages.hasNext()) { @@ -167,6 +197,34 @@ while (historicalMessages.hasNext()) { println("End of messages") ``` + +```jetpack +import androidx.compose.runtime.* +import com.ably.chat.extensions.compose.collectAsPagingMessagesState + +@Composable +fun HistoryBeforeSubscribeComponent(room: Room) { + DisposableEffect(room) { + val subscription = room.messages.subscribe { + println("New message received") + } + + var historicalMessages = subscription.getPreviousMessages(limit = 50) + println(historicalMessages.items.toString()) + + while (historicalMessages.hasNext()) { + historicalMessages = historicalMessages.next() + println(historicalMessages.items.toString()) + } + + println("End of messages") + + onDispose { + subscription.unsubscribe() + } + } +} +``` The following parameters can be passed when retrieving previously sent messages: diff --git a/src/pages/docs/chat/rooms/index.mdx b/src/pages/docs/chat/rooms/index.mdx index a287773942..c1d48c9467 100644 --- a/src/pages/docs/chat/rooms/index.mdx +++ b/src/pages/docs/chat/rooms/index.mdx @@ -15,8 +15,8 @@ The channel name is the same as the room name with an appended suffix of `::$cha Users send messages to a room and subscribe to the room in order to receive messages. - -To get an instance of a chat room, use the [`rooms.get()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Rooms.html#get)[`rooms.get()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/rooms/get%28named%3Aoptions%3A%29)[`rooms.get()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-rooms/get.html) method. It will create a new room instance if one doesn't already exist, or return the existing one if it does. + +To get an instance of a chat room, use the [`rooms.get()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Rooms.html#get)[`rooms.get()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/rooms/get%28named%3Aoptions%3A%29)[`rooms.get()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-rooms/get.html) method. It will create a new room instance if one doesn't already exist, or return the existing one if it does. @@ -64,6 +64,10 @@ let room = try await chatClient.rooms.get(named: "basketball-stream", options: . ```kotlin val room = chatClient.rooms.get(roomId = "basketball-stream") ``` + +```jetpack +val room = chatClient.rooms.get(roomId = "basketball-stream") +``` @@ -73,9 +77,9 @@ If the value changes between re-renders then the room will be discarded and recr - + -When you create or retrieve a room using `rooms.get()`, you can provide custom configuration for some features for that room by passing a [`RoomOptions`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.RoomOptions.html)[`RoomOptions`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/roomoptions)[`RoomOptions`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room-options/index.html) object as the second argument. If you do not provide a `RoomOptions` object, the default settings will be used. +When you create or retrieve a room using `rooms.get()`, you can provide custom configuration for some features for that room by passing a [`RoomOptions`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.RoomOptions.html)[`RoomOptions`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/roomoptions)[`RoomOptions`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room-options/index.html) object as the second argument. If you do not provide a `RoomOptions` object, the default settings will be used. ```javascript @@ -114,6 +118,20 @@ val room = chatClient.rooms.get(roomId = "basketball-stream") { } } ``` + +```jetpack +val room = chatClient.rooms.get(roomId = "basketball-stream") { + typing { + heartbeatThrottle = 5.seconds + } + presence { + enableEvents = true + } + occupancy { + enableEvents = true + } +} +``` The details of the options available to each feature are documented on their respective pages: @@ -132,8 +150,8 @@ Releasing a room allows the underlying resources to be garbage collected or rele Releasing a room may be optional for many applications. If you have multiple transient rooms, such as in the case of a 1:1 support chat, then it may be more beneficial. Also, proactively disconnecting rather than waiting for the standard two-minute timeout can help reduce costs and improve performance. - -Once [`rooms.release()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Rooms.html#release)[`rooms.release()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/rooms/release%28named%3A%29)[`rooms.release()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-rooms/release.html) has been called, the room will be unusable and a new instance will need to be created using [`rooms.get()`](#create) if you want to reuse it. + +Once [`rooms.release()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Rooms.html#release)[`rooms.release()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/rooms/release%28named%3A%29)[`rooms.release()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-rooms/release.html) has been called, the room will be unusable and a new instance will need to be created using [`rooms.get()`](#create) if you want to reuse it. ```javascript @@ -147,6 +165,10 @@ try await rooms.release(named: "basketball-stream") ```kotlin rooms.release("basketball-stream") ``` + +```jetpack +rooms.release("basketball-stream") +``` @@ -162,10 +184,10 @@ By default the `ChatRoomProvider` will automatically call [`release()`](https:// To start receiving messages and events from a room, you need to attach to it. Attaching to a room tells Ably to start streaming messages to the client, and ensures that events are not missed in case of temporary network interruptions. - + Once an instance of a room has been created using `rooms.get()`, clients attach to it to start receiving messages and events from the room. -Use the [`attach()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Room.html#attach)[`attach()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/room/attach%28%29)[`attach()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room/attach.html) method on a room to attach to it: +Use the [`attach()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Room.html#attach)[`attach()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/room/attach%28%29)[`attach()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room/attach.html) method on a room to attach to it: @@ -197,6 +219,10 @@ try await room.attach() ```kotlin room.attach() ``` + +```jetpack +room.attach() +``` As soon as a client is attached to a room, Ably will begin streaming messages and events to them. To receive the messages and events in your application code, you need to add listeners to the events that you are interested in by subscribing, for example using the [`messages.subscribe()`](/docs/chat/rooms/messages#subscribe) method. Add listeners before attaching to avoid missing any messages or events. @@ -209,8 +235,8 @@ As soon as a client is attached to a room, Ably will begin streaming messages an ### Detach from a room - -Use the [`detach()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Room.html#detach)[`detach()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/room/detach%28%29)[`detach()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room/detach.html) method on a room to detach from it and stop receiving messages and events: + +Use the [`detach()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Room.html#detach)[`detach()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/room/detach%28%29)[`detach()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room/detach.html) method on a room to detach from it and stop receiving messages and events: ```javascript @@ -224,6 +250,10 @@ try await room.detach() ```kotlin room.detach() ``` + +```jetpack +room.detach() +``` @@ -256,6 +286,10 @@ A room can have any of the following statuses: Use the [`status`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.RoomStatus.html#status)[`status`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/roomstatus)[`status`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room/status.html) property to check which status a room is currently in: + +Use the [`collectAsStatus()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-status.html) composable function to observe the room status as a State: + + Use the `roomStatus` property to view the current [`Room`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Room.html) status changes. The `roomError` property is its associated error. Any hooks that take an optional listener have these properties available in their response, such as `useMessages` or `useTyping`. It is more common that you will monitor the room status in the specific feature hooks rather than needing to use `useRoom`. These events are related to the room instance of the nearest [`ChatRoomProvider`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/functions/chat-react.ChatRoomProvider.html). For example, with the `useMessages` hook: @@ -294,12 +328,23 @@ let status = room.status ```kotlin val status = room.status ``` + +```jetpack +import com.ably.chat.extensions.compose.collectAsStatus + +@Composable +fun MyComponent(room: Room) { + val roomStatus by room.collectAsStatus() + + Text("Room status: $roomStatus") +} +``` - + You can also subscribe to room status updates by registering a listener. An event will be emitted whenever the status of the room changes. -Use the [`room.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Room.html#onStatusChange)[`room.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/room/onstatuschange%28%29-s9g)[`room.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room/on-status-change.html) method in a room to register a listener for status change updates: +Use the [`room.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Room.html#onStatusChange)[`room.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/room/onstatuschange%28%29-s9g)[`room.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room/on-status-change.html) method in a room to register a listener for status change updates. For Jetpack Compose, you can use [`collectAsStatus()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-status.html) to observe status changes reactively: ```javascript @@ -319,6 +364,21 @@ val (off) = room.onStatusChange { statusChange: RoomStatusChange -> println(statusChange.toString()) } ``` + +```jetpack +import com.ably.chat.extensions.compose.collectAsStatus + +@Composable +fun MyComponent(room: Room) { + val roomStatus by room.collectAsStatus() + + LaunchedEffect(roomStatus) { + println("Room status changed to: $roomStatus") + } + + Text("Room status: $roomStatus") +} +``` diff --git a/src/pages/docs/chat/rooms/message-reactions.mdx b/src/pages/docs/chat/rooms/message-reactions.mdx index 961e129af3..5ecce6b1a6 100644 --- a/src/pages/docs/chat/rooms/message-reactions.mdx +++ b/src/pages/docs/chat/rooms/message-reactions.mdx @@ -81,7 +81,7 @@ const MyComponent = () => { ## Sending a message reaction - + To send a message reaction use `room.messages.reactions.send(message, params)`. This method takes the following parameters: * `message` - The message to send the reaction to. Can be either a Message object or a string containing the message serial. * `params` - Set the `name`, and optionally override the `type` or set a `count`. @@ -145,8 +145,8 @@ await room.messages.reactions.send(forMessageWithSerial: message.serial, params: room.messages.reactions.send(message, name = "👍") // The reaction can be anything, not just UTF-8 emojis: -room.messages.reactions.send(message, name = ":like:")) -room.messages.reactions.send(message, name = "+1")) +room.messages.reactions.send(message, name = ":like:") +room.messages.reactions.send(message, name = "+1") // Send a :love: reaction using the Unique type room.messages.reactions.send(message, @@ -162,6 +162,42 @@ room.messages.reactions.send(message, ) ``` +```jetpack +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import kotlinx.coroutines.launch + +@Composable +fun SendMessageReactionComponent(room: Room, message: Message) { + val coroutineScope = rememberCoroutineScope() + + Button(onClick = { + coroutineScope.launch { + // Send a 👍 reaction using the default type + room.messages.reactions.send(message, name = "👍") + } + }) { + Text("Send 👍") + } + + Button(onClick = { + coroutineScope.launch { + // Send a ❤️ reaction with count 100 using the Multiple type + room.messages.reactions.send( + message, + name = "❤️", + type = MessageReactionType.Multiple, + count = 100, + ) + } + }) { + Text("Send ❤️ x100") + } +} +``` + ```react import { MessageReactionType } from '@ably/chat'; import { useMessages } from '@ably/chat/react'; @@ -210,7 +246,7 @@ The `annotation-publish` capability is required for sending reactions. ## Removing a message reaction - + To remove a message reaction use `room.messages.reactions.delete(message, params)`. This method takes the following parameters: * `message` - The message to remove the reaction from. This can be a Message object, or just the string serial. * `params` - Set the `name`, and optionally override the `type` or set a `count`. @@ -277,6 +313,42 @@ const MyComponent = () => { ); }; ``` + +```jetpack +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import kotlinx.coroutines.launch + +@Composable +fun RemoveMessageReactionComponent(room: Room, message: Message) { + val coroutineScope = rememberCoroutineScope() + + Button(onClick = { + coroutineScope.launch { + // Remove a 👍 reaction using the default type + room.messages.reactions.delete(message, name = "👍") + } + }) { + Text("Remove 👍") + } + + Button(onClick = { + coroutineScope.launch { + // Remove a ❤️ reaction with count 50 using the Multiple type + room.messages.reactions.delete( + message, + name = "❤️", + type = MessageReactionType.Multiple, + count = 50, + ) + } + }) { + Text("Remove ❤️ x50") + } +} +``` ## Messages and reactions @@ -354,7 +426,7 @@ Always call `Message.with(event)` when applying message events and reaction even ## Subscribing to message reactions - + Ably generates a summary (aggregate) of the reactions for each message and for each reaction type. For displaying accurate counts for message reactions, subscribe to changes in the message summary. @@ -381,6 +453,20 @@ room.messages.reactions.subscribe { event -> } ``` +```jetpack +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect + +@Composable +fun SubscribeToReactionsComponent(room: Room) { + LaunchedEffect(room) { + room.messages.reactions.asFlow().collect { event -> + println("received reactions summary event: $event") + } + } +} +``` + ```react import { useMessages } from '@ably/chat/react'; @@ -454,6 +540,36 @@ room.messages.reactions.subscribe { event -> } ``` +```jetpack +import androidx.compose.runtime.* + +@Composable +fun ReactionsWithMessagesComponent(room: Room) { + var messages by remember { mutableStateOf>(emptyList()) } + + DisposableEffect(room) { + // init messages + messages = room.messages.history(limit = 50).items + + // subscribe to message reactions summary events + val (unsubscribe) = room.messages.reactions.subscribe { event -> + // find the relevant message (in practice: use binary search or a map for lookups) + val idx = messages.indexOfLast { msg -> msg.serial == event.messageSerial } + if (idx != -1) { + // update message + messages = messages.toMutableList().apply { + this[idx] = this[idx].with(event) + } + } + } + onDispose { + unsubscribe() + } + } +} +// Note: `room.collectAsPagingMessagesState()` handles everything automatically +``` + ```react import { useState, useEffect } from 'react'; import { useMessages, Message } from '@ably/chat/react'; @@ -568,7 +684,7 @@ const MyComponent = () => { ``` - + Then you can receive raw reactions using the `room.messages.reactions.subscribeRaw()` method: @@ -607,6 +723,28 @@ room.messages.reactions.subscribeRaw { event -> } ``` +```jetpack +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect + +@Composable +fun SubscribeToRawReactionsComponent(room: Room) { + DisposableEffect(room) { + val (unsubscribe) = room.messages.reactions.subscribeRaw { event -> + if (event.type == MessageReactionEventType.Create) { + println("new reaction: ${event.reaction}") + } else if (event.type == MessageReactionEventType.Delete) { + println("reaction removed: ${event.reaction}") + } + } + + onDispose { + unsubscribe() + } + } +} +``` + ```react import { useMessages } from '@ably/chat/react'; import { MessageReactionEventType } from '@ably/chat'; diff --git a/src/pages/docs/chat/rooms/messages.mdx b/src/pages/docs/chat/rooms/messages.mdx index 6018c86335..3b34c2ef02 100644 --- a/src/pages/docs/chat/rooms/messages.mdx +++ b/src/pages/docs/chat/rooms/messages.mdx @@ -13,6 +13,10 @@ A user can also update or delete a message, all users that are subscribed to the Subscribe to receive messages in a room by registering a listener. Use the [`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#subscribe)[`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/subscribe%28%29-360z1)[`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/subscribe.html) method in a room to receive all messages that are sent to it: + +For Jetpack Compose, use the [`collectAsPagingMessagesState()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-paging-messages-state.html) composable function to observe messages with automatic pagination support. Alternatively, you can use [`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/subscribe.html) for a simple subscription: + + Subscribe to messages with the [`useMessages`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/functions/chat-react.useMessages.html) hook. Supply a listener and the hook will automatically subscribe to message events sent to the room. As long as a defined value is provided, the subscription will persist across renders. If the listener value is undefined, the subscription will be removed until it becomes defined again. @@ -53,6 +57,22 @@ val subscription = room.messages.subscribe { messageEvent: ChatMessageEvent -> println(messageEvent.message.toString()) } ``` + +```jetpack +import com.ably.chat.extensions.compose.collectAsPagingMessagesState + +@Composable +fun MyComponent(room: Room) { + val pagingMessagesState by room.messages.collectAsPagingMessagesState() + + LazyColumn { + items(pagingMessagesState.messages.size) { index -> + val message = pagingMessagesState.messages[index] + Text("Message: ${message.text}") + } + } +} +``` ### Message structure @@ -99,8 +119,8 @@ See [below](#global-ordering) for more information on how to apply deterministic ### Unsubscribe from messages - -Use the `unsubscribe()` function returned in the `subscribe()` response to remove a chat message listener: + +Use the `unsubscribe()` function returned in the `subscribe()` response to remove a chat message listener. When using Jetpack Compose's `collectAsPagingMessagesState()`, lifecycle and cleanup are handled automatically: @@ -111,7 +131,7 @@ You don't need to handle removing listeners, as this is done automatically by th When you unmount the component that is using the `useMessages` hook, it will automatically handle unsubscribing any associated listeners registered to receive messages. - + ```javascript // Initial subscription @@ -128,6 +148,16 @@ val (unsubscribe) = room.messages.subscribe { event -> println(event.message) } // To remove the listener unsubscribe() ``` + +```jetpack +// When using subscribe directly +val (unsubscribe) = room.messages.subscribe { event -> println(event.message) } + +// To remove the listener +unsubscribe() + +// Note: collectAsPagingMessagesState() handles cleanup automatically +``` @@ -141,8 +171,8 @@ The [`detach()`](/docs/chat/rooms#detach) method detaches a user from the room. ## Send a message - -Use the [`messages.send()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#send)[`messages.send()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/roomreactions/send%28withparams%3A%29)[`messages.send()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/send.html) method to send a message in a chat room. All users that are "subscribed](subscribe to messages on that room will receive it: + +Use the [`messages.send()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#send)[`messages.send()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/roomreactions/send%28withparams%3A%29)[`messages.send()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/send.html) method to send a message in a chat room. All users that are [subscribed](#subscribe) to messages on that room will receive it: @@ -179,6 +209,21 @@ let message = try await room.messages.send(params: .init(text: "hello")) ```kotlin room.messages.send(text = "hello") ``` + +```jetpack +@Composable +fun MyComponent(room: Room) { + val coroutineScope = rememberCoroutineScope() + + Button(onClick = { + coroutineScope.launch { + room.messages.send(text = "hello") + } + }) { + Text("Send Message") + } +} +``` ## Get a single message @@ -219,8 +264,8 @@ const MyComponent = () => { ## Update a message - -Use the [`messages.update()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#update)[`messages.update()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/update%28withserial:params:details:%29)[`messages.update()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/update.html) method to update a message in a chat room. All users that are [subscribed](#subscribe) to messages on that room will receive the update: + +Use the [`messages.update()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#update)[`messages.update()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/update%28withserial:params:details:%29)[`messages.update()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/update.html) method to update a message in a chat room. All users that are [subscribed](#subscribe) to messages on that room will receive the update: @@ -276,12 +321,31 @@ val updatedMessage = room.messages.update( operationDescription = "Message update by user", ) ``` + +```jetpack +@Composable +fun MyComponent(room: Room) { + val coroutineScope = rememberCoroutineScope() + val originalMessage: Message // assume this is available + + Button(onClick = { + coroutineScope.launch { + room.messages.update( + originalMessage.copy(text = "my updated text"), + operationDescription = "Message update by user", + ) + } + }) { + Text("Update Message") + } +} +``` ### Filter for updates - -Use the [`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#subscribe)[`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/subscribe%28%29-8jolq)[`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/subscribe.html) method to receive messages in a room. To filter for updated messages, provide a listener that checks the `type``action` property of the message event: + +Use the [`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#subscribe)[`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/subscribe%28%29-8jolq)[`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/subscribe.html) method to receive messages in a room. To filter for updated messages, provide a listener that checks the `type``action` property of the message event: @@ -371,6 +435,34 @@ val messagesSubscription = room.messages.subscribe { event -> } } ``` + +```jetpack +@Composable +fun MyComponent(room: Room) { + var myMessageList by remember { mutableStateOf>(emptyList()) } + + LaunchedEffect(room) { + room.messages.asFlow().collect { event -> + when (event.type) { + ChatMessageEventType.Created -> { + myMessageList = myMessageList + event.message + } + ChatMessageEventType.Updated -> { + myMessageList = myMessageList.map { message -> + if (message.serial == event.message.serial && + event.message.version.serial > message.version.serial) { + event.message + } else { + message + } + } + } + else -> {} + } + } + } +} +``` See [below](#global-ordering) for more information on how to deterministically apply ordering to update events in your application. @@ -414,8 +506,8 @@ The updated message response is identical to the structure of a message, with th ## Delete a message - -Use the [`messages.delete()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#delete)[`messages.delete()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/delete%28withserial:details:%29)[`messages.delete()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/delete.html) method to delete a message in a chat room. All users that are [subscribed](#subscribe) to messages on that room will receive the deletion: + +Use the [`messages.delete()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#delete)[`messages.delete()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/delete%28withserial:details:%29)[`messages.delete()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/delete.html) method to delete a message in a chat room. All users that are [subscribed](#subscribe) to messages on that room will receive the deletion: @@ -470,12 +562,31 @@ val deletedMessage = room().messages.delete( operationDescription = "Message deleted by user", ) ``` + +```jetpack +@Composable +fun MyComponent(room: Room) { + val coroutineScope = rememberCoroutineScope() + val messageToDelete: Message // assume this is available + + Button(onClick = { + coroutineScope.launch { + room.messages.delete( + messageToDelete, + operationDescription = "Message deleted by user", + ) + } + }) { + Text("Delete Message") + } +} +``` ### Filter for deletes - -Use the [`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#subscribe)[`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/subscribe%28%29-8jolq)[`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/subscribe.html) method to receive messages in a room. To filter for deleted messages, provide a listener that checks the `type``action` property of the message event: + +Use the [`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#subscribe)[`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/subscribe%28%29-8jolq)[`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/subscribe.html) method to receive messages in a room. To filter for deleted messages, provide a listener that checks the `type``action` property of the message event: @@ -565,6 +676,30 @@ val messagesSubscription = room.messages.subscribe { event -> } } ``` + +```jetpack +@Composable +fun MyComponent(room: Room) { + var myMessageList by remember { mutableStateOf>(emptyList()) } + + LaunchedEffect(room) { + room.messages.asFlow().collect { event -> + when (event.type) { + ChatMessageEventType.Created -> { + myMessageList = myMessageList + event.message + } + ChatMessageEventType.Deleted -> { + myMessageList = myMessageList.filterNot { message -> + message.serial == event.message.serial && + event.message.version.serial > message.version.serial + } + } + else -> {} + } + } + } +} +``` See [below](#global-ordering) for more information on how to deterministically apply ordering to delete events in your application. diff --git a/src/pages/docs/chat/rooms/occupancy.mdx b/src/pages/docs/chat/rooms/occupancy.mdx index fe7017b6ce..d0fa47fe1f 100644 --- a/src/pages/docs/chat/rooms/occupancy.mdx +++ b/src/pages/docs/chat/rooms/occupancy.mdx @@ -11,8 +11,8 @@ Occupancy generates messages on any client entering/leaving a room, and so incre ## Subscribe to room occupancy - -Subscribe to a room's occupancy by registering a listener. Occupancy events are emitted whenever the number of online users within a room changes. Use the [`occupancy.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Occupancy.html#subscribe)[`occupancy.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/occupancy/subscribe%28%29-3loon)[`occupancy.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-occupancy/subscribe.html) method in a room to receive updates: + +Subscribe to a room's occupancy by registering a listener. Occupancy events are emitted whenever the number of online users within a room changes. Use the [`occupancy.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Occupancy.html#subscribe)[`occupancy.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/occupancy/subscribe%28%29-3loon)[`occupancy.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-occupancy/subscribe.html)[`collectAsOccupancy()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-occupancy.html) method in a room to receive updates: @@ -58,6 +58,21 @@ val subscription = room.occupancy.subscribe { event: OccupancyEvent -> println("Number of members present is: ${event.occupancy.presenceMembers}") } ``` + +```jetpack +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import com.ably.chat.extensions.compose.collectAsOccupancy + +@Composable +fun OccupancyComponent(room: Room) { + val occupancy by room.occupancy.collectAsOccupancy() + + Text("Number of users connected: ${occupancy.connections}") + Text("Number of members present: ${occupancy.presenceMembers}") +} +``` - -Use the [`presence.enter()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Presence.html#enter)[`presence.enter()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/presence/enter%28withdata%3A%29)[`presence.enter()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-presence/enter.html) method to indicate when a user joins a room. This will send a presence event to all users subscribed to presence indicating that a new member has joined the chat. You can also set an optional data field with information such as the status of a user: + +Use the [`presence.enter()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Presence.html#enter)[`presence.enter()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/presence/enter%28withdata%3A%29)[`presence.enter()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-presence/enter.html) method to indicate when a user joins a room. This will send a presence event to all users subscribed to presence indicating that a new member has joined the chat. You can also set an optional data field with information such as the status of a user: @@ -176,10 +204,30 @@ room.presence.enter( }, ) ``` + +```jetpack +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.rememberCoroutineScope +import com.ably.chat.json.* + +@Composable +fun EnterPresenceComponent(room: Room) { + val coroutineScope = rememberCoroutineScope() + + LaunchedEffect(room) { + room.presence.enter( + jsonObject { + put("status", "Online") + } + ) + } +} +``` - -Use the [`presence.update()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Presence.html#update)[`presence.update()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/presence/update%28withdata%3A%29)[`presence.update()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-presence/update.html) method when a user wants to update their data, such as an update to their status, or to indicate that they're raising their hand. Updates will send a presence event to all users subscribed to presence: + +Use the [`presence.update()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Presence.html#update)[`presence.update()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/presence/update%28withdata%3A%29)[`presence.update()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-presence/update.html) method when a user wants to update their data, such as an update to their status, or to indicate that they're raising their hand. Updates will send a presence event to all users subscribed to presence: @@ -227,10 +275,36 @@ room.presence.update( }, ) ``` + +```jetpack +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import com.ably.chat.json.* +import kotlinx.coroutines.launch + +@Composable +fun UpdatePresenceComponent(room: Room) { + val coroutineScope = rememberCoroutineScope() + + Button(onClick = { + coroutineScope.launch { + room.presence.update( + jsonObject { + put("status", "Busy") + } + ) + } + }) { + Text("Set Status to Busy") + } +} +``` - -Use the [`presence.leave()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Presence.html#leave)[`presence.leave()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/presence/leave%28withdata%3A%29)[`presence.leave()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-presence/leave.html) method to explicitly remove a user from the presence set. This will send a presence event to all users subscribed to presence. You can also set an optional data field such as setting a status of 'Back later'. + +Use the [`presence.leave()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Presence.html#leave)[`presence.leave()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/presence/leave%28withdata%3A%29)[`presence.leave()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-presence/leave.html) method to explicitly remove a user from the presence set. This will send a presence event to all users subscribed to presence. You can also set an optional data field such as setting a status of 'Back later'. @@ -278,6 +352,25 @@ room.presence.leave( }, ) ``` + +```jetpack +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import com.ably.chat.json.* + +@Composable +fun LeavePresenceComponent(room: Room) { + DisposableEffect(room) { + onDispose { + room.presence.leave( + jsonObject { + put("status", "Be back later!") + } + ) + } + } +} +``` When a user goes offline or closes their [connection](/docs/chat/connect), a leave event is also emitted and they are removed from the presence set. @@ -350,10 +443,10 @@ The following options can be set when [creating a room](/docs/chat/rooms#create) ## Retrieve the presence set - + The online presence of users can be retrieved in one-off calls. This can be used to check the status of an individual user, or return the entire presence set as an array. -Use the [`presence.get()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Presence.html#get)[`presence.get()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/presence/get%28%29)[`presence.get()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-presence/get.html) method to retrieve an array of all users currently entered into the presence set, or individual users: +Use the [`presence.get()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Presence.html#get)[`presence.get()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/presence/get%28%29)[`presence.get()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-presence/get.html)[`collectAsPresenceMembers()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-presence-members.html) method to retrieve an array of all users currently entered into the presence set, or individual users: ```javascript @@ -379,9 +472,26 @@ val presentMembers = room.presence.get() // Retrieve the status of specific users by their clientId: val presentMember = room.presence.get(clientId = "clemons123") ``` + +```jetpack +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import com.ably.chat.extensions.compose.collectAsPresenceMembers + +@Composable +fun GetPresenceComponent(room: Room) { + val presentMembers by room.presence.collectAsPresenceMembers() + + Text("Total present: ${presentMembers.size}") + presentMembers.forEach { member -> + Text("User: ${member.clientId}") + } +} +``` -Alternatively, use the [`presence.isUserPresent()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Presence.html#isUserPresent)[`presence.isUserPresent()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/presence/isuserpresent%28withclientid%3A%29)[`presence.isUserPresent()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-presence/is-user-present.html) method and pass in a user's `clientId` to check whether they are online or not. This will return a boolean: +Alternatively, use the [`presence.isUserPresent()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Presence.html#isUserPresent)[`presence.isUserPresent()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/presence/isuserpresent%28withclientid%3A%29)[`presence.isUserPresent()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-presence/is-user-present.html) method and pass in a user's `clientId` to check whether they are online or not. This will return a boolean: ```javascript @@ -395,6 +505,27 @@ let isPresent = try await room.presence.isUserPresent(withClientID: "clemons123" ```kotlin val isPresent = room.presence.isUserPresent("client-id") ``` + +```jetpack +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue + +@Composable +fun IsUserPresentComponent(room: Room, clientId: String) { + var isPresent by remember { mutableStateOf(false) } + + LaunchedEffect(room, clientId) { + isPresent = room.presence.isUserPresent(clientId) + } + + Text("User $clientId is ${if (isPresent) "present" else "not present"}") +} +``` diff --git a/src/pages/docs/chat/rooms/reactions.mdx b/src/pages/docs/chat/rooms/reactions.mdx index e494ebc5e6..4385beafc5 100644 --- a/src/pages/docs/chat/rooms/reactions.mdx +++ b/src/pages/docs/chat/rooms/reactions.mdx @@ -9,8 +9,8 @@ Room reactions are ephemeral and not stored or aggregated by Ably. The intention ## Subscribe to room reactions - -Subscribe to room reactions by registering a listener. Use the [`reactions.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.RoomReactions.html#subscribe)[`reactions.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/roomreactions/subscribe%28%29-64gdf)[`reactions.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room-reactions/subscribe.html) method in a room to receive reactions: + +Subscribe to room reactions by registering a listener. Use the [`reactions.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.RoomReactions.html#subscribe)[`reactions.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/roomreactions/subscribe%28%29-64gdf)[`reactions.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room-reactions/subscribe.html) method in a room to receive reactions: @@ -47,7 +47,21 @@ for await event in reactionSubscription { ```kotlin val subscription = room.reactions.subscribe { event: RoomReactionEvent -> - println("Received a reaciton of name ${event.reaction.name} with metadata ${event.reaction.metadata}") + println("Received a reaction of name ${event.reaction.name} with metadata ${event.reaction.metadata}") +} +``` + +```jetpack +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect + +@Composable +fun RoomReactionsComponent(room: Room) { + LaunchedEffect(room) { + room.reactions.asFlow().collect { event: RoomReactionEvent -> + println("Received a reaction of name ${event.reaction.name} with metadata ${event.reaction.metadata}") + } + } } ``` @@ -69,8 +83,8 @@ The following are the properties of a room reaction event: ### Unsubscribe from room reactions - -Use the `unsubscribe()` function returned in the `subscribe()` response to remove a room reaction listener: + +Use the `unsubscribe()` function returned in the `subscribe()` response to remove a room reaction listener. Jetpack Compose automatically handles lifecycle and cleanup when using `LaunchedEffect`: @@ -81,7 +95,7 @@ You don't need to handle removing listeners, as this is done automatically by th When you unmount the component that is using the `useRoomReactions` hook, it will automatically handle unsubscribing any associated listeners registered for room reactions. - + ```javascript // Initial subscription @@ -99,6 +113,17 @@ val (unsubscribe) = room.reactions.subscribe { event -> println("Received a reaction of type ${event.reaction.name}, and metadata ${event.reaction.metadata}") } +// To remove the listener +unsubscribe() +``` + +```jetpack +// Jetpack Compose handles cleanup automatically +// When using subscribe directly: +val (unsubscribe) = room.reactions.subscribe { event -> + println("Received a reaction of type ${event.reaction.name}, and metadata ${event.reaction.metadata}") +} + // To remove the listener unsubscribe() ``` @@ -107,8 +132,8 @@ unsubscribe() ## Send a room reaction - -Use the [`reactions.send()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.RoomReactions.html#send)[`reactions.send()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/roomreactions/send%28withparams%3A%29)[`reactions.send()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room-reactions/send.html) method to send a room-level reaction. The most common way of using this method is to trigger it whenever a user clicks an emoji button in a room: + +Use the [`reactions.send()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.RoomReactions.html#send)[`reactions.send()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/roomreactions/send%28withparams%3A%29)[`reactions.send()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room-reactions/send.html) method to send a room-level reaction. The most common way of using this method is to trigger it whenever a user clicks an emoji button in a room: @@ -154,4 +179,39 @@ room.reactions.send(name = "heart", metadata = jsonObject { put("effect", "fireworks") }) ``` + +```jetpack +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import com.ably.chat.json.* +import kotlinx.coroutines.launch + +@Composable +fun SendReactionComponent(room: Room) { + val coroutineScope = rememberCoroutineScope() + + Button(onClick = { + coroutineScope.launch { + room.reactions.send(name = "like") + } + }) { + Text("Send Like") + } + + Button(onClick = { + coroutineScope.launch { + room.reactions.send( + name = "heart", + metadata = jsonObject { + put("effect", "fireworks") + } + ) + } + }) { + Text("Send Heart with Effect") + } +} +``` diff --git a/src/pages/docs/chat/rooms/typing.mdx b/src/pages/docs/chat/rooms/typing.mdx index ec6b07adfd..d86d3b3c54 100644 --- a/src/pages/docs/chat/rooms/typing.mdx +++ b/src/pages/docs/chat/rooms/typing.mdx @@ -7,8 +7,8 @@ Typing indicators enable you to display which users are currently writing a mess ## Subscribe to typing events - -Subscribe to typing events by registering a listener. Typing events can be emitted when a user starts typing, and when they stop typing. Use the [`typing.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Typing.html#subscribe)[`typing.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/typing/subscribe%28%29-7uox7)[`typing.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-typing/subscribe.html) method in a room to receive these updates: + +Subscribe to typing events by registering a listener. Typing events can be emitted when a user starts typing, and when they stop typing. Use the [`typing.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Typing.html#subscribe)[`typing.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/typing/subscribe%28%29-7uox7)[`typing.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-typing/subscribe.html)[`collectAsCurrentlyTyping()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-currently-typing.html) method in a room to receive these updates: @@ -55,6 +55,20 @@ val subscription = room.typing.subscribe { event: TypingSetEvent -> println("currently typing: ${event.currentlyTyping}") } ``` + +```jetpack +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import com.ably.chat.extensions.compose.collectAsCurrentlyTyping + +@Composable +fun TypingComponent(room: Room) { + val currentlyTyping by room.typing.collectAsCurrentlyTyping() + + Text("Currently typing: ${currentlyTyping.joinToString(", ")}") +} +``` ### Typing event structure @@ -91,8 +105,8 @@ You can use the size of the `currentlyTyping` set to decide whether to display i ### Unsubscribe from typing events - -Use the `unsubscribe()` function returned in the `subscribe()` response to remove a typing listener: + +Use the `unsubscribe()` function returned in the `subscribe()` response to remove a typing listener. Jetpack Compose automatically handles lifecycle and cleanup when using `collectAsCurrentlyTyping()`: @@ -103,7 +117,7 @@ You don't need to handle removing listeners, as this is done automatically by th When you unmount the component that is using the `useTyping` hook, it will automatically handle unsubscribing any associated listeners registered for typing events. - + ```javascript // Initial subscription @@ -122,6 +136,17 @@ val (unsubscribe) = room.typing.subscribe { event -> println("Typing event received: $event") } +// To remove the listener +unsubscribe() +``` + +```jetpack +// Jetpack Compose handles cleanup automatically +// When using subscribe directly: +val (unsubscribe) = room.typing.subscribe { event -> + println("Typing event received: $event") +} + // To remove the listener unsubscribe() ``` @@ -130,8 +155,8 @@ unsubscribe() ## Set typing status - -Use the [`typing.keystroke()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Typing.html#keystroke)[`typing.keystroke()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/typing/keystroke%28%29)[`typing.keystroke()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-typing/keystroke.html) method to emit a typing event with `type` set to `typing.started`. + +Use the [`typing.keystroke()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Typing.html#keystroke)[`typing.keystroke()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/typing/keystroke%28%29)[`typing.keystroke()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-typing/keystroke.html) method to emit a typing event with `type` set to `typing.started`. @@ -169,10 +194,35 @@ try await room.typing.keystroke() ```kotlin room.typing.keystroke() ``` + +```jetpack +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.runtime.* +import kotlinx.coroutines.launch + +@Composable +fun TypingKeystrokeComponent(room: Room) { + val coroutineScope = rememberCoroutineScope() + var text by remember { mutableStateOf("") } + + TextField( + value = text, + onValueChange = { newText -> + text = newText + coroutineScope.launch { + room.typing.keystroke() + } + }, + label = { Text("Type a message") } + ) +} +``` - -Use the [`stop()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Typing.html#stop)[`stop()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/typing/stop%28%29)[`stop()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-typing/stop.html) method to emit a typing event with `type` set to `typing.stopped`. + +Use the [`stop()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Typing.html#stop)[`stop()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/typing/stop%28%29)[`stop()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-typing/stop.html) method to emit a typing event with `type` set to `typing.stopped`. @@ -209,6 +259,27 @@ try await room.typing.stop() ```kotlin room.typing.stop() ``` + +```jetpack +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import kotlinx.coroutines.launch + +@Composable +fun StopTypingComponent(room: Room) { + val coroutineScope = rememberCoroutineScope() + + Button(onClick = { + coroutineScope.launch { + room.typing.stop() + } + }) { + Text("Stop Typing") + } +} +``` ### Typing Event Frequency @@ -249,8 +320,8 @@ All clients in a room must have the same timeout value configured. If not, typin ## Retrieve a list of users that are currently typing - -Use the [`typing.current`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Typing.html#current)[`typing.current`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/typing/current)[`typing.current`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-typing/current.html) property to retrieve a set of `clientId`s for all users that are currently typing in the room: + +Use the [`typing.current`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Typing.html#current)[`typing.current`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/typing/current)[`typing.current`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-typing/current.html)[`collectAsCurrentlyTyping()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-currently-typing.html) property to retrieve a set of `clientId`s for all users that are currently typing in the room: @@ -265,6 +336,20 @@ let currentlyTypingClientIds = room.typing.current ```kotlin val currentlyTypingClientIds = room.typing.current ``` + +```jetpack +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import com.ably.chat.extensions.compose.collectAsCurrentlyTyping + +@Composable +fun CurrentlyTypingComponent(room: Room) { + val currentlyTyping by room.typing.collectAsCurrentlyTyping() + + Text("Currently typing: ${currentlyTyping.joinToString(", ")}") +} +``` diff --git a/src/pages/docs/chat/setup.mdx b/src/pages/docs/chat/setup.mdx index fdb0aafdaf..ec6d85787a 100644 --- a/src/pages/docs/chat/setup.mdx +++ b/src/pages/docs/chat/setup.mdx @@ -115,7 +115,7 @@ import AblyChat - + ### Gradle The Ably Chat SDK is available on the Maven Central Repository. To include the dependency in your project, add the following to your `build.gradle.kts` file: @@ -124,6 +124,11 @@ The Ably Chat SDK is available on the Maven Central Repository. To include the d ```kotlin implementation("com.ably.chat:chat:1.0.0") ``` + +```jetpack +implementation("com.ably.chat:chat:1.0.0") +implementation("com.ably.chat:chat-extensions-compose:1.0.0") +``` For groovy: @@ -132,6 +137,11 @@ For groovy: ```kotlin implementation 'com.ably.chat:chat:1.0.0' ``` + +```jetpack +implementation("com.ably.chat:chat:1.0.0") +implementation("com.ably.chat:chat-extensions-compose:1.0.0") +``` @@ -186,6 +196,21 @@ val realtimeClient = AblyRealtime( }, ) +val chatClient = ChatClient(realtimeClient) +``` + +```jetpack +import com.ably.chat.ChatClient +import io.ably.lib.realtime.AblyRealtime +import io.ably.lib.types.ClientOptions + +val realtimeClient = AblyRealtime( +ClientOptions().apply { + key = "{{API_KEY}}" + clientId = "" +}, +) + val chatClient = ChatClient(realtimeClient) ``` @@ -214,7 +239,7 @@ Additional options can also be passed to the Chat client to customize the follow - + | Property | Description | | -------- | ----------- | @@ -271,13 +296,26 @@ val chatClient = ChatClient(realtimeClient) { logLevel = LogLevel.Debug } ``` + +```jetpack +val realtimeClient = AblyRealtime( +ClientOptions().apply { + key = "{{API_KEY}}" + clientId = "" +}, +) +val chatClient = ChatClient(realtimeClient) { + logHandler = CustomLogHandler() // Implements com.ably.chat.LogHandler interface + logLevel = LogLevel.Debug +} + ``` The `logHandler` property is your own function that will be called for each line of log output generated by the Chat SDK. - + The `logHandler` property is your custom `LogHandler` implementation that will be called for each line of log output generated by the Chat SDK. From e6875baf0e21346b0b9b9f6797bd123395d327df Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Mon, 8 Dec 2025 12:58:29 +0000 Subject: [PATCH 2/9] fixup! chat: add Jetpack Compose support to documentation --- src/data/languages/languageData.ts | 1 + src/data/languages/languageInfo.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/data/languages/languageData.ts b/src/data/languages/languageData.ts index eebaff10d9..48ae522706 100644 --- a/src/data/languages/languageData.ts +++ b/src/data/languages/languageData.ts @@ -28,6 +28,7 @@ export default { react: '1.1', swift: '1.0', kotlin: '1.0', + jetpack: '1.0', }, spaces: { javascript: '0.4', diff --git a/src/data/languages/languageInfo.ts b/src/data/languages/languageInfo.ts index f78ff81a2e..69f589b8ea 100644 --- a/src/data/languages/languageInfo.ts +++ b/src/data/languages/languageInfo.ts @@ -86,6 +86,10 @@ export default { label: 'Kotlin', syntaxHighlighterKey: 'kotlin', }, + jetpack: { + label: 'Jetpack Compose', + syntaxHighlighterKey: 'kotlin', + }, realtime: { label: 'Realtime', syntaxHighlighterKey: 'javascript', From 0293510ab244140086e2c19e36980e311110ec0b Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 12 Jan 2026 22:36:47 +0530 Subject: [PATCH 3/9] Refactored jetpack code snippets as per review comments --- src/pages/docs/chat/connect.mdx | 21 +++++++ src/pages/docs/chat/rooms/history.mdx | 8 +-- src/pages/docs/chat/rooms/index.mdx | 15 ++++- .../docs/chat/rooms/message-reactions.mdx | 11 ++++ src/pages/docs/chat/rooms/messages.mdx | 61 ++++++++++++++----- src/pages/docs/chat/rooms/occupancy.mdx | 25 +++----- src/pages/docs/chat/rooms/presence.mdx | 8 ++- src/pages/docs/chat/rooms/typing.mdx | 4 ++ src/pages/docs/chat/setup.mdx | 22 +++---- 9 files changed, 123 insertions(+), 52 deletions(-) diff --git a/src/pages/docs/chat/connect.mdx b/src/pages/docs/chat/connect.mdx index 3892a88818..acd09d12e5 100644 --- a/src/pages/docs/chat/connect.mdx +++ b/src/pages/docs/chat/connect.mdx @@ -182,6 +182,10 @@ The Chat SDK provides an `onDiscontinuity()` handler exposed via the Room object Any hooks that take an optional listener to monitor their events, such as typing indicator events in the `useTyping` hook, can also register a listener to be notified of, and handle, periods of discontinuity. + +Use the [`discontinuityAsFlow()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/discontinuity-as-flow.html) extension function to observe discontinuity events as a Flow in Jetpack Compose: + + For example, for messages: @@ -218,6 +222,23 @@ val (off) = room.onDiscontinuity { reason: ErrorInfo -> // Recover from the discontinuity } ``` + +```jetpack +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import com.ably.chat.Room +import com.ably.chat.discontinuityAsFlow + +@Composable +fun MyComponent(room: Room) { + LaunchedEffect(room) { + room.discontinuityAsFlow().collect { error -> + // Recover from the discontinuity + println("Discontinuity detected: $error") + } + } +} +``` diff --git a/src/pages/docs/chat/rooms/history.mdx b/src/pages/docs/chat/rooms/history.mdx index 1c07175960..651c57692d 100644 --- a/src/pages/docs/chat/rooms/history.mdx +++ b/src/pages/docs/chat/rooms/history.mdx @@ -8,11 +8,11 @@ The history feature enables users to retrieve messages that have been previously ## Retrieve previously sent messages -Use the [`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#history)[`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/history(withparams:))[`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/history.html)[`collectAsPagingMessagesState()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-paging-messages-state.html) method to retrieve messages that have been previously sent to a room. This returns a paginated response, which can be queried further to retrieve the next set of messages. +Use the [`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#history)[`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/history(withparams:))[`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/history.html) method to retrieve messages that have been previously sent to a room. This returns a paginated response, which can be queried further to retrieve the next set of messages. - Use the [`collectAsPagingMessagesState()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-paging-messages-state.html) method to retrieve messages that have been previously sent to a room. This returns a paginated response, which can be queried further to retrieve the next set of messages. +Use the [`collectAsPagingMessagesState()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-paging-messages-state.html) method to retrieve messages that have been previously sent to a room. This returns a paginated response, which can be queried further to retrieve the next set of messages. @@ -113,11 +113,11 @@ The following optional parameters can be passed when retrieving previously sent Users can also retrieve historical messages that were sent to a room before the point that they registered a listener by [subscribing](/docs/chat/rooms/messages#subscribe). The order of messages returned is from most recent, to oldest. This is useful for providing conversational context when a user first joins a room, or when they subsequently rejoin it later on. It also ensures that the message history they see is continuous, without any overlap of messages being returned between their subscription and their history call. -Use the [`historyBeforeSubscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.MessageSubscriptionResponse.html#historyBeforeSubscribe)[`historyBeforeSubscribe(withParams:)`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messagesubscriptionresponse/historybeforesubscribe%28withparams%3A%29))[`getPreviousMessages()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages-subscription/get-previous-messages.html) function returned as part of a [message subscription](/docs/chat/rooms/messages#subscribe) response to only retrieve messages that were received before the listener was subscribed to the room. This returns a paginated response, which can be queried further to retrieve the next set of messages. +Use the [`historyBeforeSubscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.MessageSubscriptionResponse.html#historyBeforeSubscribe)[`historyBeforeSubscribe(withParams:)`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messagesubscriptionresponse/historybeforesubscribe%28withparams%3A%29))[`historyBeforeSubscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages-subscription/history-before-subscribe.html) function returned as part of a [message subscription](/docs/chat/rooms/messages#subscribe) response to only retrieve messages that were received before the listener was subscribed to the room. This returns a paginated response, which can be queried further to retrieve the next set of messages. - Use the [`getPreviousMessages()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages-subscription/get-previous-messages.html) function returned as part of a [message subscription](/docs/chat/rooms/messages#subscribe) response to only retrieve messages that were received before the listener was subscribed to the room. This returns a paginated response, which can be queried further to retrieve the next set of messages. +Use the [`historyBeforeSubscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages-subscription/history-before-subscribe.html) function returned as part of a [message subscription](/docs/chat/rooms/messages#subscribe) response to only retrieve messages that were received before the listener was subscribed to the room. This returns a paginated response, which can be queried further to retrieve the next set of messages. diff --git a/src/pages/docs/chat/rooms/index.mdx b/src/pages/docs/chat/rooms/index.mdx index c1d48c9467..51fbea82ba 100644 --- a/src/pages/docs/chat/rooms/index.mdx +++ b/src/pages/docs/chat/rooms/index.mdx @@ -341,10 +341,15 @@ fun MyComponent(room: Room) { ``` - + You can also subscribe to room status updates by registering a listener. An event will be emitted whenever the status of the room changes. -Use the [`room.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Room.html#onStatusChange)[`room.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/room/onstatuschange%28%29-s9g)[`room.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room/on-status-change.html) method in a room to register a listener for status change updates. For Jetpack Compose, you can use [`collectAsStatus()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-status.html) to observe status changes reactively: +Use the [`room.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Room.html#onStatusChange)[`room.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/room/onstatuschange%28%29-s9g)[`room.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-room/on-status-change.html) method in a room to register a listener for status change updates: + + + +Use the [`collectAsStatus()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-status.html) extension function to observe room status changes reactively in Jetpack Compose: + ```javascript @@ -366,6 +371,11 @@ val (off) = room.onStatusChange { statusChange: RoomStatusChange -> ``` ```jetpack +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsStatus @Composable @@ -380,7 +390,6 @@ fun MyComponent(room: Room) { } ``` - Use the `off()` function returned in the `onStatusChange()` response to remove a room status listener: diff --git a/src/pages/docs/chat/rooms/message-reactions.mdx b/src/pages/docs/chat/rooms/message-reactions.mdx index 5ecce6b1a6..7cb60f0c23 100644 --- a/src/pages/docs/chat/rooms/message-reactions.mdx +++ b/src/pages/docs/chat/rooms/message-reactions.mdx @@ -167,6 +167,9 @@ import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope +import com.ably.chat.Message +import com.ably.chat.MessageReactionType +import com.ably.chat.Room import kotlinx.coroutines.launch @Composable @@ -319,6 +322,9 @@ import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope +import com.ably.chat.Message +import com.ably.chat.MessageReactionType +import com.ably.chat.Room import kotlinx.coroutines.launch @Composable @@ -456,6 +462,7 @@ room.messages.reactions.subscribe { event -> ```jetpack import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import com.ably.chat.Room @Composable fun SubscribeToReactionsComponent(room: Room) { @@ -542,6 +549,8 @@ room.messages.reactions.subscribe { event -> ```jetpack import androidx.compose.runtime.* +import com.ably.chat.Message +import com.ably.chat.Room @Composable fun ReactionsWithMessagesComponent(room: Room) { @@ -726,6 +735,8 @@ room.messages.reactions.subscribeRaw { event -> ```jetpack import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import com.ably.chat.MessageReactionEventType +import com.ably.chat.Room @Composable fun SubscribeToRawReactionsComponent(room: Room) { diff --git a/src/pages/docs/chat/rooms/messages.mdx b/src/pages/docs/chat/rooms/messages.mdx index 3b34c2ef02..8ba35bc1be 100644 --- a/src/pages/docs/chat/rooms/messages.mdx +++ b/src/pages/docs/chat/rooms/messages.mdx @@ -14,7 +14,7 @@ Subscribe to receive messages in a room by registering a listener. Use the -For Jetpack Compose, use the [`collectAsPagingMessagesState()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-paging-messages-state.html) composable function to observe messages with automatic pagination support. Alternatively, you can use [`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/subscribe.html) for a simple subscription: +Use the [`collectAsPagingMessagesState()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/jetpack/chat-extensions-compose/com.ably.chat.extensions.compose/collect-as-paging-messages-state.html) composable function to observe messages with automatic pagination support. Alternatively, you can use [`messages.subscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/subscribe.html) for a simple subscription: @@ -119,8 +119,12 @@ See [below](#global-ordering) for more information on how to apply deterministic ### Unsubscribe from messages - -Use the `unsubscribe()` function returned in the `subscribe()` response to remove a chat message listener. When using Jetpack Compose's `collectAsPagingMessagesState()`, lifecycle and cleanup are handled automatically: + +Use the `unsubscribe()` function returned in the `subscribe()` response to remove a chat message listener: + + + +`collectAsPagingMessagesState()` handles lifecycle and cleanup automatically. @@ -131,7 +135,7 @@ You don't need to handle removing listeners, as this is done automatically by th When you unmount the component that is using the `useMessages` hook, it will automatically handle unsubscribing any associated listeners registered to receive messages. - + ```javascript // Initial subscription @@ -148,16 +152,6 @@ val (unsubscribe) = room.messages.subscribe { event -> println(event.message) } // To remove the listener unsubscribe() ``` - -```jetpack -// When using subscribe directly -val (unsubscribe) = room.messages.subscribe { event -> println(event.message) } - -// To remove the listener -unsubscribe() - -// Note: collectAsPagingMessagesState() handles cleanup automatically -``` @@ -211,6 +205,13 @@ room.messages.send(text = "hello") ``` ```jetpack +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import com.ably.chat.Room +import kotlinx.coroutines.launch + @Composable fun MyComponent(room: Room) { val coroutineScope = rememberCoroutineScope() @@ -228,8 +229,8 @@ fun MyComponent(room: Room) { ## Get a single message - -Use the [`messages.get()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#get) method to get a message in a chat room. + +Use the [`messages.get()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#get)[`messages.get()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/get(serial:))[`messages.get()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/get.html) method to get a message in a chat room. @@ -258,6 +259,18 @@ const MyComponent = () => { ); }; ``` + +```swift +let message = try await room.messages.get(serial: serial) +``` + +```kotlin +val message = room.messages.get(serial) +``` + +```jetpack +val message = room.messages.get(serial) +``` @@ -323,6 +336,14 @@ val updatedMessage = room.messages.update( ``` ```jetpack +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import com.ably.chat.Message +import com.ably.chat.Room +import kotlinx.coroutines.launch + @Composable fun MyComponent(room: Room) { val coroutineScope = rememberCoroutineScope() @@ -564,6 +585,14 @@ val deletedMessage = room().messages.delete( ``` ```jetpack +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import com.ably.chat.Message +import com.ably.chat.Room +import kotlinx.coroutines.launch + @Composable fun MyComponent(room: Room) { val coroutineScope = rememberCoroutineScope() diff --git a/src/pages/docs/chat/rooms/occupancy.mdx b/src/pages/docs/chat/rooms/occupancy.mdx index d0fa47fe1f..7641d4453c 100644 --- a/src/pages/docs/chat/rooms/occupancy.mdx +++ b/src/pages/docs/chat/rooms/occupancy.mdx @@ -97,7 +97,7 @@ The following is the structure of an occupancy event: - + ```json { @@ -122,7 +122,7 @@ The following are the properties of an occupancy event: - + | Property | Description | Type | | -------- | ----------- | ---- | @@ -138,8 +138,12 @@ A user is counted for every device that they are in the room with. For example, ### Unsubscribe from room occupancy - -Use the `unsubscribe()` function returned in the `subscribe()` response to remove a room occupancy listener. Jetpack Compose automatically handles lifecycle and cleanup when using `collectAsOccupancy()`: + +Use the `unsubscribe()` function returned in the `subscribe()` response to remove a room occupancy listener: + + + +Jetpack Compose automatically handles lifecycle and cleanup when using `collectAsOccupancy()`. @@ -150,7 +154,7 @@ You don't need to handle removing listeners, as this is done automatically by th When you unmount the component that is using the `useOccupancy` hook, it will automatically handle unsubscribing any associated listeners registered for room occupancy. - + ```javascript // Initial subscription @@ -168,17 +172,6 @@ val (unsubscribe) = room.occupancy.subscribe { event -> println(event) } -// To remove the listener -unsubscribe() -``` - -```jetpack -// Jetpack Compose handles cleanup automatically -// When using subscribe directly: -val (unsubscribe) = room.occupancy.subscribe { event -> - println(event) -} - // To remove the listener unsubscribe() ``` diff --git a/src/pages/docs/chat/rooms/presence.mdx b/src/pages/docs/chat/rooms/presence.mdx index 413db77fb0..b695a4a544 100644 --- a/src/pages/docs/chat/rooms/presence.mdx +++ b/src/pages/docs/chat/rooms/presence.mdx @@ -99,8 +99,12 @@ The following are the properties of a presence event: ## Unsubscribe from presence - -Use the `unsubscribe()` function returned in the `subscribe()` response to remove a presence listener. Jetpack Compose automatically handles lifecycle and cleanup when using `collectAsPresenceMembers()`: + +Use the `unsubscribe()` function returned in the `subscribe()` response to remove a presence listener: + + + +Jetpack Compose automatically handles lifecycle and cleanup when using `collectAsPresenceMembers()`. diff --git a/src/pages/docs/chat/rooms/typing.mdx b/src/pages/docs/chat/rooms/typing.mdx index d86d3b3c54..0a29327334 100644 --- a/src/pages/docs/chat/rooms/typing.mdx +++ b/src/pages/docs/chat/rooms/typing.mdx @@ -60,6 +60,7 @@ val subscription = room.typing.subscribe { event: TypingSetEvent -> import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsCurrentlyTyping @Composable @@ -200,6 +201,7 @@ import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.runtime.* +import com.ably.chat.Room import kotlinx.coroutines.launch @Composable @@ -265,6 +267,7 @@ import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope +import com.ably.chat.Room import kotlinx.coroutines.launch @Composable @@ -341,6 +344,7 @@ val currentlyTypingClientIds = room.typing.current import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsCurrentlyTyping @Composable diff --git a/src/pages/docs/chat/setup.mdx b/src/pages/docs/chat/setup.mdx index ec6d85787a..c7c6935899 100644 --- a/src/pages/docs/chat/setup.mdx +++ b/src/pages/docs/chat/setup.mdx @@ -205,10 +205,10 @@ import io.ably.lib.realtime.AblyRealtime import io.ably.lib.types.ClientOptions val realtimeClient = AblyRealtime( -ClientOptions().apply { - key = "{{API_KEY}}" - clientId = "" -}, + ClientOptions().apply { + key = "{{API_KEY}}" + clientId = "" + }, ) val chatClient = ChatClient(realtimeClient) @@ -299,16 +299,16 @@ val chatClient = ChatClient(realtimeClient) { ```jetpack val realtimeClient = AblyRealtime( -ClientOptions().apply { - key = "{{API_KEY}}" - clientId = "" -}, + ClientOptions().apply { + key = "{{API_KEY}}" + clientId = "" + }, ) val chatClient = ChatClient(realtimeClient) { - logHandler = CustomLogHandler() // Implements com.ably.chat.LogHandler interface - logLevel = LogLevel.Debug + logHandler = CustomLogHandler() // Implements com.ably.chat.LogHandler interface + logLevel = LogLevel.Debug } - ``` +``` From 37360d5d0f11616f3a075802487956927a63efef Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 13 Jan 2026 18:47:15 +0530 Subject: [PATCH 4/9] Added missing doc for swift, kotlin and jetpack for message-reactions --- .../docs/chat/rooms/message-reactions.mdx | 92 ++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/src/pages/docs/chat/rooms/message-reactions.mdx b/src/pages/docs/chat/rooms/message-reactions.mdx index 7cb60f0c23..442b564005 100644 --- a/src/pages/docs/chat/rooms/message-reactions.mdx +++ b/src/pages/docs/chat/rooms/message-reactions.mdx @@ -23,7 +23,7 @@ Note that if sending two identical reactions of type `Distinct`, the second one ### Configure the default reaction type - + The default reaction type can be configured at room-level by passing `RoomOptions` when calling `rooms.get`. If nothing is set, the default is `Distinct`. @@ -421,6 +421,96 @@ interface Message { } } ``` + +```swift +struct Message { + // ... (other fields omitted) + var reactions: MessageReactionSummary +} + +struct MessageReactionSummary { + var unique: [String: ClientIDList] + var distinct: [String: ClientIDList] + var multiple: [String: ClientIDCounts] +} + +// example (in real use, it is unlikely that all reaction types are present): +// ... other message fields omitted +reactions: MessageReactionSummary( + unique: [ + "👍": ClientIDList(total: 2, clientIDs: ["clientA", "clientB"], clipped: false), + "❤️": ClientIDList(total: 1, clientIDs: ["clientC"], clipped: false), + ], + distinct: [ + "👍": ClientIDList(total: 2, clientIDs: ["clientA", "clientB"], clipped: false), + "❤️": ClientIDList(total: 1, clientIDs: ["clientA"], clipped: false), + ], + multiple: [ + "👍": ClientIDCounts(total: 10, clientIDs: ["clientA": 7, "clientB": 3], totalUnidentified: 0, clipped: false, totalClientIDs: 2), + "❤️": ClientIDCounts(total: 100, clientIDs: ["clientA": 100], totalUnidentified: 0, clipped: false, totalClientIDs: 1), + ] +) +``` + +```kotlin +interface Message { + // ... (other fields omitted) + val reactions: MessageReactionSummary +} + +interface MessageReactionSummary { + val unique: Map + val distinct: Map + val multiple: Map +} + +// example (in real use, it is unlikely that all reaction types are present): +// ... other message fields omitted +reactions = MessageReactionSummary( + unique = mapOf( + "👍" to SummaryClientIdList(total = 2, clientIds = listOf("clientA", "clientB"), clipped = false), + "❤️" to SummaryClientIdList(total = 1, clientIds = listOf("clientC"), clipped = false), + ), + distinct = mapOf( + "👍" to SummaryClientIdList(total = 2, clientIds = listOf("clientA", "clientB"), clipped = false), + "❤️" to SummaryClientIdList(total = 1, clientIds = listOf("clientA"), clipped = false), + ), + multiple = mapOf( + "👍" to SummaryClientIdCounts(total = 10, clientIds = mapOf("clientA" to 7, "clientB" to 3), totalUnidentified = 0, clipped = false, totalClientIds = 2), + "❤️" to SummaryClientIdCounts(total = 100, clientIds = mapOf("clientA" to 100), totalUnidentified = 0, clipped = false, totalClientIds = 1), + ) +) +``` + +```jetpack +interface Message { + // ... (other fields omitted) + val reactions: MessageReactionSummary +} + +interface MessageReactionSummary { + val unique: Map + val distinct: Map + val multiple: Map +} + +// example (in real use, it is unlikely that all reaction types are present): +// ... other message fields omitted +reactions = MessageReactionSummary( + unique = mapOf( + "👍" to SummaryClientIdList(total = 2, clientIds = listOf("clientA", "clientB"), clipped = false), + "❤️" to SummaryClientIdList(total = 1, clientIds = listOf("clientC"), clipped = false), + ), + distinct = mapOf( + "👍" to SummaryClientIdList(total = 2, clientIds = listOf("clientA", "clientB"), clipped = false), + "❤️" to SummaryClientIdList(total = 1, clientIds = listOf("clientA"), clipped = false), + ), + multiple = mapOf( + "👍" to SummaryClientIdCounts(total = 10, clientIds = mapOf("clientA" to 7, "clientB" to 3), totalUnidentified = 0, clipped = false, totalClientIds = 2), + "❤️" to SummaryClientIdCounts(total = 100, clientIds = mapOf("clientA" to 100), totalUnidentified = 0, clipped = false, totalClientIds = 1), + ) +) +``` All reaction types are always available via `Message.reactions`, regardless of the default reaction type configured via room options. From 1abfd276f71aa61574e8969e552e62ebcdf95f22 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 13 Jan 2026 19:06:27 +0530 Subject: [PATCH 5/9] - Fixed history.mdx for kotlin and jetpack using historyBeforeSubscribe method - Added missing Room imports for occupancy and presence - Fixed android.mdx to use `historyBeforeSubscribe` method instead of `getPreviousMessages` --- .../docs/chat/getting-started/android.mdx | 4 +- src/pages/docs/chat/rooms/history.mdx | 38 ++++++++++++++----- .../docs/chat/rooms/message-reactions.mdx | 1 - src/pages/docs/chat/rooms/occupancy.mdx | 1 + src/pages/docs/chat/rooms/presence.mdx | 1 + 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/pages/docs/chat/getting-started/android.mdx b/src/pages/docs/chat/getting-started/android.mdx index 7a63016ceb..93d2e107d7 100644 --- a/src/pages/docs/chat/getting-started/android.mdx +++ b/src/pages/docs/chat/getting-started/android.mdx @@ -536,7 +536,7 @@ When you click on the edit button in the UI, you can modify the text and it will ## Step 6: Message history and continuity -Ably Chat enables you to retrieve previously sent messages in a room. This is useful for providing conversational context when a user first joins a room, or when they subsequently rejoin it later on. The message subscription object exposes the [`getPreviousMessages()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages-subscription/get-previous-messages.html) method to enable this functionality. This method returns a paginated response, which can be queried further to retrieve the next set of messages. +Ably Chat enables you to retrieve previously sent messages in a room. This is useful for providing conversational context when a user first joins a room, or when they subsequently rejoin it later on. The message subscription object exposes the [`historyBeforeSubscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages-subscription/history-before-subscribe.html) method to enable this functionality. This method returns a paginated response, which can be queried further to retrieve the next set of messages. Extend the `ChatBox` component to include a method to retrieve the last 10 messages when the component mounts. In your `MainActivity.kt` file, add the `DisposableEffect` in your `ChatBox` component: @@ -702,7 +702,7 @@ fun ChatBox(room: Room?) { Do the following to test this out: 1. Use the ably CLI to simulate sending some messages to the room from another client. -2. Refresh the page, this will cause the `ChatBox` component to mount again and call the `getPreviousMessages()` method. +2. Refresh the page, this will cause the `ChatBox` component to mount again and call the `historyBeforeSubscribe()` method. 3. You'll see the last 10 messages appear in the chat box. ## Step 7: Display who is present in the room diff --git a/src/pages/docs/chat/rooms/history.mdx b/src/pages/docs/chat/rooms/history.mdx index 651c57692d..0e3e1a4b34 100644 --- a/src/pages/docs/chat/rooms/history.mdx +++ b/src/pages/docs/chat/rooms/history.mdx @@ -81,6 +81,8 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import com.ably.chat.OrderBy +import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsPagingMessagesState @Composable @@ -187,7 +189,7 @@ val subscription = room.messages.subscribe { println("New message received") } -var historicalMessages = subscription.getPreviousMessages(limit = 50) +var historicalMessages = subscription.historyBeforeSubscribe(limit = 50) println(historicalMessages.items.toString()) while (historicalMessages.hasNext()) { @@ -199,30 +201,48 @@ println("End of messages") ``` ```jetpack +import androidx.compose.foundation.layout.Column +import androidx.compose.material.Text import androidx.compose.runtime.* -import com.ably.chat.extensions.compose.collectAsPagingMessagesState +import com.ably.chat.Room +import kotlinx.coroutines.launch @Composable fun HistoryBeforeSubscribeComponent(room: Room) { + val coroutineScope = rememberCoroutineScope() + var messages by remember { mutableStateOf>(emptyList()) } + DisposableEffect(room) { val subscription = room.messages.subscribe { println("New message received") } - var historicalMessages = subscription.getPreviousMessages(limit = 50) - println(historicalMessages.items.toString()) - - while (historicalMessages.hasNext()) { - historicalMessages = historicalMessages.next() + // Launch coroutine for suspend function calls + coroutineScope.launch { + var historicalMessages = subscription.historyBeforeSubscribe(limit = 50) println(historicalMessages.items.toString()) - } + messages = historicalMessages.items.map { it.text } - println("End of messages") + while (historicalMessages.hasNext()) { + historicalMessages = historicalMessages.next() + println(historicalMessages.items.toString()) + messages = messages + historicalMessages.items.map { it.text } + } + + println("End of messages") + } onDispose { subscription.unsubscribe() } } + + // Display messages in UI + Column { + messages.forEach { message -> + Text(message) + } + } } ``` diff --git a/src/pages/docs/chat/rooms/message-reactions.mdx b/src/pages/docs/chat/rooms/message-reactions.mdx index 442b564005..1b34a79ca8 100644 --- a/src/pages/docs/chat/rooms/message-reactions.mdx +++ b/src/pages/docs/chat/rooms/message-reactions.mdx @@ -666,7 +666,6 @@ fun ReactionsWithMessagesComponent(room: Room) { } } } -// Note: `room.collectAsPagingMessagesState()` handles everything automatically ``` ```react diff --git a/src/pages/docs/chat/rooms/occupancy.mdx b/src/pages/docs/chat/rooms/occupancy.mdx index 7641d4453c..5b503a37d3 100644 --- a/src/pages/docs/chat/rooms/occupancy.mdx +++ b/src/pages/docs/chat/rooms/occupancy.mdx @@ -63,6 +63,7 @@ val subscription = room.occupancy.subscribe { event: OccupancyEvent -> import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsOccupancy @Composable diff --git a/src/pages/docs/chat/rooms/presence.mdx b/src/pages/docs/chat/rooms/presence.mdx index b695a4a544..94a4bdad39 100644 --- a/src/pages/docs/chat/rooms/presence.mdx +++ b/src/pages/docs/chat/rooms/presence.mdx @@ -69,6 +69,7 @@ val subscription = room.presence.subscribe { event: PresenceEvent -> import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsPresenceMembers @Composable From f3638306784445ae73ac566bd7358a0a2fe3dd1a Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 13 Jan 2026 19:28:13 +0530 Subject: [PATCH 6/9] Fixed gradle dep. installation syntax groovy for chat-kotlin --- src/pages/docs/chat/setup.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/docs/chat/setup.mdx b/src/pages/docs/chat/setup.mdx index c7c6935899..f5191b4706 100644 --- a/src/pages/docs/chat/setup.mdx +++ b/src/pages/docs/chat/setup.mdx @@ -122,12 +122,12 @@ The Ably Chat SDK is available on the Maven Central Repository. To include the d ```kotlin - implementation("com.ably.chat:chat:1.0.0") + implementation("com.ably.chat:chat:1.1.0") ``` ```jetpack -implementation("com.ably.chat:chat:1.0.0") -implementation("com.ably.chat:chat-extensions-compose:1.0.0") +implementation("com.ably.chat:chat:1.1.0") +implementation("com.ably.chat:chat-extensions-compose:1.1.0") ``` @@ -135,12 +135,12 @@ For groovy: ```kotlin - implementation 'com.ably.chat:chat:1.0.0' + implementation 'com.ably.chat:chat:1.1.0' ``` ```jetpack -implementation("com.ably.chat:chat:1.0.0") -implementation("com.ably.chat:chat-extensions-compose:1.0.0") +implementation 'com.ably.chat:chat:1.1.0' +implementation 'com.ably.chat:chat-extensions-compose:1.1.0' ``` From 4a5066e78afb70f090318501f7758fa5d603bb47 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 13 Jan 2026 20:10:13 +0530 Subject: [PATCH 7/9] Package imports shortened to make sure devs can focus on actual snippets rather than imports --- src/pages/docs/chat/connect.mdx | 6 ++- src/pages/docs/chat/rooms/history.mdx | 11 ++-- src/pages/docs/chat/rooms/index.mdx | 9 ++-- .../docs/chat/rooms/message-reactions.mdx | 22 ++++---- src/pages/docs/chat/rooms/messages.mdx | 18 +++---- src/pages/docs/chat/rooms/occupancy.mdx | 22 ++++---- src/pages/docs/chat/rooms/presence.mdx | 52 +++++++++---------- src/pages/docs/chat/rooms/reactions.mdx | 12 ++--- src/pages/docs/chat/rooms/typing.mdx | 20 +++---- 9 files changed, 76 insertions(+), 96 deletions(-) diff --git a/src/pages/docs/chat/connect.mdx b/src/pages/docs/chat/connect.mdx index acd09d12e5..49d0632d35 100644 --- a/src/pages/docs/chat/connect.mdx +++ b/src/pages/docs/chat/connect.mdx @@ -135,6 +135,9 @@ val (off) = chatClient.connection.onStatusChange { statusChange: ConnectionStatu ``` ```jetpack +import androidx.compose.material.* +import androidx.compose.runtime.* +import com.ably.chat.ChatClient import com.ably.chat.extensions.compose.collectAsStatus @Composable @@ -224,8 +227,7 @@ val (off) = room.onDiscontinuity { reason: ErrorInfo -> ``` ```jetpack -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.* import com.ably.chat.Room import com.ably.chat.discontinuityAsFlow diff --git a/src/pages/docs/chat/rooms/history.mdx b/src/pages/docs/chat/rooms/history.mdx index 8069fd3e48..aca86b07cd 100644 --- a/src/pages/docs/chat/rooms/history.mdx +++ b/src/pages/docs/chat/rooms/history.mdx @@ -77,10 +77,9 @@ println("End of messages") ``` ```jetpack -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.foundation.lazy.* +import androidx.compose.material.* +import androidx.compose.runtime.* import com.ably.chat.OrderBy import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsPagingMessagesState @@ -201,8 +200,8 @@ println("End of messages") ``` ```jetpack -import androidx.compose.foundation.layout.Column -import androidx.compose.material.Text +import androidx.compose.foundation.layout.* +import androidx.compose.material.* import androidx.compose.runtime.* import com.ably.chat.Room import kotlinx.coroutines.launch diff --git a/src/pages/docs/chat/rooms/index.mdx b/src/pages/docs/chat/rooms/index.mdx index 66bbf37286..e4f9338874 100644 --- a/src/pages/docs/chat/rooms/index.mdx +++ b/src/pages/docs/chat/rooms/index.mdx @@ -336,6 +336,9 @@ val status = room.status ``` ```jetpack +import androidx.compose.material.* +import androidx.compose.runtime.* +import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsStatus @Composable @@ -377,10 +380,8 @@ val (off) = room.onStatusChange { statusChange: RoomStatusChange -> ``` ```jetpack -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue +import androidx.compose.material.* +import androidx.compose.runtime.* import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsStatus diff --git a/src/pages/docs/chat/rooms/message-reactions.mdx b/src/pages/docs/chat/rooms/message-reactions.mdx index 1b34a79ca8..a59cc6b750 100644 --- a/src/pages/docs/chat/rooms/message-reactions.mdx +++ b/src/pages/docs/chat/rooms/message-reactions.mdx @@ -163,10 +163,8 @@ room.messages.reactions.send(message, ``` ```jetpack -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.material.* +import androidx.compose.runtime.* import com.ably.chat.Message import com.ably.chat.MessageReactionType import com.ably.chat.Room @@ -318,10 +316,8 @@ const MyComponent = () => { ``` ```jetpack -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.material.* +import androidx.compose.runtime.* import com.ably.chat.Message import com.ably.chat.MessageReactionType import com.ably.chat.Room @@ -550,8 +546,7 @@ room.messages.reactions.subscribe { event -> ``` ```jetpack -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.* import com.ably.chat.Room @Composable @@ -646,10 +641,12 @@ import com.ably.chat.Room fun ReactionsWithMessagesComponent(room: Room) { var messages by remember { mutableStateOf>(emptyList()) } - DisposableEffect(room) { + LaunchedEffect(room) { // init messages messages = room.messages.history(limit = 50).items + } + DisposableEffect(room) { // subscribe to message reactions summary events val (unsubscribe) = room.messages.reactions.subscribe { event -> // find the relevant message (in practice: use binary search or a map for lookups) @@ -822,8 +819,7 @@ room.messages.reactions.subscribeRaw { event -> ``` ```jetpack -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.* import com.ably.chat.MessageReactionEventType import com.ably.chat.Room diff --git a/src/pages/docs/chat/rooms/messages.mdx b/src/pages/docs/chat/rooms/messages.mdx index 3aa585ee4d..6524ed9b09 100644 --- a/src/pages/docs/chat/rooms/messages.mdx +++ b/src/pages/docs/chat/rooms/messages.mdx @@ -209,10 +209,8 @@ room.messages.send(text = "hello") ``` ```jetpack -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.material.* +import androidx.compose.runtime.* import com.ably.chat.Room import kotlinx.coroutines.launch @@ -340,10 +338,8 @@ val updatedMessage = room.messages.update( ``` ```jetpack -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.material.* +import androidx.compose.runtime.* import com.ably.chat.Message import com.ably.chat.Room import kotlinx.coroutines.launch @@ -589,10 +585,8 @@ val deletedMessage = room().messages.delete( ``` ```jetpack -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.material.* +import androidx.compose.runtime.* import com.ably.chat.Message import com.ably.chat.Room import kotlinx.coroutines.launch diff --git a/src/pages/docs/chat/rooms/occupancy.mdx b/src/pages/docs/chat/rooms/occupancy.mdx index 5b503a37d3..620b4893fc 100644 --- a/src/pages/docs/chat/rooms/occupancy.mdx +++ b/src/pages/docs/chat/rooms/occupancy.mdx @@ -60,9 +60,8 @@ val subscription = room.occupancy.subscribe { event: OccupancyEvent -> ``` ```jetpack -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.material.* +import androidx.compose.runtime.* import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsOccupancy @@ -201,9 +200,9 @@ val occupancy = room.occupancy.current ``` ```jetpack -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.material.* +import androidx.compose.runtime.* +import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsOccupancy @Composable @@ -261,13 +260,10 @@ val occupancy = room.occupancy.get() ``` ```jetpack -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material.* +import androidx.compose.runtime.* +import com.ably.chat.OccupancyData +import com.ably.chat.Room @Composable fun GetOccupancyComponent(room: Room) { diff --git a/src/pages/docs/chat/rooms/presence.mdx b/src/pages/docs/chat/rooms/presence.mdx index 94a4bdad39..7c2582d9fb 100644 --- a/src/pages/docs/chat/rooms/presence.mdx +++ b/src/pages/docs/chat/rooms/presence.mdx @@ -66,9 +66,8 @@ val subscription = room.presence.subscribe { event: PresenceEvent -> ``` ```jetpack -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.material.* +import androidx.compose.runtime.* import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsPresenceMembers @@ -211,9 +210,8 @@ room.presence.enter( ``` ```jetpack -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.* +import com.ably.chat.Room import com.ably.chat.json.* @Composable @@ -282,10 +280,9 @@ room.presence.update( ``` ```jetpack -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.material.* +import androidx.compose.runtime.* +import com.ably.chat.Room import com.ably.chat.json.* import kotlinx.coroutines.launch @@ -359,19 +356,24 @@ room.presence.leave( ``` ```jetpack -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.* +import com.ably.chat.Room import com.ably.chat.json.* +import kotlinx.coroutines.launch @Composable fun LeavePresenceComponent(room: Room) { + val coroutineScope = rememberCoroutineScope() + DisposableEffect(room) { onDispose { - room.presence.leave( - jsonObject { - put("status", "Be back later!") - } - ) + coroutineScope.launch { + room.presence.leave( + jsonObject { + put("status", "Be back later!") + } + ) + } } } } @@ -479,9 +481,9 @@ val presentMember = room.presence.get(clientId = "clemons123") ``` ```jetpack -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.material.* +import androidx.compose.runtime.* +import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsPresenceMembers @Composable @@ -512,13 +514,9 @@ val isPresent = room.presence.isUserPresent("client-id") ``` ```jetpack -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material.* +import androidx.compose.runtime.* +import com.ably.chat.Room @Composable fun IsUserPresentComponent(room: Room, clientId: String) { diff --git a/src/pages/docs/chat/rooms/reactions.mdx b/src/pages/docs/chat/rooms/reactions.mdx index 4385beafc5..4988e267dc 100644 --- a/src/pages/docs/chat/rooms/reactions.mdx +++ b/src/pages/docs/chat/rooms/reactions.mdx @@ -52,8 +52,9 @@ val subscription = room.reactions.subscribe { event: RoomReactionEvent -> ``` ```jetpack -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.* +import com.ably.chat.Room +import com.ably.chat.RoomReactionEvent @Composable fun RoomReactionsComponent(room: Room) { @@ -181,10 +182,9 @@ room.reactions.send(name = "heart", metadata = jsonObject { ``` ```jetpack -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.material.* +import androidx.compose.runtime.* +import com.ably.chat.Room import com.ably.chat.json.* import kotlinx.coroutines.launch diff --git a/src/pages/docs/chat/rooms/typing.mdx b/src/pages/docs/chat/rooms/typing.mdx index 0a29327334..5d3a0cb67f 100644 --- a/src/pages/docs/chat/rooms/typing.mdx +++ b/src/pages/docs/chat/rooms/typing.mdx @@ -57,9 +57,8 @@ val subscription = room.typing.subscribe { event: TypingSetEvent -> ``` ```jetpack -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.material.* +import androidx.compose.runtime.* import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsCurrentlyTyping @@ -197,9 +196,7 @@ room.typing.keystroke() ``` ```jetpack -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.material.TextField +import androidx.compose.material.* import androidx.compose.runtime.* import com.ably.chat.Room import kotlinx.coroutines.launch @@ -263,10 +260,8 @@ room.typing.stop() ``` ```jetpack -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.material.* +import androidx.compose.runtime.* import com.ably.chat.Room import kotlinx.coroutines.launch @@ -341,9 +336,8 @@ val currentlyTypingClientIds = room.typing.current ``` ```jetpack -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.material.* +import androidx.compose.runtime.* import com.ably.chat.Room import com.ably.chat.extensions.compose.collectAsCurrentlyTyping From 4e71d1245eb5ffa9055512595308b546243719dd Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 13 Jan 2026 20:25:32 +0530 Subject: [PATCH 8/9] - Added kotlin icon for jetpack temporarily till we have jetpack icon merged into ably-ui - Bumped up kotlin and jetpack version to 1.1 --- src/data/languages/languageData.ts | 4 ++-- src/data/languages/languageInfo.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/data/languages/languageData.ts b/src/data/languages/languageData.ts index ad59704c5a..baba22da4e 100644 --- a/src/data/languages/languageData.ts +++ b/src/data/languages/languageData.ts @@ -39,8 +39,8 @@ export default { javascript: '1.1', react: '1.1', swift: '1.0', - kotlin: '1.0', - jetpack: '1.0', + kotlin: '1.1', + jetpack: '1.1', }, spaces: { javascript: '0.4', diff --git a/src/data/languages/languageInfo.ts b/src/data/languages/languageInfo.ts index 69f589b8ea..c1ff9c00c6 100644 --- a/src/data/languages/languageInfo.ts +++ b/src/data/languages/languageInfo.ts @@ -89,6 +89,7 @@ export default { jetpack: { label: 'Jetpack Compose', syntaxHighlighterKey: 'kotlin', + alias: 'kotlin', }, realtime: { label: 'Realtime', From d086414471f6a4dfcff992d686430bc617df2339 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 14 Jan 2026 13:04:29 +0530 Subject: [PATCH 9/9] Updated/refactored code snippets for jetpack compose in web docs --- .../docs/chat/getting-started/android.mdx | 41 +++++++++---------- src/pages/docs/chat/rooms/history.mdx | 21 +++++----- .../docs/chat/rooms/message-reactions.mdx | 7 +--- src/pages/docs/chat/rooms/presence.mdx | 19 +++++---- 4 files changed, 42 insertions(+), 46 deletions(-) diff --git a/src/pages/docs/chat/getting-started/android.mdx b/src/pages/docs/chat/getting-started/android.mdx index ab0841503c..e49c4b51b8 100644 --- a/src/pages/docs/chat/getting-started/android.mdx +++ b/src/pages/docs/chat/getting-started/android.mdx @@ -252,8 +252,8 @@ fun ChatBox(room: Room?) { var sending by remember { mutableStateOf(false) } val messages = remember { mutableStateListOf() } - DisposableEffect(room) { - val subscription = room?.messages?.subscribe { event -> + LaunchedEffect(room) { + room?.messages?.asFlow()?.collect { event -> when (event.type) { MessageEventType.Created -> { // Check if the incoming message is correctly ordered @@ -266,10 +266,6 @@ fun ChatBox(room: Room?) { else -> Unit } } - - onDispose { - subscription?.unsubscribe() - } } Column( @@ -445,8 +441,8 @@ var edited: Message? by remember { mutableStateOf(null) } ```kotlin -DisposableEffect(room) { - val subscription = room?.messages?.subscribe { event -> +LaunchedEffect(room) { + room?.messages?.asFlow()?.collect { event -> when (event.type) { MessageEventType.Created -> messages.add(0, event.message) MessageEventType.Updated -> messages.replaceFirstWith(event.message) { @@ -455,10 +451,6 @@ DisposableEffect(room) { else -> Unit } } - - onDispose { - subscription?.unsubscribe() - } } ``` @@ -551,9 +543,10 @@ Extend the `ChatBox` component to include a method to retrieve the last 10 messa ```kotlin fun ChatBox(room: Room?) { /* variables declaration */ + var subscription by remember { mutableStateOf(null) } DisposableEffect(room) { - val subscription = room?.messages?.subscribe { event -> + subscription = room?.messages?.subscribe { event -> when (event.type) { MessageEventType.Created -> messages.add(0, event.message) MessageEventType.Updated -> messages.replaceFirstWith(event.message) { @@ -563,16 +556,19 @@ fun ChatBox(room: Room?) { } } - scope.launch { - val previousMessages = subscription?.historyBeforeSubscribe(10)?.items ?: emptyList() - messages.addAll(previousMessages) - } - onDispose { subscription?.unsubscribe() } - } - /* rest of your code */ + } + + LaunchedEffect(subscription) { + subscription?.let { sub -> + val previousMessages = sub.historyBeforeSubscribe(10)?.items ?: emptyList() + messages.addAll(previousMessages) + } + } + + /* rest of your code */ } ``` @@ -722,14 +718,15 @@ In your `MainActivity.kt` file, create a new component called `PresenceStatusUi` ```kotlin @Composable fun PresenceStatusUi(room: Room?) { - val members = room?.collectAsPresenceMembers() + val membersState = room?.collectAsPresenceMembers() + val members by membersState ?: remember { mutableStateOf(emptyList()) } LaunchedEffect(room) { room?.presence?.enter() } Text( - text = "Online: ${members?.size ?: 0}", + text = "Online: ${members.size}", style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(start = 8.dp) ) diff --git a/src/pages/docs/chat/rooms/history.mdx b/src/pages/docs/chat/rooms/history.mdx index aca86b07cd..2673214b58 100644 --- a/src/pages/docs/chat/rooms/history.mdx +++ b/src/pages/docs/chat/rooms/history.mdx @@ -203,22 +203,27 @@ println("End of messages") import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* +import com.ably.chat.MessagesSubscription import com.ably.chat.Room -import kotlinx.coroutines.launch @Composable fun HistoryBeforeSubscribeComponent(room: Room) { - val coroutineScope = rememberCoroutineScope() var messages by remember { mutableStateOf>(emptyList()) } + var subscription by remember { mutableStateOf(null) } DisposableEffect(room) { - val subscription = room.messages.subscribe { + subscription = room.messages.subscribe { println("New message received") } - // Launch coroutine for suspend function calls - coroutineScope.launch { - var historicalMessages = subscription.historyBeforeSubscribe(limit = 50) + onDispose { + subscription?.unsubscribe() + } + } + + LaunchedEffect(subscription) { + subscription?.let { sub -> + var historicalMessages = sub.historyBeforeSubscribe(limit = 50) println(historicalMessages.items.toString()) messages = historicalMessages.items.map { it.text } @@ -230,10 +235,6 @@ fun HistoryBeforeSubscribeComponent(room: Room) { println("End of messages") } - - onDispose { - subscription.unsubscribe() - } } // Display messages in UI diff --git a/src/pages/docs/chat/rooms/message-reactions.mdx b/src/pages/docs/chat/rooms/message-reactions.mdx index a59cc6b750..6424bb50fa 100644 --- a/src/pages/docs/chat/rooms/message-reactions.mdx +++ b/src/pages/docs/chat/rooms/message-reactions.mdx @@ -646,9 +646,9 @@ fun ReactionsWithMessagesComponent(room: Room) { messages = room.messages.history(limit = 50).items } - DisposableEffect(room) { + LaunchedEffect(room) { // subscribe to message reactions summary events - val (unsubscribe) = room.messages.reactions.subscribe { event -> + room.messages.reactions.asFlow().collect { event -> // find the relevant message (in practice: use binary search or a map for lookups) val idx = messages.indexOfLast { msg -> msg.serial == event.messageSerial } if (idx != -1) { @@ -658,9 +658,6 @@ fun ReactionsWithMessagesComponent(room: Room) { } } } - onDispose { - unsubscribe() - } } } ``` diff --git a/src/pages/docs/chat/rooms/presence.mdx b/src/pages/docs/chat/rooms/presence.mdx index 7c2582d9fb..57958868cb 100644 --- a/src/pages/docs/chat/rooms/presence.mdx +++ b/src/pages/docs/chat/rooms/presence.mdx @@ -356,6 +356,7 @@ room.presence.leave( ``` ```jetpack +import androidx.compose.material.* import androidx.compose.runtime.* import com.ably.chat.Room import com.ably.chat.json.* @@ -365,16 +366,16 @@ import kotlinx.coroutines.launch fun LeavePresenceComponent(room: Room) { val coroutineScope = rememberCoroutineScope() - DisposableEffect(room) { - onDispose { - coroutineScope.launch { - room.presence.leave( - jsonObject { - put("status", "Be back later!") - } - ) - } + Button(onClick = { + coroutineScope.launch { + room.presence.leave( + jsonObject { + put("status", "Be back later!") + } + ) } + }) { + Text("Leave Presence") } } ```