---
source_url: https://www.pubnub.com/docs/chat/kotlin-chat-sdk/migrate/kotlin-chat-v10-migration-guide
title: Kotlin Chat SDK 1.0.0 migration guide
updated_at: 2026-07-01T13:56:10.325Z
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Kotlin Chat SDK 1.0.0 migration guide

The 1.0.0 release of the Kotlin Chat SDK introduces a set of API improvements that make real-time subscriptions more discoverable, event payloads strongly typed, and membership management more expressive. The core change is the shift from generic listener methods to entity-owned callbacks: instead of calling `chat.listenForEvents()` or passing a callback to `channel.connect()`, you now attach callbacks directly to entity objects (`channel.onMessageReceived`, `user.onMentioned`, etc.).

This guide is for developers who use Kotlin Chat SDK `0.x.x` in existing applications.

If your application uses `0.x.x`, it will not compile against `1.0.0` without changes to `join()`, `Message.reactions`, and `GetCurrentUserMentionsResult`. This guide summarizes the differences between the versions and shows how to migrate to Kotlin Chat SDK `1.0.0`.

Most notable changes include:

* **join() signature change**: The callback parameter is removed and `join()` no longer subscribes to messages automatically. Call `onMessageReceived()` separately after joining to start message delivery.
* **Entity-first callbacks**: All streaming methods gain entity-owned alternatives. `connect()` → `onMessageReceived()`, `getTyping()` → `onTypingChanged()`, `streamPresence()` → `onPresenceChanged()`, `streamUpdates()` → `onUpdated()` + `onDeleted()`. The old methods are deprecated but still compile.
* **Typed event payloads**: Events that previously required manual `EventContent` parsing now deliver purpose-built types. `onMentioned()` delivers `Mention`, `onInvited()` delivers `Invite`, `onRestrictionChanged()` delivers `Restriction`, and `onReadReceiptReceived()` delivers `ReadReceipt`.
* **Message.reactions type change**: `Message.reactions` changed from `Map<String, List<Action>>` to `List<MessageReaction>`. Each `MessageReaction` exposes `value`, `count`, `isMine`, and `userIds`.
* **GetCurrentUserMentionsResult.mentions**: The `enhancedMentionsData` field is deprecated (still available) and a new `mentions` field is added. Its element type changes from the union of `UserMentionData`, `ChannelMentionData`, and `ThreadMentionData` to a unified `UserMention` type with fields `message` (a `Message` object), `userId`, `channelId`, and `parentChannelId?`.
* **New read receipt capabilities**: `channel.fetchReadReceipts()` returns a paginated snapshot of read positions. A new `emitReadReceiptEvents` configuration option controls which channel types emit read receipt signals.
* **sendText() simplified**: A new `sendText(text, params: SendTextParams)` overload consolidates the previous multi-parameter signature. The old overload is deprecated.
* **New membership methods**: `channel.hasMember()`, `channel.getMember()`, `user.isMemberOf()`, and `user.getMembership()` let you check and retrieve a specific membership without fetching the full list.

## Differences between 0.x.x and 1.0.0

| Feature/Method | `0.x.x` | `1.0.0` |
| --- | --- | --- |
| `join()` return type | `PNFuture<JoinResult>` (includes disconnect) | `PNFuture<Membership>` |
| `join()` signature | `join(custom, callback)` | `join(status?, type?, custom?)` |
| `JoinResult` | Available | Removed |
| Message delivery after `join()` | Automatic (via callback) | Requires explicit `onMessageReceived()` call |
| `connect()` | Primary method for message delivery | Deprecated — use `channel.onMessageReceived()` |
| `getTyping()` | Typing indicator streaming | Deprecated — use `channel.onTypingChanged()` |
| `streamPresence()` | Presence streaming | Deprecated — use `channel.onPresenceChanged()` |
| `streamUpdates()` on entity | Update streaming (single entity) | Deprecated — use `onUpdated()` |
| `streamUpdatesOn()` on entity | Update streaming (multiple entities) | Still supported |
| Deletion events | Part of `streamUpdates()` callback | Separate `onDeleted()` event |
| `streamReadReceipts()` | `Map<Long, List<String>>` (timetoken → user IDs) | Deprecated — use `channel.onReadReceiptReceived()` with typed `ReadReceipt` |
| `streamMessageReports()` | Raw event callback | Deprecated — use `channel.onMessageReported()` |
| `Message.reactions` type | `Map<String, List<Action>>` | `List<MessageReaction>` |
| `Message.actions` | Available | Deprecated |
| Mention events | `chat.listenForEvents<EventContent.Mention>()` | `user.onMentioned(Mention)` |
| Invite events | `chat.listenForEvents<EventContent.Invite>()` | `user.onInvited(Invite)` |
| Moderation events | `chat.listenForEvents<EventContent.Moderation>()` | `user.onRestrictionChanged(Restriction)` |
| Custom events (emit) | `chat.emitEvent()` | Deprecated — use `channel.emitCustomEvent()` |
| Custom events (listen) | `chat.listenForEvents<EventContent.Custom>()` | Deprecated — use `channel.onCustomEvent()` |
| `EventContent.*` types | Available | Deprecated |
| `sendText()` options | Multi-parameter overload (with `quotedMessage`, `mentionedUsers`, `files`) | New `sendText(text, params: SendTextParams)` overload for publishing options (`meta`, `shouldStore`, `usePost`, `ttl`, `customPushData`); old overload deprecated |
| Read receipts snapshot | Not available | `channel.fetchReadReceipts()` → `ReadReceiptsResponse` |
| `emitReadReceiptEvents` config | Not available | Available in `ChatConfiguration` |
| `GetCurrentUserMentionsResult` field | `enhancedMentionsData: List<...>` | `mentions: List<UserMention>` |
| Membership existence check | `getMembers()` then filter | `channel.hasMember(userId)`, `channel.getMember(userId)` |
| User membership check | `getMemberships()` then filter | `user.isMemberOf(channelId)`, `user.getMembership(channelId)` |
| `removeThread()` return | `PNFuture<Pair<ThreadChannel, Message>>` | `PNFuture<Unit>` |
| `Channel.delete()` / `User.delete()` | Accepts `soft` parameter | `soft` parameter removed; returns `PNFuture<Unit>` |
| `Membership.update()` signature | `update(custom)` | `update(status?, type?, custom?)` |

## join() and onMessageReceived()

:::warning Important change
This change affects any code that calls `join()` and then listens for messages. Update it before releasing to users.
:::

In `0.x.x`, calling `join()` on a channel both created the membership server-side and subscribed to message delivery automatically — messages arrived via the callback you passed to `join()`. In `1.0.0`, `join()` creates the membership only. Call `onMessageReceived()` separately to start receiving messages.

The `JoinResult` type is removed. `join()` now returns `PNFuture<Membership>` directly.

###### 0.x.x

```kotlin
channel.join(custom = mapOf("role" to "member")) { message ->
    println("New message: ${message.text}")
}.async { result ->
    result.onSuccess { joinResult ->
        val membership = joinResult.membership
        val disconnect = joinResult.disconnect
    }.onFailure { exception ->
        println("Join failed: ${exception.message}")
    }
}
```

###### 1.0.0

```kotlin
// join() creates the membership — messages are not delivered automatically
channel.join(
    status = "active",
    custom = mapOf("role" to "member")
).async { result ->
    result.onSuccess { membership ->
        // Subscribe to messages separately
        val disconnect: AutoCloseable = channel.onMessageReceived { message ->
            println("New message: ${message.text}")
        }
    }.onFailure { exception ->
        println("Join failed: ${exception.message}")
    }
}

// Leave:
val disconnect: AutoCloseable = channel.onMessageReceived { }
disconnect.close()
channel.leave().async { result -> }
```

## Entity-first callbacks

In `0.x.x`, streaming subscriptions were created by calling methods on the `Chat` or `Channel` object and passing a callback as a parameter. In `1.0.0`, entity objects expose dedicated callback properties. Attach a lambda directly to the entity; the method returns an `AutoCloseable` that you call `.close()` on to stop the subscription.

The old instance methods (`connect()`, `getTyping()`, `streamPresence()`, `streamUpdates()`) are deprecated but still compile. The companion `streamUpdatesOn()` helper remains supported. Migrate to the new names at your own pace.

### 0.x.x

```kotlin
// Channel — message delivery
val stopMessages: AutoCloseable = channel.connect { message ->
    println("Message: ${message.text}")
}

// Channel — typing indicator
val stopTyping: AutoCloseable = channel.getTyping { typingUserIds ->
    println("Typing: $typingUserIds")
}

// Channel — presence
val stopPresence: AutoCloseable = channel.streamPresence { presence ->
    println("Present: ${presence.occupants}")
}

// Channel — metadata updates
val stopUpdates: AutoCloseable = channel.streamUpdates { updatedChannel ->
    println("Channel updated: ${updatedChannel?.id}")
}

// User — metadata updates
val stopUserUpdates: AutoCloseable = user.streamUpdates { updatedUser ->
    println("User updated: ${updatedUser?.id}")
}

// Membership — updates
val stopMembershipUpdates: AutoCloseable = membership.streamUpdates { updated ->
    println("Membership updated")
}
```

### 1.0.0

```kotlin
// Channel — message delivery
val stopMessages: AutoCloseable = channel.onMessageReceived { message ->
    println("Message: ${message.text}")
}

// Channel — typing indicator
val stopTyping: AutoCloseable = channel.onTypingChanged { typingUserIds ->
    println("Typing: $typingUserIds")
}

// Channel — presence
val stopPresence: AutoCloseable = channel.onPresenceChanged { userIds ->
    println("Present: $userIds")
}

// Channel — metadata updates (update and deletion are now separate)
val stopUpdated: AutoCloseable = channel.onUpdated { updatedChannel ->
    println("Channel updated: ${updatedChannel.id}")
}
val stopDeleted: AutoCloseable = channel.onDeleted {
    println("Channel was deleted")
}

// User — metadata updates
val stopUserUpdated: AutoCloseable = user.onUpdated { updatedUser ->
    println("User updated: ${updatedUser.id}")
}
val stopUserDeleted: AutoCloseable = user.onDeleted {
    println("User was deleted")
}

// Membership — updates
val stopMembershipUpdated: AutoCloseable = membership.onUpdated { updated ->
    println("Membership updated")
}
val stopMembershipDeleted: AutoCloseable = membership.onDeleted {
    println("Membership was deleted")
}

// All return AutoCloseable — call .close() to unsubscribe
stopMessages.close()
```

The same pattern applies to `Message.onUpdated()` for individual message update subscriptions.

## Typed event payloads

In `0.x.x`, invite, mention, and moderation events required calling `chat.listenForEvents()` with a generic `EventContent` type parameter and reading fields from the raw payload. In `1.0.0`, these events are exposed as dedicated entity callbacks with purpose-built types. The `chat.listenForEvents()` method and all `EventContent.*` subtypes are deprecated.

### 0.x.x

```kotlin
// Mention events — raw ChatEvent with EventContent.Mention payload
val stopMentions: AutoCloseable = chat.listenForEvents<EventContent.Mention>(
    channelId = currentUser.id
) { event ->
    val payload = event.payload as? EventContent.Mention
    println("Mentioned in: ${payload?.channelId}")
}

// Invite events — raw ChatEvent with EventContent.Invite payload
val stopInvites: AutoCloseable = chat.listenForEvents<EventContent.Invite>(
    channelId = currentUser.id
) { event ->
    val payload = event.payload as? EventContent.Invite
    println("Invited to: ${payload?.channelId} by ${payload?.invitedByUserId}")
}

// Moderation events — raw ChatEvent with EventContent.Moderation payload
val stopModeration: AutoCloseable = chat.listenForEvents<EventContent.Moderation>(
    channelId = "PUBNUB_INTERNAL_MODERATION.${currentUser.id}"
) { event ->
    val payload = event.payload as? EventContent.Moderation
    println("Restricted on: ${payload?.channelId}")
}

// Message report events — raw ChatEvent with EventContent.Report payload
val stopReports: AutoCloseable = channel.streamMessageReports { event ->
    val payload = event.payload as? EventContent.Report
    println("Reported: ${payload?.reason}")
}
```

### 1.0.0

```kotlin
// Mention events — typed Mention struct
val stopMentions: AutoCloseable = user.onMentioned { mention ->
    println("Mentioned in: ${mention.channelId}")
    println("By: ${mention.mentionedByUserId}")
    println("Message timetoken: ${mention.messageTimetoken}")
}

// Invite events — typed Invite struct
val stopInvites: AutoCloseable = user.onInvited { invite ->
    println("Invited to: ${invite.channelId} (${invite.channelType})")
    println("By: ${invite.invitedByUserId} at ${invite.invitationTimetoken}")
}

// Moderation events — typed Restriction struct
val stopModeration: AutoCloseable = user.onRestrictionChanged { restriction ->
    println("Channel: ${restriction.channelId}")
    println("Ban: ${restriction.ban}, Mute: ${restriction.mute}")
    println("Reason: ${restriction.reason}")
}

// Message report events — typed Report struct
val stopReports: AutoCloseable = channel.onMessageReported { report ->
    println("Reported by: ${report.reportedUserId}, reason: ${report.reason}")
}
```

## Read receipts

The read receipts API gains two new capabilities in `1.0.0`: a typed real-time callback (`onReadReceiptReceived()`) and an on-demand snapshot (`fetchReadReceipts()`). The old `streamReadReceipts()` method is deprecated.

In `0.x.x`, `streamReadReceipts()` delivered a `Map<Long, List<String>>` on every event, where keys were timetokens and values were lists of user IDs who had read up to that point.

In `1.0.0`, `onReadReceiptReceived()` fires once per incoming receipt signal and delivers a single `ReadReceipt` value containing the user ID and their last-read timetoken. `fetchReadReceipts()` provides a paginated snapshot of the current read state for all channel members.

### 0.x.x

```kotlin
val stop: AutoCloseable = channel.streamReadReceipts { receipts ->
    // receipts: Map<Long, List<String>> — timetoken to user IDs
    receipts.forEach { (timetoken, userIds) ->
        println("Timetoken $timetoken read by $userIds")
    }
}
```

### 1.0.0

```kotlin
// Real-time updates — one event per user receipt
val stop: AutoCloseable = channel.onReadReceiptReceived { receipt ->
    println("User ${receipt.userId} read up to ${receipt.lastReadTimetoken}")
}

// On-demand snapshot of all current read positions
channel.fetchReadReceipts().async { result ->
    result.onSuccess { response ->
        response.receipts.forEach { receipt ->
            println("${receipt.userId}: ${receipt.lastReadTimetoken}")
        }
    }
}
```

### Controlling which channel types emit read receipts

A new `emitReadReceiptEvents` parameter in `ChatConfiguration` controls which channel types produce read receipt signals. By default, `public` channels do not emit read receipts, which is appropriate for large-scale deployments.

```kotlin
val config = ChatConfiguration(
    // Default: public = false, group = true, direct = true
    // To restore the old behavior (always emit), set all to true:
    emitReadReceiptEvents = EmitReadReceiptEvents(
        public = true,
        group = true,
        direct = true
    )
)
```

:::warning Behavioral change
In `1.0.0`, `setLastReadMessage()`, `setLastReadMessageTimetoken()`, and `markAllMessagesAsRead()` now emit `receipt` events by default on `direct` and `group` channels. If your app previously relied on these methods not emitting events, you may see unexpected receipt signals and additional network traffic after upgrading. Set `emitReadReceiptEvents` to `EmitReadReceiptEvents(direct = false, group = false)` or the equivalent `Map<ChannelType, Boolean>` to restore the old behavior.
:::

## Message reactions

:::warning Important change
This change affects any code that reads `Message.reactions` or `Message.actions`. Update it before releasing to users.
:::

In `0.x.x`, `Message.reactions` returned a `Map<String, List<Action>>`. Grouping reactions by value, counting them, or checking whether the current user had reacted required manual processing of the raw action list.

In `1.0.0`, `Message.reactions` returns `List<MessageReaction>`. Each `MessageReaction` aggregates reactions of the same value and exposes `count` (how many users reacted), `isMine` (whether the current user is among them), and `userIds` (list of reacting user IDs). The raw `Message.actions` property is deprecated.

###### 0.x.x

```kotlin
// Map<String, List<Action>> — manual grouping required
val rawReactions: Map<String, List<Action>> = message.actions
rawReactions.forEach { (type, actions) ->
    println("$type: ${actions.size} reaction(s)")
    val isMine = actions.any { it.uuid == chat.currentUser.id }
    println("Mine: $isMine")
}
```

###### 1.0.0

```kotlin
// List<MessageReaction> — aggregated by reaction value
val reactions: List<MessageReaction> = message.reactions
reactions.forEach { reaction ->
    println("${reaction.value}: ${reaction.count} reaction(s), mine: ${reaction.isMine}")
    println("Reacted by: ${reaction.userIds}")
}
```

## Sending messages in 1.0.0

`1.0.0` separates message sending into two distinct paths: a lightweight API for plain text and a composition API for rich content.

| Use case | API |
| --- | --- |
| Send plain text with delivery options (`meta`, `ttl`, `shouldStore`, push options) | `channel.sendText(text, params: SendTextParams)` |
| Build messages with mentions, channel references, links, files, or quoted messages | `channel.createMessageDraft()` → `draft.send()` |
| Existing multi-parameter `sendText()` calls with `quotedMessage`, `mentionedUsers`, `files` | Still compiles — deprecated; migrate to `MessageDraft` for new integrations |

`SendTextParams` covers lightweight delivery options only: `meta`, `shouldStore`, `usePost`, `ttl`, and `customPushData`. It does not accept rich-content parameters.

For rich composition, prefer `channel.createMessageDraft()` for new integrations. The old multi-parameter `sendText()` overload remains available for compatibility but is deprecated and should not be the recommended path in new code.

### Simple send

```kotlin
channel.sendText(
    text = "Hi everyone!",
    params = SendTextParams(
        meta = mapOf("priority" to "high"),
        shouldStore = true,
        ttl = 15
    )
).async { result ->
    result.onSuccess { message ->
        println("Sent: ${message.timetoken}")
    }
}
```

### Rich send with MessageDraft

```kotlin
chat.getChannel("support").async { channelResult ->
    channelResult.onSuccess { channel ->
        if (channel != null) {
            val draft = channel.createMessageDraft(
                isTypingIndicatorTriggered = true,
                userLimit = 10,
                channelLimit = 10
            )
            val listener = MessageDraftChangeListener { elements, suggestedMentionsFuture ->
                renderComposer(elements)
                suggestedMentionsFuture.async { suggestionsResult ->
                    suggestionsResult.onSuccess { suggestions ->
                        renderSuggestions(suggestions)
                    }
                }
            }
            draft.addChangeListener(listener)
            draft.update("Hey Alex, please check #offtopic and this link")
            draft.addMention(4, 4, MentionTarget.User("alex_d"))
            draft.addMention(23, 9, MentionTarget.Channel("offtopic"))
            draft.send(
                SendTextParams(
                    meta = mapOf("source" to "composer"),
                    shouldStore = true,
                    ttl = 24
                )
            ).async { result -> }
        }
    }
}
```

### Migrating from 0.x.x

#### 0.x.x

```kotlin
channel.sendText(
    text = "See you there!",
    quotedMessage = someMessage,
    mentionedUsers = mapOf(8 to mentionedUser),
    files = listOf(myFile)
).async { result ->
    result.onSuccess { message ->
        println("Sent: ${message.timetoken}")
    }
}
```

#### 1.0.0

```kotlin
// Plain text with delivery options
val params = SendTextParams(
    meta = mapOf("priority" to "high"),
    shouldStore = true,
    ttl = 24
)
channel.sendText(text = "See you there!", params = params).async { result ->
    result.onSuccess { message ->
        println("Sent: ${message.timetoken}")
    }
}

// Rich content — use MessageDraft
// The old multi-parameter sendText() overload is deprecated; prefer createMessageDraft() for new integrations
```

## removeThread() return type

In `0.x.x`, `removeThread()` returned `PNFuture<Pair<ThreadChannel, Message>>` containing the removed thread channel and the updated parent message. In `1.0.0`, it returns `PNFuture<Unit>` — the operation completes without returning data.

### 0.x.x

```kotlin
message.removeThread().async { result ->
    result.onSuccess { (threadChannel, parentMessage) ->
        println("Thread removed from: ${threadChannel.id}")
        println("Parent hasThread: ${parentMessage.hasThread}")
    }
}
```

### 1.0.0

```kotlin
message.removeThread().async { result ->
    result.onSuccess {
        println("Thread removed successfully.")
    }
}
```

## Soft-delete removed from entity deletion

In `0.x.x`, `Channel.delete()`, `ThreadChannel.delete()`, `User.delete()`, `Chat.deleteChannel()`, and `Chat.deleteUser()` accepted a `soft` parameter for soft-deletion. In `1.0.0`, the `soft` parameter is removed — all deletions are permanent. Return types are simplified to `PNFuture<Unit>`.

### 0.x.x

```kotlin
channel.delete(soft = true).async { result ->
    result.onSuccess { deletedChannel ->
        println("Channel soft-deleted: ${deletedChannel.id}")
    }
}
```

### 1.0.0

```kotlin
channel.delete().async { result ->
    result.onSuccess {
        println("Channel deleted.")
    }
}
```

:::note Message soft-delete
You can still soft-delete messages by calling `Message.delete(soft)`.
:::

## Membership.update() signature expanded

In `0.x.x`, `Membership.update()` accepted only `custom`. In `1.0.0`, it also accepts `status` and `type`.

### 0.x.x

```kotlin
membership.update(custom = mapOf("role" to "admin")).async { result -> }
```

### 1.0.0

```kotlin
membership.update(
    status = "active",
    type = "admin",
    custom = mapOf("role" to "admin")
).async { result -> }
```

## Custom events

`chat.emitEvent()` and `chat.listenForEvents()` are deprecated. Use `channel.emitCustomEvent()` to emit a custom event and `channel.onCustomEvent()` to listen for one. Pass the payload directly as a map; the old `EventContent.Custom` wrapper type is deprecated.

### 0.x.x

```kotlin
// Emit
chat.emitEvent(
    channelId = "my-channel",
    payload = EventContent.Custom(
        data = mapOf("action" to "ping"),
        method = EmitEventMethod.PUBLISH
    )
).async { result -> }

// Listen
val stop: AutoCloseable = chat.listenForEvents<EventContent.Custom>(
    channelId = "my-channel"
) { event ->
    val data = (event.payload as? EventContent.Custom)?.data
    println("Custom event: $data")
}
```

### 1.0.0

```kotlin
// Emit — pass payload directly
channel.emitCustomEvent(
    payload = mapOf("action" to "ping")
).async { result -> }

// Listen — typed callback on the channel
val stop: AutoCloseable = channel.onCustomEvent { event ->
    println("Custom event: ${event.payload}")
}
```

## GetCurrentUserMentionsResult

:::warning Important change
This change affects any code that reads `GetCurrentUserMentionsResult`. Update it before releasing to users.
:::

The `enhancedMentionsData` field on `GetCurrentUserMentionsResult` is renamed to `mentions`. Its element type changes from the union of `UserMentionData`, `ChannelMentionData`, and `ThreadMentionData` to a single `UserMention` type that covers all mention contexts.

###### 0.x.x

```kotlin
chat.getCurrentUserMentions().async { result ->
    result.onSuccess { mentionsResult ->
        mentionsResult.enhancedMentionsData.forEach { mentionData ->
            when (mentionData) {
                is UserMentionData -> println("User mention in: ${mentionData.channelId}")
                is ChannelMentionData -> println("Channel mention: ${mentionData.channelId}")
                is ThreadMentionData -> println("Thread mention in: ${mentionData.parentChannelId}")
            }
        }
    }
}
```

###### 1.0.0

```kotlin
chat.getCurrentUserMentions().async { result ->
    result.onSuccess { mentionsResult ->
        mentionsResult.mentions.forEach { userMention ->
            println("Mention in channel: ${userMention.channelId}")
            println("By: ${userMention.userId}")
            println("Message timetoken: ${userMention.message.timetoken}")
            if (userMention.parentChannelId != null) {
                println("Thread in: ${userMention.parentChannelId}")
            }
        }
    }
}
```

## New capabilities in 1.0.0

The following features are new in `1.0.0` and have no counterpart in `0.x.x`.

| Feature | Description |
| --- | --- |
| **channel.fetchReadReceipts()** | Returns a paginated `ReadReceiptsResponse` with the last-read timetoken for each channel member. Use for initial load or on-demand checks. |
| **channel.hasMember(userId)** | Returns `PNFuture<Boolean>` — checks whether a user is a member of the channel without fetching the full membership list. |
| **channel.getMember(userId)** | Returns `PNFuture<Membership?>` — retrieves a specific member's membership object directly from the channel. |
| **user.isMemberOf(channelId)** | Returns `PNFuture<Boolean>` — checks whether the user is a member of the given channel. |
| **user.getMembership(channelId)** | Returns `PNFuture<Membership?>` — retrieves the user's membership for a specific channel. |
| **Membership.onUpdated()** | Fires when membership metadata changes. Returns `AutoCloseable`. |
| **Membership.onDeleted()** | Fires when the membership is deleted. Returns `AutoCloseable`. |
| **channel.emitCustomEvent()** | Emits a custom event directly on the channel. Replaces `chat.emitEvent()`. |
| **channel.onCustomEvent()** | Subscribes to custom events on the channel. Replaces `chat.listenForEvents<EventContent.Custom>()`. |
| **user.onMentioned()** | Subscribes to mention events for the user. Delivers typed `Mention` objects. |
| **user.onInvited()** | Subscribes to invite events for the user. Delivers typed `Invite` objects. |
| **user.onRestrictionChanged()** | Subscribes to moderation events for the user. Delivers typed `Restriction` objects. |
| **emitReadReceiptEvents config** | `ChatConfiguration.emitReadReceiptEvents` (`Map<ChannelType, Boolean>`) controls which channel types emit read receipt signals. |
| **ChannelGroup.onMessageReceived()** | Subscribes to incoming messages on all channels in the group. |
| **ChannelGroup.onPresenceChanged()** | Subscribes to presence events across all channels in the group. |

## Custom interface implementations

If your application implements or extends Chat SDK interfaces (such as `Channel`, `User`, `Message`, or `Membership`), `1.0.0` adds new required methods to these interfaces. Compilation will fail until you implement the new methods.

Key additions include entity-owned callback methods (`onUpdated()`, `onDeleted()`, `onMessageReceived()`, etc.) and new membership/presence methods (`hasMember()`, `getMember()`, `isMemberOf()`, `getMembership()`).

Review the entity documentation for each interface your code extends to identify the new required members:

* [Channel](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/learn/chat-entities/channel)
* [User](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/learn/chat-entities/user)
* [Message](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/learn/chat-entities/message)
* [Membership](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/learn/chat-entities/membership)

## Migration steps

To migrate from Kotlin Chat SDK `0.x.x` to `1.0.0`:

1. Replace every `channel.join(custom, callback)` call. Remove the callback parameter from `join()`. Add `channel.onMessageReceived { message -> }` after a successful join to restore message delivery. Update your code to use `Membership` directly from the `join()` result — `JoinResult` no longer exists.
2. Rename streaming methods on `Channel`: `connect()` → `onMessageReceived()`, `getTyping()` → `onTypingChanged()`, `streamPresence()` → `onPresenceChanged()`.
3. Replace `channel.streamUpdates()` with `channel.onUpdated()`. Add `channel.onDeleted()` wherever you previously detected deletion via the `streamUpdates()` callback. Note: `streamUpdatesOn()` is still supported and does not need to be replaced.
4. Apply the same `onUpdated()` / `onDeleted()` pattern to `User`, `Membership`, and `Message`.
5. Replace `channel.streamReadReceipts()` with `channel.onReadReceiptReceived()`. Update the handler to use `ReadReceipt` (fields: `userId`, `lastReadTimetoken`) instead of the old `Map<Long, List<String>>` type.
6. Replace `channel.streamMessageReports()` with `channel.onMessageReported()` and update the handler to use the typed `Report` struct.
7. Replace `chat.listenForEvents<EventContent.Mention>()` with `user.onMentioned()` and update the handler to use the `Mention` type.
8. Replace `chat.listenForEvents<EventContent.Invite>()` with `user.onInvited()` and update the handler to use the `Invite` type.
9. Replace `chat.listenForEvents<EventContent.Moderation>()` with `user.onRestrictionChanged()` and update the handler to use the `Restriction` type.
10. Update all code that reads `Message.reactions`. Replace `Map<String, List<Action>>` iteration with `List<MessageReaction>` iteration. Access `reaction.value`, `reaction.count`, `reaction.isMine`, and `reaction.userIds` directly. Remove any references to `Message.actions`.
11. Replace `sendText()` calls that pass only publishing options with the new `sendText(text, params: SendTextParams)` overload. Rich-content features like `quotedMessage`, `mentionedUsers`, and `files` require `MessageDraft` or the deprecated multi-parameter overload.
12. Replace `chat.emitEvent()` calls with `channel.emitCustomEvent()`. Replace `chat.listenForEvents<EventContent.Custom>()` calls with `channel.onCustomEvent()`. Remove all references to `EventContent.*` types.
13. Replace `mentionsResult.enhancedMentionsData` with `mentionsResult.mentions` in any code that calls `chat.getCurrentUserMentions()`. Update element handling to use `UserMention` (fields: `message`, `userId`, `channelId`, `parentChannelId`). Note that `message` is a `Message` object, not a timetoken. `enhancedMentionsData` is deprecated but still available.
14. Review `ChatConfiguration` initialization and add `emitReadReceiptEvents` if you need non-default read receipt emission behavior.
15. Remove the `soft` parameter from all `Channel.delete()`, `User.delete()`, `Chat.deleteChannel()`, and `Chat.deleteUser()` calls. Update return-type handling from the old result type to `Unit`.
16. Update `Membership.update()` calls to use the expanded signature (`status`, `type`, `custom`).
17. Update any code that destructures `Message.removeThread()` results — the method now returns `Unit`.
18. If you implement Chat SDK interfaces, add the new required methods to your implementations.

If you encounter issues during migration, [contact PubNub Support](https://support.pubnub.com).