Getting started: Chat with Kotlin
This guide will help you get started with Ably Chat in a new Android Kotlin application built with Jetpack Compose.
It will take you through the following steps:
- Creating a client and establishing a realtime connection to Ably
- Creating a room and subscribing to its messages
- Sending messages to the room and editing messages
- Retrieving historical messages to provide context for new joiners
- Displaying online status of clients in the room
- Subscribing to and sending reactions
- Disconnecting and resource cleanup
Prerequisites
Ably
-
Sign up for an Ably account.
-
Create a new app and get your API key. You can use the root API key that is provided by default to get started.
-
Install the Ably CLI:
npm install -g @ably/cli
- Run the following to log in to your Ably account and set the default app and API key:
ably login
ably apps switch
ably auth keys switch
Create a new project
Create a new Android project with Jetpack Compose. For detailed instructions, refer to the Android Studio documentation.
- Create a new Android project in Android Studio.
- Select Empty Activity as the template
- Name the project Chat Example and place it in the
com.example.chatexample
package - Set the minimum SDK level to API 24 or higher
- Select Kotlin as the programming language
- Add the Ably dependencies to your app-level
build.gradle.kts
file:
1
2
3
4
implementation("com.ably.chat:chat-android:<latest-version>")
// This package contains extension functions for better Jetpack Compose integration.
// It's experimental for now (safe to use, but the API may change later). You can always use its code as a reference.
implementation("com.ably.chat:chat-extensions-compose:<latest-version>")
Step 1: Setting up Ably
Replace the contents of your MainActivity.kt
file with the following code to set up the Ably client.
Replace {{API_KEY}}
with your Ably API key from the dashboard.
Note that this is for example purposes only. In production, you should use token authentication to avoid exposing your API keys publicly:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.example.chatexample
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.material.icons.*
import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.*
import com.example.chatexample.ui.theme.ChatExampleTheme
import com.ably.chat.*
import com.ably.chat.extensions.compose.*
import io.ably.lib.realtime.AblyRealtime
import io.ably.lib.types.ClientOptions
import kotlinx.coroutines.launch
import java.text.*
import java.util.*
class MainActivity : ComponentActivity() {
private lateinit var realtimeClient: AblyRealtime
private lateinit var chatClient: ChatClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
realtimeClient = AblyRealtime(
ClientOptions().apply {
key = "{{API_KEY}}" // In production, you should use token authentication to avoid exposing your API keys publicly
clientId = "my-first-client"
},
)
chatClient = ChatClient(realtimeClient) { logLevel = LogLevel.Info }
enableEdgeToEdge()
setContent {
ChatExampleTheme {
App(chatClient)
}
}
}
}
@Composable
fun App(chatClient: ChatClient) {
Scaffold { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(paddingValues)
) {
Text("Hello Chat App")
}
}
}
Step 2: Connect to Ably
Clients establish a connection with Ably when they instantiate an SDK. This enables them to send and receive messages in realtime across channels.
In your MainActivity.kt
file, add the following ConnectionStatusUi
composable component:
1
2
3
4
5
6
7
8
9
10
// This component will display the current connection status
@Composable
fun ConnectionStatusUi(connection: Connection) {
val connectionStatus = connection.collectAsStatus()
Text(
text = "Connection Status: $connectionStatus",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(start = 8.dp)
)
}
Update the App
component to display the connection status using the new ConnectionStatusUi
component:
1
2
3
4
5
6
7
8
9
10
11
12
13
@Composable
fun App(chatClient: ChatClient) {
Scaffold { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(paddingValues)
) {
ConnectionStatusUi(connection = chatClient.connection)
}
}
}
Run your application by pressing Run button.
Step 3: Create a room
Now that you have a connection to Ably, you can create a room. Use rooms to separate and organize clients and messages into different topics, or 'chat rooms'. Rooms are the entry point for Chat, providing access to all of its features, such as messages, presence and reactions.
In your project, open MainActivity.kt
, and add a new component called RoomStatusUi
:
1
2
3
4
5
6
7
8
9
@Composable
fun RoomStatusUi(roomName: String, room: Room?) {
val roomStatus = room?.collectAsStatus()
Text(
text = "Room Name: ${roomName}, Room Status: ${roomStatus ?: ""}",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(start = 8.dp)
)
}
Update your main app component to get and attach to the room and nest the RoomStatusUi
component inside it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Composable
fun App(chatClient: ChatClient) {
val roomName = "my-first-room"
var room by remember { mutableStateOf<Room?>(null) }
LaunchedEffect(roomName) {
val chatRoom = chatClient.rooms.get(roomName)
chatRoom.attach()
room = chatRoom
}
Scaffold { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(paddingValues)
) {
ConnectionStatusUi(connection = chatClient.connection)
RoomStatusUi(roomName = roomName, room = room)
}
}
}
The above code creates a room with the ID my-first-room
and sets up a listener to monitor the room status. It also displays the room ID and current status in the UI.
Step 4: Send a message
Messages are how your clients interact with one another.
In your project, open MainActivity.kt
, and add a new component called ChatBox
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
@Composable
fun ChatBox(room: Room?) {
val scope = rememberCoroutineScope()
var textInput by remember { mutableStateOf(TextFieldValue("")) }
var sending by remember { mutableStateOf(false) }
val messages = remember { mutableStateListOf<Message>() }
DisposableEffect(room) {
val subscription = room?.messages?.subscribe { event ->
when (event.type) {
MessageEventType.Created -> messages.add(0, event.message)
else -> Unit
}
}
onDispose {
subscription?.unsubscribe()
}
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Card(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
) {
if (messages.isNullOrEmpty()) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "No messages yet",
color = Color.Gray
)
}
} else {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(8.dp),
reverseLayout = true,
) {
items(messages.size, key = { messages[it].serial }) {
val message = messages[it]
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
) {
Text(
text = "${message.clientId}: ${message.text}",
style = MaterialTheme.typography.bodyMedium
)
Text(
text = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
.format(Date(message.timestamp)),
style = MaterialTheme.typography.labelSmall,
color = Color.Gray,
modifier = Modifier.align(Alignment.End)
)
HorizontalDivider(modifier = Modifier.padding(top = 4.dp))
}
}
}
}
}
Spacer(modifier = Modifier.height(8.dp))
// Message input
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
OutlinedTextField(
value = textInput,
onValueChange = { textInput = it },
modifier = Modifier.weight(1f),
placeholder = { Text("Type a message") },
maxLines = 3
)
Spacer(modifier = Modifier.width(8.dp))
Button(
onClick = {
sending = true
if (textInput.text.isNotBlank()) {
scope.launch {
try {
room?.messages?.send(textInput.text)
} catch (e: Exception) {
Log.e("APP", e.message, e)
}
textInput = TextFieldValue("")
sending = false
}
}
},
enabled = textInput.text.isNotBlank() && !sending
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.Send,
contentDescription = "Send"
)
}
}
}
}
Add the ChatBox
component to your main app component:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Composable
fun App(chatClient: ChatClient) {
val roomName = "my-first-room"
var room by remember { mutableStateOf<Room?>(null) }
LaunchedEffect(roomName) {
val chatRoom = chatClient.rooms.get(
roomName,
)
chatRoom.attach()
room = chatRoom
}
Scaffold { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(paddingValues)
) {
ConnectionStatusUi(connection = chatClient.connection)
RoomStatusUi(roomName = roomName, room = room)
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
ChatBox(room = room)
}
}
}
The UI should automatically render the new component, and you should be able to send messages to the room.
Type a message in the input box and click the send button. You'll see the message appear in the chat box.
You can also use the Ably CLI to send a message to the room from another environment:
ably rooms messages send my-first-room 'Hello from CLI!'
You'll see the message in your app's chat box UI. If you have sent a message via CLI, it should appear in a different color to the one you sent from the app.
Step 5: Edit a message
If your client makes a typo, or needs to update their original message then they can edit it. To do this, you can extend the functionality of the ChatBox
component to allow updating of messages.
- We will begin by adding this utility function to facilitate message data updates in the UI:
1
2
3
4
inline fun <T> MutableList<T>.replaceFirstWith(replacement: T, predicate: (T) -> Boolean) {
val index = indexOfFirst(predicate)
if (index != -1) set(index, replacement)
}
- Add the edited state variable:
1
var edited: Message? by remember { mutableStateOf(null) }
- Update the message subscription to handle edited messages:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DisposableEffect(room) {
val subscription = room?.messages?.subscribe { event ->
when (event.type) {
MessageEventType.Created -> messages.add(0, event.message)
MessageEventType.Updated -> messages.replaceFirstWith(event.message) {
it.serial == event.message.serial
}
else -> Unit
}
}
onDispose {
subscription?.unsubscribe()
}
}
- Let's enhance the message display. To add an edit button to each message, we'll first need to locate
the
Column
composable function within your layout inside theitems(messages.size, key = { messages[it].serial }) {
declarative loop. This is the component responsible for rendering the message content itself, along with the sender's username and the message timestamp. Once we've identified thisColumn
, we will integrate the new edit button alongside these existing elements. Replace theColumn
with the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.weight(1f)
.padding(vertical = 4.dp)
) {
// Message content
Text(
text = "${message.clientId}: ${message.text}",
style = MaterialTheme.typography.bodyMedium
)
// Timestamp
Text(
text = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
.format(Date(message.timestamp)),
style = MaterialTheme.typography.labelSmall,
color = Color.Gray,
modifier = Modifier.align(Alignment.End)
)
HorizontalDivider(modifier = Modifier.padding(top = 4.dp))
}
// Edit button
IconButton(onClick = {
edited = message
textInput = TextFieldValue(message.text)
}) {
Icon(Icons.Default.Edit, contentDescription = "Edit")
}
}
- Update the send button to handle both new messages and edits:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Button(
onClick = {
sending = true
if (textInput.text.isNotBlank()) {
scope.launch {
try {
edited?.let {
room?.messages?.update(it.copy(text = textInput.text))
} ?: room?.messages?.send(textInput.text)
} catch (e: Exception) {
Log.e("APP", e.message, e)
}
edited = null
textInput = TextFieldValue("")
sending = false
}
}
},
enabled = textInput.text.isNotBlank() && !sending
) {
Icon(
imageVector = if (edited != null) Icons.Filled.Edit
else Icons.AutoMirrored.Filled.Send,
contentDescription = if (edited != null) "Edit" else "Send"
)
}
When you click on the edit button in the UI, you can modify the text and it will send the updated message contents to the room.
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()
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
fun ChatBox(room: Room?) {
/* variables declaration */
DisposableEffect(room) {
val subscription = room?.messages?.subscribe { event ->
when (event.type) {
MessageEventType.Created -> messages.add(0, event.message)
MessageEventType.Updated -> messages.replaceFirstWith(event.message) {
it.serial == event.message.serial
}
else -> Unit
}
}
scope.launch {
val previousMessages = subscription?.getPreviousMessages(10)?.items ?: emptyList()
messages.addAll(previousMessages)
}
onDispose {
subscription?.unsubscribe()
}
}
/* rest of your code */
}
The above code will retrieve the last 10 messages when the component mounts, and set them in the state.
You also can use collectAsPagingMessagesState
to automatically subscribe to new messages
and lazily load previous messages as you scroll through the message list.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
@Composable
fun ChatBox(room: Room?) {
val scope = rememberCoroutineScope()
var textInput by remember { mutableStateOf(TextFieldValue("")) }
val messagesState = room?.collectAsPagingMessagesState()
var sending by remember { mutableStateOf(false) }
var edited by remember { mutableStateOf<Message?>(null) }
val messages = messagesState?.loaded
Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Card(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
) {
if (messages.isNullOrEmpty()) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "No messages yet",
color = Color.Gray
)
}
} else {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(8.dp),
reverseLayout = true,
state = messagesState.listState,
) {
items(messages.size, key = { messages[it].serial }) {
val message = messages[it]
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.weight(1f)
.padding(vertical = 4.dp)
) {
Text(
text = "${message.clientId}: ${message.text}",
style = MaterialTheme.typography.bodyMedium
)
Text(
text = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
.format(Date(message.timestamp)),
style = MaterialTheme.typography.labelSmall,
color = Color.Gray,
modifier = Modifier.align(Alignment.End)
)
HorizontalDivider(modifier = Modifier.padding(top = 4.dp))
}
IconButton(onClick = {
edited = message
textInput = TextFieldValue(message.text)
}) {
Icon(Icons.Default.Edit, contentDescription = "Edit")
}
}
}
}
}
}
Spacer(modifier = Modifier.height(8.dp))
// Message input
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
OutlinedTextField(
value = textInput,
onValueChange = { textInput = it },
modifier = Modifier.weight(1f),
placeholder = { Text("Type a message") },
maxLines = 3
)
Spacer(modifier = Modifier.width(8.dp))
Button(
onClick = {
sending = true
if (textInput.text.isNotBlank()) {
scope.launch {
try {
edited?.let {
room?.messages?.update(it.copy(text = textInput.text))
} ?: room?.messages?.send(textInput.text)
} catch (e: Exception) {
Log.e("APP", e.message, e)
}
edited = null
textInput = TextFieldValue("")
sending = false
}
}
},
enabled = textInput.text.isNotBlank() && !sending
) {
Icon(
imageVector = if (edited != null) Icons.Default.Edit
else Icons.AutoMirrored.Filled.Send,
contentDescription = if (edited != null) "Edit" else "Send"
)
}
}
}
}
Do the following to test this out:
- Use the ably CLI to simulate sending some messages to the room from another client.
- Refresh the page, this will cause the
ChatBox
component to mount again and call thegetPreviousMessages()
method. - You'll see the last 10 messages appear in the chat box.
Step 7: Display who is present in the room
Display the online status of clients using the presence feature. This enables clients to be aware of one another if they are present in the same room. You can then show clients who else is online, provide a custom status update for each, and notify the room when someone enters it, or leaves it, such as by going offline.
In your MainActivity.kt
file, create a new component called PresenceStatusUi
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Composable
fun PresenceStatusUi(room: Room?) {
val members = room?.collectAsPresenceMembers()
LaunchedEffect(room) {
room?.presence?.enter()
}
Text(
text = "Online: ${members?.size ?: 0}",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(start = 8.dp)
)
}
Add the PresenceStatusUi
component to your main app component:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Composable
fun App(chatClient: ChatClient) {
val roomName = "my-first-room"
var room by remember { mutableStateOf<Room?>(null) }
LaunchedEffect(roomName) {
val chatRoom = chatClient.rooms.get(
roomName,
)
chatRoom.attach()
room = chatRoom
}
Scaffold { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(paddingValues)
) {
ConnectionStatusUi(connection = chatClient.connection)
RoomStatusUi(roomName = roomName, room = room)
PresenceStatusUi(room = room)
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
ChatBox(room = room)
}
}
}
You'll now see your current client ID in the list of present users.
You can also use the Ably CLI to enter the room from another client by running the following command:
ably rooms presence enter my-first-room --client-id "my-cli"
Step 8: Send a reaction
Clients can send a reaction to a room to show their sentiment for what is happening, such as a point being scored in a sports game. These are short-lived (ephemeral) and are not stored in the room history.
In your MainActivity.kt
file, add a new component called ReactionBar
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@Composable
fun ReactionBar(room: Room?) {
val scope = rememberCoroutineScope()
val availableReactions = listOf("👍", "❤️", "💥", "🚀", "👎", "💔")
val receivedReactions = remember { mutableStateListOf<Reaction>() }
LaunchedEffect(room) {
room?.reactions?.asFlow()?.collect {
receivedReactions.add(it)
}
}
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Reaction send buttons
LazyRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
contentPadding = PaddingValues(vertical = 4.dp)
) {
items(availableReactions) { emoji ->
Button(
onClick = {
scope.launch {
room?.reactions?.send(emoji)
}
},
colors = ButtonDefaults.buttonColors(containerColor = Color.LightGray)
) {
Text(
text = emoji,
fontSize = 12.sp
)
}
}
}
// Display received reactions
if (receivedReactions.isNotEmpty()) {
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Received Reactions",
modifier = Modifier.padding(vertical = 2.dp)
)
LazyRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
contentPadding = PaddingValues(vertical = 4.dp)
) {
items(receivedReactions) { reaction ->
Box(
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = reaction.name,
fontSize = 12.sp
)
}
}
}
}
}
}
}
Add the ReactionBar
component to your main app component:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Composable
fun App(chatClient: ChatClient) {
val roomName = "my-first-room"
var room by remember { mutableStateOf<Room?>(null) }
LaunchedEffect(roomName) {
val chatRoom = chatClient.rooms.get(
roomName,
)
chatRoom.attach()
room = chatRoom
}
Scaffold { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(paddingValues)
) {
ConnectionStatusUi(connection = chatClient.connection)
RoomStatusUi(roomName = roomName, room = room)
PresenceStatusUi(room = room)
ReactionBar(room = room)
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
ChatBox(room = room)
}
}
}
The above code should display a list of reactions that can be sent to the room. When you click on a reaction, it will send it to the room and display it in the UI.
You can also send a reaction to the room via the Ably CLI by running the following command:
ably rooms reactions send my-first-room 👍
Step 9: Disconnection and clean up resources
To gracefully close connection and clean up resources, you can subscribe to activity lifecycle events and close the connection when activity has paused or stopped, and then reconnect when activity resumes. To do this modify MainActivity
class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class MainActivity : ComponentActivity() {
private lateinit var realtimeClient: AblyRealtime
private lateinit var chatClient: ChatClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
realtimeClient = AblyRealtime(
ClientOptions().apply {
key = "{{API_KEY}}"
clientId = "my-first-client"
},
)
chatClient = ChatClient(realtimeClient) { logLevel = LogLevel.Info }
enableEdgeToEdge()
setContent {
ChatExampleTheme {
App(chatClient)
}
}
}
override fun onRestart() {
super.onRestart()
realtimeClient.connect()
}
override fun onResume() {
super.onResume()
realtimeClient.connect()
}
override fun onPause() {
super.onPause()
realtimeClient.close()
}
override fun onStop() {
super.onStop()
realtimeClient.close()
}
}
Next steps
Continue exploring Ably Chat with Kotlin:
Read more about the concepts covered in this guide:
- Read more about using rooms and sending messages.
- Find out more regarding online status.
- Understand how to use typing indicators.
- Send reactions to your rooms.
- Read into pulling messages from history and providing context to new joiners.
- Understand token authentication before going to production.
Explore the Ably CLI further, or check out the Chat Kotlin API references for additional functionality.