Message threads
The message threads feature organizes conversations into threads, making it easier to follow and respond to specific topics.
Users need threads to:
-
Set topic-specific discussions - within group chats or 1:1 conversations, you can initiate or participate in separate threads dedicated to specific topics, ensuring focused and structured discussions.
-
Clarify and resolve issues - message threads let you address specific problems or questions within a conversation, allowing you to provide relevant input and ensure clarity and efficient problem-solving.
-
Reduce confusion - by allowing you to focus the conversation in a separate thread, you minimize cross-talk and make the conversation easier to follow.
In the Chat SDK, a thread is a separate channel created for a selected message. Such a thread channel gets the ID starting with PUBNUB_INTERNAL_THREAD followed by the channel ID and message ID (separated by underscores). The message for which you create a thread channel has the hasThread parameter set to true to distinguish it from other messages. All messages in a thread (thread messages) are ordered by timetokens containing info on when messages were published.
Each thread message and thread channel are separate entities in the Chat SDK that extend the Message and Channel entities. This means that they offer separate methods for handling threads but also let you use all methods under the Message and Channel entities (for example, for editing or deleting).
Create thread
createThread() creates a thread (channel) for a selected message.
Method signature
This method has the following signature:
1createThread() async throws -> ThreadChannelImpl
Input
This method doesn't take any parameters.
Output
| Parameter | Description |
|---|---|
ThreadChannelImpl | Object returning the thread channel metadata for the message updated with the hasThread parameter (and a threadRootId action type underneath). |
Sample code
Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
Create a thread for a message on the support channel.
1// Assuming you have a reference of type "ChatImpl" named "chat"
2Task {
3 if let channel = try await chat.getChannel(channelId: "support") {
4 // Timetoken for the message you're trying to get
5 let timetoken: Timetoken = 16200000000000001
6 // Get the message with the specified timetoken
7 if let message = try await channel.getMessage(timetoken: timetoken) {
8 let threadChannel = try await message.createThread()
9 debugPrint("Thread created successfully with ID: \(threadChannel.id)")
10 debugPrint("Parent channel ID: \(threadChannel.parentChannelId)")
11 } else {
12 debugPrint("No message found with this timetoken")
13 }
14 } else {
15 debugPrint("Channel not found")
show all 17 linesSend thread message
Reply to a message in a thread by calling the sendText() method from the previously created ThreadChannel object.
Method signature
Head over to the sendText() method section for details.
Sample code
Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
Send a message in a thread created for the last message on the support channel.
1// Assuming you have a reference of type "ChatImpl" named "chat"
2Task {
3 if let channel = try await chat.getChannel(channelId: "support") {
4 // Timetoken for the message you're trying to get
5 let timetoken: Timetoken = 16200000000000001
6 // Get the message with the specified timetoken
7 if let message = try await channel.getMessage(timetoken: timetoken) {
8 // Get the thread channel for a given message
9 let threadChannel = try await message.getThread()
10 // Send a new message to the thread channel
11 let replyInThreadTimetoken = try await threadChannel.sendText(text: "Good job, guys!")
12 // Print the timetoken of the message that has been sent
13 debugPrint("Reply in a thread successfuly sent at \(replyInThreadTimetoken)")
14 } else {
15 debugPrint("No message found with this timetoken")
show all 20 linesGet thread
Get the thread channel on which the thread message is published.
Method signature
This method has the following signature:
1message.getThread() async throws -> ThreadChannelImpl
Input
This method doesn't take any parameters.
Output
| Type | Description |
|---|---|
ThreadChannelImpl | Object returning the thread channel metadata. |
Sample code
Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
Get the thread channel created from the message with the 16200000000000001 timetoken.
1// Assuming you have a reference of type "ChatImpl" named "chat"
2Task {
3 if let channel = try await chat.getChannel(channelId: "support") {
4 // Timetoken for the message you're trying to get
5 let timetoken: Timetoken = 16200000000000001
6 // Get the message with the specified timetoken
7 if let message = try await channel.getMessage(timetoken: timetoken) {
8 let threadChannel = try await message.getThread()
9 debugPrint("Thread channel ID: \(threadChannel.id)")
10 debugPrint("Parent channel ID: \(threadChannel.parentChannelId)")
11 } else {
12 debugPrint("No message found with this timetoken")
13 }
14 } else {
15 debugPrint("Channel not found")
show all 17 linesCheck if message starts thread
You can access the hasThread property of the Message object to check if a given message starts a thread.
Sample code
Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
Check if the message with the 16200000000000001 timetoken starts a thread.
1// Assuming you have a reference of type "ChatImpl" named "chat"
2Task {
3 if let channel = try await chat.getChannel(channelId: "support") {
4 // Timetoken for the message you're trying to get
5 let timetoken: Timetoken = 16200000000000001
6 // Get the message with the specified timetoken
7 if let message = try await channel.getMessage(timetoken: timetoken) {
8 debugPrint("Message fetched successfully: \(message.text)")
9 debugPrint("Does this message have a thread: \(message.hasThread)")
10 } else {
11 debugPrint("No message found with this timetoken")
12 }
13 } else {
14 debugPrint("Channel not found")
15 }
show all 16 linesGet thread message updates
You can receive updates when specific message threads and related message reactions are added, edited, or removed on other clients using the streamUpdatesOn() on the ThreadMessage class.
This method returns an asynchronous stream which produces a new value whenever someone adds, edits or deletes a message, or adds or removes a message reaction to/from a list of thread messages.
Underneath, this method subscribes the current user to a channel and adds a message reactions event listener to receive all messageAction events of type added or removed.
Method signature
This method takes the following parameters:
1ThreadMessageImpl.streamUpdatesOn(messages: [ThreadMessageImpl]) -> AsyncStream<[ThreadMessageImpl]>
Input
| Parameter | Description |
|---|---|
messages *Type: [ThreadMessageImpl]Default: n/a | Collection of ThreadMessageImpl objects for which you want to get updates on changed message threads or related message reactions. |
Output
| Parameter | Description |
|---|---|
AsyncStream<[ThreadMessageImpl]> | An asynchronous stream that produces updates when any item in the messages collection is updated. |
Sample code
Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
Get message threads and message reaction-related updates for the first page of messages published in a thread on the support channel.
- AsyncStream
- Closure
1// Assuming you have a reference of type "ThreadChannelImpl" named "threadChannel"
2Task {
3 if let threadMessage = try await threadChannel.getHistory(count: 1).messages.first {
4 for await receivedThreadMessages in ThreadMessageImpl.streamUpdatesOn(messages: [threadMessage]) {
5 debugPrint("Received elements: \(receivedThreadMessages)")
6 }
7 } else {
8 debugPrint("Message not found")
9 }
10}
1// Important: Keep a strong reference to the returned "AutoCloseable" object as long as you want
2// to receive updates. If the "AutoCloseable" is deallocated, the stream will be cancelled,
3// and no further items will be produced. You can also stop receiving updates manually
4// by calling the "close()" method on the "AutoCloseable" object.
5
6// Assuming you have a reference of type "ThreadMessageImpl" named "threadMessage"
7autoCloseable = ThreadMessageImpl.streamUpdatesOn(messages: [threadMessage]) { updatedThreadMessages in
8 updatedThreadMessages.forEach { updatedThreadMessage in
9 debugPrint("-=Updated thread message: \(updatedThreadMessage)")
10 }
11}
Get thread channel updates
You can receive updates when specific ThreadChannel object(s) are edited or removed on other clients using the streamUpdatesOn() method that checks updates on a channel list and it's tied to the ThreadChannel class.
This method returns an asynchronous stream which produces a new value whenever someone adds, changes, or removes channel thread metadata.
Underneath, this method subscribes the current user to a channel and add an objects event listener to receive all objects events of type channel.
Method signature
This method takes the following parameters:
1ThreadChannelImpl.streamUpdatesOn(
2 channels: [ThreadChannelImpl],
3) -> AsyncStream<[ThreadChannelImpl]>
Input
| Parameter | Description |
|---|---|
channels *Type: ThreadChannelImpl]Default: n/a | Array of ThreadChannelImpl objects for which you want to get updates on changed channel threads. |
Output
| Parameter | Description |
|---|---|
AsyncStream<[ThreadChannelImpl]> | An asynchronous stream that produces updates when any item in the channels collection is updated. |
Sample code
Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
- AsyncStream
- Closure
1// Assuming you have two references, "threadChannel" and "anotherThreadChannel", of type "ThreadChannelImpl"
2Task {
3 for await updatedThreadChannel in ThreadChannelImpl.streamUpdatesOn(channels: [threadChannel, anotherThreadChannel]) {
4 debugPrint("Updated thread channel: \(updatedThreadChannel)")
5 }
6}
1// Important: Keep a strong reference to the returned "AutoCloseable" object as long as you want
2// to receive new updates. If the "AutoCloseable" is deallocated, the stream will be cancelled,
3// and no further items will be produced. You can also stop receiving updates manually
4// by calling the "close()" method on the "AutoCloseable" object.
5
6// Assuming you have an array of "ThreadChannelImpl" objects named "threadChannels
7autoCloseable = ThreadChannelImpl.streamUpdatesOn(channels: threadChannels) { updatedThreadChannels in
8 updatedThreadChannels.forEach { updatedThreadChannel in
9 debugPrint("Updated thread channel: \(updatedThreadChannel)")
10 }
11}
Get historical thread message
getHistory() called on the ThreadChannel object fetches historical thread messages from that thread channel.
Method signature
This method takes the following parameters:
1threadChannel.getHistory(
2 startTimetoken: Timetoken? = nil,
3 endTimetoken: Timetoken? = nil,
4 count: Int = 25
5 ) async throws -> (messages: [ThreadMessageImpl], isMore: Bool)
Input
| Parameter | Description |
|---|---|
startTimetokenType: TimetokenDefault: n/a | Timetoken delimiting the start of a time slice (exclusive) to pull thread messages from. For details, refer to the Fetch History section. |
endTimetokenType: TimetokenDefault: n/a | Timetoken delimiting the end of a time slice (inclusive) to pull thread messages from. For details, refer to the Fetch History section. |
count *Type: IntDefault: 25 | Number of historical thread messages to return for the channel in a single call. Since each call returns all attached message reactions by default, the maximum number of returned thread messages is 25. For more details, refer to the description of the includeMessageActions parameter in the Swift SDK docs. |
Output
| Parameter | Description |
|---|---|
(messages: [ThreadMessageImpl], isMore: Bool)Type: object | A tuple containing a list of ThreadMessageImpl objects and a boolean flag indicating if there are more messages available. |
By default, each call returns all message reactions and metadata attached to the retrieved thread messages.
Sample code
Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
Fetch 10 historical thread messages that are older than the timetoken 15343325214676133.
1// Assuming you have a reference of type "ThreadChannelImpl" named "threadChannel"
2Task {
3 let response = try await threadChannel.getHistory(
4 startTimetoken: 15343325214676133,
5 count: 10
6 )
7 response.messages.forEach { message in
8 debugPrint("Thread message: \(message.channelId) - \(message.text)")
9 }
10
11 // The code below demonstrates how to fetch the next page of messages. Skip it if you're only
12 // interested in the initial set.
13
14 // Retrieves the timetoken of the oldest message from the previous response
15 let theOldestTimetoken = response.messages.compactMap { $0.timetoken }.min()
show all 23 linesRemove thread
removeThread() removes a thread (channel) for a selected message.
Method signature
This method has the following signature:
1message.removeThread() async throws -> ChannelImpl
Input
This method doesn't take any parameters.
Output
| Parameter | Description |
|---|---|
ChannelImpl | The updated channel object after the removal of the thread. |
Sample code
Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
Remove a thread for the last message on the support channel.
1// Assuming you have a reference of type "ChatImpl" named "chat"
2Task {
3 if let channel = try await chat.getChannel(channelId: "support") {
4 if let message = try await channel.getHistory(count: 1).messages.first {
5 let updatedChannel = try await message.removeThread()
6 debugPrint("Thread removed successfully.")
7 debugPrint("Updated Channel: \(String(describing: updatedChannel))")
8 } else {
9 debugPrint("Message not found")
10 }
11 } else {
12 debugPrint("Channel not found")
13 }
14}
Pin thread message to thread channel
pinMessage() called on the ThreadChannel object pins a selected thread message to the thread channel.
Method signature
This method takes the following parameters:
1threadChannel.pinMessage(
2 message: MessageImpl
3) async throws -> ThreadChannelImpl
Input
| Parameter | Description |
|---|---|
message *Type: MessageImplDefault: n/a | MessageImpl object you want to pin to the selected thread channel. |
Output
| Parameter | Description |
|---|---|
ThreadChannelImpl | Object returning the thread channel metadata updated with these custom fields: pinnedMessageTimetoken to mark the timetoken when the message was pinned pinnedMessageChannelID to mark the channel on which the message was pinned to the thread channel (unpinning was performed either directly on the parent channel or on a thread channel). |
Sample code
Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
A thread was created for the last message in the support parent channel. Pin the last message from this thread to the thread channel.
1// Assuming you have a reference of type "ChatImpl" named "chat"
2Task {
3 if let channel = try await chat.getChannel(channelId: "support") {
4 // Get the last message on the channel, which is the root message for the thread
5 if let message = try await channel.getHistory(count: 1).messages.first {
6 // Get the thread channel
7 let threadChannel = try await message.getThread()
8 // Get the last message on the thread channel
9 if let threadMessage = try await threadChannel.getHistory(count: 1).messages.first {
10 // Pin the message from the thread to the thread channel
11 let updatedChannel = try await threadChannel.pinMessage(message: threadMessage)
12 // Prints the updated channel object
13 debugPrint("Updated channel object: \(updatedChannel)")
14 } else {
15 debugPrint("No messages found in the thread channel.")
show all 23 linesPin thread message to parent channel
You can pin a selected thread message to the parent channel with pinMessageToParentChannel() and pinToParentChannel(). They give the same output, and the only difference is that you call a given method either on the ThreadChannel or the ThreadMessage object. Depending on the object, these methods take different input parameters - you have to specify the thread message you want to pin or not because it's already known.
Method signature
These methods take the following parameters:
-
pinMessageToParentChannel()(on theThreadChannelobject)1threadChannel.pinMessageToParentChannel(
2 message: ThreadMessageImpl
3) async throws -> ChannelImpl -
pinToParentChannel()(on theThreadMessageobject)1threadMessage.pinToParentChannel() async throws -> ChannelImpl
Input
| Parameter | Required in pinMessageToParentChannel() | Required in pinToParentChannel() | Description |
|---|---|---|---|
messageType: ThreadMessageImplDefault: n/a | Yes | No | ThreadMessageImpl object you want to pin to the selected parent channel. |
Output
| Parameter | Description |
|---|---|
ChannelImpl | Object returning the channel metadata updated with these custom fields: pinnedMessageTimetoken to mark the timetoken when the message was pinned pinnedMessageChannelID to mark the channel on which the message was pinned to the parent channel (pinning was performed either directly on the parent channel or on a thread channel). |
Sample code
Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
A thread was created for the last message in the support parent channel. Pin the last message from this thread to the parent channel.
-
pinMessageToParentChannel()
show all 23 lines1// Assuming you have a reference of type "ChatImpl" named "chat"
2Task {
3 if let channel = try await chat.getChannel(channelId: "support") {
4 // Get the last message on the channel, which is the root message for the thread
5 if let message = try await channel.getHistory(count: 1).messages.first {
6 // Get the thread channel
7 let threadChannel = try await message.getThread()
8 // Get the last message on the thread channel
9 if let threadMessage = try await threadChannel.getHistory(count: 1).messages.first {
10 // Pin the message to the parent channel
11 let updatedChannel = try await threadChannel.pinMessageToParentChannel(message: threadMessage)
12 debugPrint("Message pinned successfully to the parent channel")
13 debugPrint("Updated channel object: \(updatedChannel)")
14 } else {
15 debugPrint("No messages found in the thread channel") -
pinToParentChannel()
show all 23 lines1// Assuming you have a reference of type "ChatImpl" named "chat"
2Task {
3 if let channel = try await chat.getChannel(channelId: "support") {
4 // Get the last message on the channel, which is the root message for the thread
5 if let message = try await channel.getHistory(count: 1).messages.first {
6 // Get the thread channel
7 let threadChannel = try await message.getThread()
8 // Get the last message on the thread channel
9 if let threadMessage = try await threadChannel.getHistory(count: 1).messages.first {
10 // Pin the message to the parent channel
11 let updatedChannel = try await threadMessage.pinToParentChannel()
12 debugPrint("Message pinned successfully to the parent channel")
13 debugPrint("Updated channel object: \(updatedChannel)")
14 } else {
15 debugPrint("No messages found in the thread channel")
Unpin thread message from thread channel
unpinMessage() called on the ThreadChannel object unpins the previously pinned thread message from the thread channel.
Method signature
This method has the following signature:
1threadChannel.unpinMessage() async throws -> ThreadChannelImpl
Input
This method doesn't take any parameters.
Output
| Parameter | Description |
|---|---|
ThreadChannelImpl | Object returning the thread channel metadata updated with these custom fields: pinnedMessageTimetoken to mark the timetoken when the message was unpinned pinnedMessageChannelID to mark the channel on which the message was unpinned from the thread channel (unpinning was performed either directly on the parent channel or on a thread channel). |
Sample code
Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
Unpin the thread message from the thread (channel) created for the last message on the support channel.
1 // Assuming you have a reference of type "ChatImpl" named "chat"
2 Task {
3 if let channel = try await chat.getChannel(channelId: "support") {
4 // Get the last message on the channel, which is the root message for the thread
5 if let message = try await channel.getHistory(count: 1).messages.first {
6 // Get the thread channel
7 let threadChannel = try await message.getThread()
8 // Get the last message on the thread channel
9 if let threadMessage = try await threadChannel.getHistory(count: 1).messages.first {
10 let updatedChannel = try await threadChannel.unpinMessage()
11 debugPrint("Message unpinned successfully from the thread channel")
12 debugPrint("Updated channel object: \(updatedChannel)")
13 } else {
14 debugPrint("No messages found in the thread channel.")
15 }
show all 22 linesUnpin thread message from parent channel
You can unpin the previously pinned thread message from the parent channel with unpinMessageFromParentChannel() and unpinFromParentChannel().
Method signature
These methods have the following signatures:
-
unpinMessageFromParentChannel()(on theThreadChannelobject)1threadChannel.unpinMessageFromParentChannel() -> ChannelImpl -
unpinFromParentChannel()(on theThreadMessageobject)1threadMessage.unpinFromParentChannel() -> ChannelImpl
Input
These methods don't take any parameters.
Output
| Parameter | Description |
|---|---|
ChannelImpl | Object returning the channel metadata updated with these custom fields: pinnedMessageTimetoken to mark the timetoken when the message was unpinned pinnedMessageChannelID to mark the channel on which the message was unpinned from the parent channel (unpinning was performed either directly on the parent channel or on a thread channel). |
Sample code
Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
Unpin the thread message from the support parent channel.
-
unpinMessageFromParentChannel()
show all 22 lines1// Assuming you have a reference of type "ChatImpl" named "chat"
2Task {
3 if let channel = try await chat.getChannel(channelId: "support") {
4 // Get the last message on the channel, which is the root message for the thread
5 if let message = try await channel.getHistory(count: 1).messages.first {
6 // Get the thread channel
7 let threadChannel = try await message.getThread()
8 // Get the last message on the thread channel
9 if let threadMessage = try await threadChannel.getHistory(count: 1).messages.first {
10 let updatedChannel = try await threadChannel.unpinMessageFromParentChannel()
11 debugPrint("Message unpinned successfully from the parent channel")
12 debugPrint("Updated channel object: \(updatedChannel)")
13 } else {
14 debugPrint("No messages found in the thread channel")
15 } -
unpinFromParentChannel()
show all 21 lines1Task {
2 if let channel = try await chat.getChannel(channelId: "support") {
3 // Get the last message on the channel, which is the root message for the thread
4 if let message = try await channel.getHistory(count: 1).messages.first {
5 // Get the thread channel
6 let threadChannel = try await message.getThread()
7 // Get the last message on the thread channel
8 if let threadMessage = try await threadChannel.getHistory(count: 1).messages.first {
9 let updatedChannel = try await threadMessage.unpinFromParentChannel()
10 debugPrint("Message unpinned successfully from the parent channel")
11 debugPrint("Updated channel object: \(updatedChannel)")
12 } else {
13 debugPrint("No messages found in the thread channel")
14 }
15 } else {