Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"validate-llms-txt": "node bin/validate-llms.txt.ts"
},
"dependencies": {
"@ably/ui": "17.9.16",
"@ably/ui": "17.11.4",
"@codesandbox/sandpack-react": "^2.20.0",
"@codesandbox/sandpack-themes": "^2.0.21",
"@gfx/zopfli": "^1.0.15",
Expand Down
3 changes: 2 additions & 1 deletion src/data/languages/languageData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export default {
javascript: '1.1',
react: '1.1',
swift: '1.0',
kotlin: '1.0',
kotlin: '1.1',
jetpack: '1.1',
},
spaces: {
javascript: '0.4',
Expand Down
4 changes: 4 additions & 0 deletions src/data/languages/languageInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ export default {
label: 'Kotlin',
syntaxHighlighterKey: 'kotlin',
},
jetpack: {
label: 'Jetpack Compose',
syntaxHighlighterKey: 'kotlin',
},
realtime: {
label: 'Realtime',
syntaxHighlighterKey: 'javascript',
Expand Down
1 change: 1 addition & 0 deletions src/data/languages/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const languageKeys = [
'css',
'laravel',
'typescript',
'jetpack',
] as const;

export type LanguageKey = (typeof languageKeys)[number];
Expand Down
57 changes: 57 additions & 0 deletions src/pages/docs/chat/connect.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Use the <If lang="javascript">[`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:
</If>

<If lang="jetpack">
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:
</If>

<Code>
```javascript
const connectionStatus = chatClient.connection.status;
Expand Down Expand Up @@ -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")
}
```
</Code>

<If lang="react">
Expand Down Expand Up @@ -84,6 +99,10 @@ Listeners can also be registered to monitor the changes in connection status. An
Use the <If lang="javascript">[`connection.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Connection.html#onStatusChange)</If><If lang="swift">[`connection.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/connection/onstatuschange%28%29-76t7)</If><If lang="kotlin">[`connection.status.onStatusChange()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-connection/on-status-change.html)</If> method to register a listener for status change updates:
</If>

<If lang="jetpack">
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:
</If>

<Code>
```javascript
const { off } = chatClient.connection.onStatusChange((change) => console.log(change));
Expand Down Expand Up @@ -114,6 +133,24 @@ val (off) = chatClient.connection.onStatusChange { statusChange: ConnectionStatu
println(statusChange.toString())
}
```

```jetpack
import androidx.compose.material.*
import androidx.compose.runtime.*
import com.ably.chat.ChatClient
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")
}
```
</Code>

<If lang="javascript,kotlin">
Expand Down Expand Up @@ -148,6 +185,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.
</If>

<If lang="jetpack">
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:
</If>

For example, for messages:

<Code>
Expand Down Expand Up @@ -184,6 +225,22 @@ val (off) = room.onDiscontinuity { reason: ErrorInfo ->
// Recover from the discontinuity
}
```

```jetpack
import androidx.compose.runtime.*
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")
}
}
}
```
</Code>

<If lang="javascript,kotlin">
Expand Down
45 changes: 21 additions & 24 deletions src/pages/docs/chat/getting-started/android.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,8 @@ fun ChatBox(room: Room?) {
var sending by remember { mutableStateOf(false) }
val messages = remember { mutableStateListOf<Message>() }

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
Expand All @@ -266,10 +266,6 @@ fun ChatBox(room: Room?) {
else -> Unit
}
}

onDispose {
subscription?.unsubscribe()
}
}

Column(
Expand Down Expand Up @@ -445,8 +441,8 @@ var edited: Message? by remember { mutableStateOf(null) }

<Code>
```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) {
Expand All @@ -455,10 +451,6 @@ DisposableEffect(room) {
else -> Unit
}
}

onDispose {
subscription?.unsubscribe()
}
}
```
</Code>
Expand Down Expand Up @@ -543,17 +535,18 @@ When you click on the edit button in the UI, you can modify the text and it will

## Step 6: Message history and continuity <a id="step-6"/>

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:

<Code>
```kotlin
fun ChatBox(room: Room?) {
/* variables declaration */
var subscription by remember { mutableStateOf<MessagesSubscription?>(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) {
Expand All @@ -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 */
}
```
</Code>
Expand Down Expand Up @@ -709,7 +705,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 <a id="step-7"/>
Expand All @@ -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 = membersState?.value ?: 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)
)
Expand Down
80 changes: 79 additions & 1 deletion src/pages/docs/chat/rooms/history.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ The history feature enables users to retrieve messages that have been previously
Use the <If lang="javascript">[`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Messages.html#history)</If><If lang="swift">[`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messages/history(withparams:))</If><If lang="kotlin">[`messages.history()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages/history.html)</If> 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.
</If>

<If lang="jetpack">
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.
</If>

<If lang="react">
Use the [`history()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-react.UseMessagesResponse.html#history) method available from the response of the `useMessages` hook 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.
</If>
Expand Down Expand Up @@ -71,6 +75,29 @@ while (historicalMessages.hasNext()) {

println("End of messages")
```

```jetpack
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

@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}")
}
}
}
```
</Code>

The following optional parameters can be passed when retrieving previously sent messages:
Expand All @@ -87,7 +114,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.

<If lang="javascript,swift,kotlin">
Use the <If lang="javascript">[`historyBeforeSubscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.MessageSubscriptionResponse.html#historyBeforeSubscribe)</If><If lang="swift">[`historyBeforeSubscribe(withParams:)`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messagesubscriptionresponse/historybeforesubscribe%28withparams%3A%29))</If><If lang="kotlin">[`getPreviousMessages()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages-subscription/get-previous-messages.html)</If> 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 <If lang="javascript">[`historyBeforeSubscribe()`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.MessageSubscriptionResponse.html#historyBeforeSubscribe)</If><If lang="swift">[`historyBeforeSubscribe(withParams:)`](https://sdk.ably.com/builds/ably/ably-chat-swift/main/AblyChat/documentation/ablychat/messagesubscriptionresponse/historybeforesubscribe%28withparams%3A%29))</If><If lang="kotlin">[`historyBeforeSubscribe()`](https://sdk.ably.com/builds/ably/ably-chat-kotlin/main/dokka/chat/com.ably.chat/-messages-subscription/history-before-subscribe.html)</If> 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.
</If>

<If lang="jetpack">
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.
</If>

<If lang="react">
Expand Down Expand Up @@ -167,6 +198,53 @@ while (historicalMessages.hasNext()) {

println("End of messages")
```

```jetpack
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import com.ably.chat.MessagesSubscription
import com.ably.chat.Room

@Composable
fun HistoryBeforeSubscribeComponent(room: Room) {
var messages by remember { mutableStateOf<List<String>>(emptyList()) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe Jetpack recommends

Suggested change
var messages by remember { mutableStateOf<List<String>>(emptyList()) }
val myMessageList by remember { mutableStateListOf<Message>() }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai, according to claude this won't work because

  1. mutableStateListOf returns SnapshotStateList, not State, so by delegation won't work
  2. The code later tries to reassign myMessageList = ... but it's declared as val
    Can you check if this is true

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sacOO7, Claude is absolutely correct on both points:

  1. mutableStateListOf returns SnapshotStateList<T>, not State<T>: The mutableStateListOf function returns an object of type SnapshotStateList<T>. The by delegation syntax requires a type that implements the getValue and setValue operators (which State<T> provides), but SnapshotStateList<T> does not support delegation. Using by with mutableStateListOf produces an error: "Type 'TypeVariable(T)' has no method 'getValue(Nothing?, KProperty<*>)' and thus it cannot serve as a delegate".

  2. Cannot reassign a val: The current code reassigns messages multiple times (messages = ...), which requires var, not val.

The correct way to use mutableStateListOf would be:

val messages = remember { mutableStateListOf<String>() }  // No 'by', just '='

// Then use list mutation operations instead of reassignment:
messages.addAll(historicalMessages.items.map { it.text })

However, your current approach with var messages by remember { mutableStateOf<List<String>>(emptyList()) } is actually perfectly valid and appropriate for this use case. It uses immutable list replacement rather than mutable list operations. Both patterns work fine in Compose—choose based on whether you prefer immutable list replacement or mutable list operations.

var subscription by remember { mutableStateOf<MessagesSubscription?>(null) }

DisposableEffect(room) {
subscription = room.messages.subscribe {
println("New message received")
}

onDispose {
subscription?.unsubscribe()
}
}

LaunchedEffect(subscription) {
subscription?.let { sub ->
var historicalMessages = sub.historyBeforeSubscribe(limit = 50)
println(historicalMessages.items.toString())
messages = historicalMessages.items.map { it.text }

while (historicalMessages.hasNext()) {
historicalMessages = historicalMessages.next()
println(historicalMessages.items.toString())
messages = messages + historicalMessages.items.map { it.text }
}

println("End of messages")
}
}

// Display messages in UI
Column {
messages.forEach { message ->
Text(message)
}
}
}
```
</Code>

The following parameters can be passed when retrieving previously sent messages:
Expand Down
Loading