---
source_url: https://www.pubnub.com/docs/chat/community-supported/android/ui-components
title: UI components for PubNub Chat Components for Android
updated_at: 2026-06-23T11:41:46.986Z
---

> 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


# UI components for PubNub Chat Components for Android

PubNub Chat Components for Android provide easy-to-use building blocks to create chat applications for various use cases with different functionalities and customizable looks.

Available components include:

* [ChannelList](#channellist)
* [MemberList](#memberlist)
* [MessageList](#messagelist) (with [Message Reactions UI](#message-reactions))
* [MessageInput](#messageinput)

## ChannelList

The `ChannelList` component is responsible for displaying provided channels. It can represent all channels of the application, only channels joined by the current user, or all available channels. Also, it allows you to define actions for UI interactions.

### Parameters

You can configure the `ChannelList` component using the following parameters.

:::note Required parameters
Required parameters don't have default values. If a parameter has a default value, it's optional.
:::

| Parameter | Default value | Description |
| --- | --- | --- |
| channels | List<ChannelUi> | Optional |  | n/a | A list or a paged flow of channels. |
| onSelected | (ChannelUi.Data) | Optional |  | `{}` | An action called on channel tap. It's used to navigate to the discussion in a selected channel. |
| onAdd | (() | Optional |  | `null` | An action called after add button tap. It's used to create a new channel. Available only with groups of channels. |
| onLeave | ((ChannelUi.Data) | Optional |  | `null` | An action called on channel leave icon tap. It's used to leave a channel. The **Leave** icon is visible only if the `onLeave` action is not `null`. |
| useStickyHeader | Boolean | Optional |  | `false` | A parameter that defines if you use the [sticky headers](https://developer.android.com/jetpack/compose/lists#sticky-headers) feature. |
| headerContent | @Composable | Optional |  | `{}` | A composable function to draw above the list. You can use it to draw a search box. |
| footerContent | @Composable | Optional |  | `{}` | A composable function to draw below the list. You can use it to draw a typing indicator. |
| itemContent | @Composable | Optional |  | `{ channel -> ChannelListContent(channel, useStickyHeader, onSelected, onAdd, onLeave) }` | A composable function to draw a message list item. |

### View model

The `ChannelViewModel` class is designed to store and manage UI-related data. It contains the logic for getting the list of channels from the repository. The returned object is mapped to UI data and contains only the data needed to be displayed.

You can initialize `ChannelViewModel` with default values using the `ChannelViewModel.default()` constructor.

#### Default constructor

The default constructor allows for loading data only from the database.

```kotlin
@Composable
fun default(
    resources: Resources,
    id: UserId = LocalUser.current,
    repository: ChannelRepository = LocalChannelRepository.current,
    dbMapper: Mapper<DBChannelWithMembers, ChannelUi.Data> = DBChannelMapper(),
): ChannelViewModel
```

| Parameter | Default value | Description |
| --- | --- | --- |
| `resources`Type: `Resources` | n/a | [Android object](https://developer.android.com/reference/android/content/res/Resources) |
| `id`Type: `UserId` | `LocalUser.current` | Current user ID |
| `repository`Type: `ChannelRepository<DBChannel, DBChannelWithMembers>` | `LocalChannelRepository.current` | The `ChannelRepository` implementation responsible for CRUD operations (Create, Read, Update, Delete) on channel objects in the database. |
| `dbMapper`Type: `Mapper<DBChannelWithMembers, ChannelUi.Data>` | `DBChannelMapper()` | Database object to UI object mapper |

#### Get channel by ID

This method returns the representation of `ChannelUi.Data` for the specified `channelId`. If the channel doesn't exist, this method returns `null`.

```kotlin
fun get(id: ChannelId): ChannelUi.Data?
```

| Parameter | Default value | Description |
| --- | --- | --- |
| `id`Type: `ChannelId` | n/a | ID of the channel. |

##### Example

```kotlin
get("my-channel")
```

#### Get paged list of channels

This method returns the paged list of the `ChannelUi` data, containing both the `ChannelUi.Data` and `ChannelUi.Header` items. Returned channels are grouped by type. The result is updated every time the channel data changes in the database.

```kotlin
fun getAll(filter: Query? = null, sorted: Array<Sorted> = emptyArray(), transform: PagingData<ChannelUi>.() -> PagingData<ChannelUi> = { this }): Flow<PagingData<ChannelUi>>
```

| Parameter | Default value | Description |
| --- | --- | --- |
| `filter`Type: `Query?` | n/a | Query filter for the database. |
| `sorted`Type: `Array<Sorted>` | `emptyArray()` | Array of sorted objects. |
| `transform`Type: `PagingData<ChannelUi>.() -> PagingData<ChannelUi>` | `{ this }` | Parameter allowing you to modify the flow of data paging. You can use it to filter and sort the data, or add custom separators, headers, and footers. |

##### Example

```kotlin
getAll()
getAll(filter = Query("type LIKE ?", listOf("direct")))
getAll(sorted = arrayOf(Sorted("type", Sorted.Direction.ASC)))
getAll(transform = {
    insertSeparators { after: MessageUi?, before: MessageUi? ->
        if (
            (before is MessageUi.Data? && after is MessageUi.Data?) &&
            before == null && after != null ||
            before is MessageUi.Data && after is MessageUi.Data &&
            !before.timetoken.isSameDate(after.timetoken)
        ) {
            after as MessageUi.Data
            MessageUi.Separator(after.timetoken.formatDate())
        } else null
    }
})
```

#### Get map of grouped channels

This method is similar to the get paged list of channels (`getAll()`) method. The main difference is that this method returns groups of channels with titles. Like in the previous method, the channels are grouped by type.

```kotlin
fun getList(): Map<String?, List<ChannelUi>>
```

##### Example

```kotlin
getList()
```

### Actions

This component supports the following UI interactions:

* **On a channel tap** - `onSelected` will be called.
* **On a group add button tap** - `onAdd` will be called. Available only with groups of channels.
* **On a leave icon tap** - `onLeave` will be called. Available if the `onLeave` action is not `null`.

#### Example

The following example shows how to set an action. `onSelected` navigates to the defined route with the `MessageList` component from the selected channel. For more information about navigating in Jetpack Compose, refer to the [official guide](https://developer.android.com/jetpack/compose/navigation).

```kotlin
val navController = rememberNavController()
val viewModel: ChannelViewModel = ChannelViewModel.default()

ChannelList(
  channels = viewModel.getAll(),
  onSelected = { id ->
    navController.navigate("channel/${id}")
  },
)
```

## MemberList

The `MemberList` component is responsible for displaying provided members. It can represent all the members of the application or members who joined the selected channel. Also, it allows you to define actions for UI interactions.

### Parameters

:::note Required parameters
Required parameters don't have default values. If a parameter has a default value, it's optional.
:::

| Parameter | Default value | Description |
| --- | --- | --- |
| `members`Type: `List<MemberUi>` or `Flow<PagingData<MemberUi>>` | n/a | A list or a paged flow of members. |
| `presence`Type: `Presence?` | `null` | A `Presence` object which holds the online state of users. |
| `onSelected`Type: `(MemberUi.Data) -> Unit` | `{}` | An action to be called on member tap. You can use it to navigate to the user profile. |
| `useStickyHeader`Type: `Boolean` | `false` | A parameter that defines if you use the [sticky headers](https://developer.android.com/jetpack/compose/lists#sticky-headers) feature. |
| `headerContent`Type: `@Composable (LazyItemScope) -> Unit` | `{}` | A composable function to draw above the list. You can use it for a search box. |
| `footerContent`Type: `@Composable (LazyItemScope) -> Unit` | `{}` | A composable function to draw after the list. You can use it for the **Invite** button. |
| `itemContent`Type: `@Composable LazyListScope.(MemberUi?) -> Unit` | `{ member -> MemberListContent(member, useStickyHeader, presence, onSelected) }` | A composable function to draw a message list item. |

### View model

The `MemberViewModel` class is designed to store and manage UI-related data. It contains the logic for getting the list of members from the repository and getting members' online state. The returned object is mapped to UI data and contains only the data needed to be displayed.

You can initialize the `MemberViewModel` with default values using the `MemberViewModel.default()` constructor.

#### Default constructor

The default constructor allows for loading data only from the database.

```kotlin
@Composable
fun default(
    userId: UserId = LocalUser.current,
    repository: MemberRepository<DBMember, DBMemberWithChannels> = LocalMemberRepository.current,
    occupancyService: OccupancyService = LocalOccupancyService.current,
    dbMapper: Mapper<DBMemberWithChannels, MemberUi.Data> = DBMemberMapper(),
): MemberViewModel
```

| Parameter | Default value | Description |
| --- | --- | --- |
| `userId`Type: `UserId` | `LocalUser.current` | Current user ID |
| `repository`Type: `MemberRepository<DBMember, DBMemberWithChannels>` | `LocalMemberRepository.current` | `MemberRepository` implementation, responsible for CRUD operations (Create, Read, Update, Delete) on member objects in database. |
| `occupancyService`Type: `OccupancyService` | `LocalOccupancyService.current` | `OccupancyService` implementation, responsible for resolving occupancy for channels. |
| `dbMapper`Type: `Mapper<DBMemberWithChannels, MemberUi.Data>` | `DBMemberMapper()` | Database object to UI object mapper |

#### Get member by ID

This method returns the representation of `MemberUi.Data` for the specified user `id`. If the member doesn't exist, this method returns `null`.

```kotlin
fun getMember(id: UserId = this.userId): MemberUi.Data?
```

| Parameter | Default value | Description |
| --- | --- | --- |
| `id`Type: `UserId` | `this.userId` | ID of the user. The default value is the current user ID. |

##### Example

```kotlin
getMember("my-user-id")
```

#### Get paged list of members

This method returns the paged list of `MemberUi` data containing `MemberUi.Data` items. When `id` parameter isn't set, members of all channels are returned. The result is updated every time the member data changes in the database.

```kotlin
fun getAll(id: ChannelId? = null, filter: Query? = null, sorted: Array<Sorted> = arrayOf(Sorted(MemberUi.Data::name.name, Sorted.Direction.ASC)), transform: PagingData<MemberUi>.() -> PagingData<MemberUi> = { this }): Flow<PagingData<MemberUi>>
```

| Parameter | Default value | Description |
| --- | --- | --- |
| `id`Type: `ChannelId` | `null` | ID of the channel |
| `filter`Type: `Query?` | `null` | Query filter for the database |
| `sorted`Type: `Array<Sorted>` | `arrayOf(Sorted(MemberUi.Data::name.name, Sorted.Direction.ASC)` | Array of sorted objects. The default value is sorted ascending by a member name. |
| `transform`Type: `PagingData<MemberUi>.() -> PagingData<MemberUi>` | `{ this }` | Parameter allowing you to modify the flow of data paging. You can use it to filter and sort the data, or add custom separators, headers, and footers. |

##### Example

```kotlin
getAll()
getAll('my-channel')
getAll(filter = Query("${MemberUi.Data::name.name} LIKE ?", "Android"))
getAll(sorted = arrayOf(Sorted(MemberUi.Data::name.name, Sorted.Direction.DESC)))
getAll(transform = {
    insertSeparators { after: MessageUi?, before: MessageUi? ->
        if (
            (before is MessageUi.Data? && after is MessageUi.Data?) &&
            before == null && after != null ||
            before is MessageUi.Data && after is MessageUi.Data &&
            !before.timetoken.isSameDate(after.timetoken)
        ) {
            after as MessageUi.Data
            MessageUi.Separator(after.timetoken.formatDate())
        } else null
    }
})
```

#### Get list of members

```kotlin
fun getList(id: ChannelId? = null): List<MemberUi.Data>
```

This method returns the list of `MemberUi.Data` items. When the `id` parameter isn't set, members of all channels are returned.

| Parameter | Default value | Description |
| --- | --- | --- |
| `id`Type: `ChannelId` | `null` | ID of the channel |

##### Example

```kotlin
getList()
getList('my-channel')
```

#### Get grouped list of members

This method returns the map of group name and list of `MemberUi.Data` items. Members are grouped by the first name letter. When the `id` parameter isn't set, members of all channels are returned.

```kotlin
fun getListGroup(id: ChannelId? = null): List<MemberUi.Data>
```

| Parameter | Default value | Description |
| --- | --- | --- |
| `id`Type: `ChannelId` | `null` | ID of the channel |

##### Example

```kotlin
getListGroup()
getListGroup('my-channel')
```

#### Get user state

```kotlin
fun getPresence(): Presence?
```

This method returns the `Presence` object when `presenceService` exists, or null otherwise. The `Presence` object contains a map of `Member` ID and current online/offline state.

##### Example

```kotlin
getPresence()
```

### Actions

This component supports the following UI interactions:

* **On a member tap** - `onSelected` will be called.

#### Example

The following example shows how to set an action. `onSelected` navigates to the defined route with selected user information. For more details about navigating in Jetpack Compose, refer to the [official guide](https://developer.android.com/jetpack/compose/navigation).

```kotlin
val navController = rememberNavController()
val viewModel: MemberViewModel = MemberViewModel.default()

MemberList(
  members = viewModel.getAll(),
  presence = viewModel.getPresence(),
  onSelected = { member ->
    navController.navigate("members/${member.id}")
  }
)
```

## MessageList

The `MessageList` component is responsible for displaying provided messages. It can represent all messages of the application, only messages from the provided channel, or only unread messages. Also, it allows you to define actions for UI interactions.

### Parameters

You can configure the `MessageList` component using the following parameters:

:::note Required parameters
Required parameters don't have default values. If a parameter has a default value, it's optional.
:::

| Parameter | Default value | Description |
| --- | --- | --- |
| `messages`Type: `Flow<PagingData<MessageUi>>` | n/a | A paged flow of messages |
| `modifier`Type: `Modifier` | `Modifier` | A modifier object which defines the on screen position. |
| `presence`Type: `Presence?` | `null` | A `Presence` object which holds a user's online state. |
| `onMessageSelected`Type: `((MessageUi.Data) -> Unit)?` | `null` | Option that opens up the Bottom Menu with available message actions when you long-tap a message. |
| `onReactionSelected`Type: `((React) -> Unit)?` | `null` | Action that is to be called on chosing a reaction. |
| `useStickyHeader`Type: `Boolean` | `false` | A parameter that defines if you use the [sticky headers](https://developer.android.com/jetpack/compose/lists#sticky-headers) feature. |
| `headerContent`Type: `@Composable (LazyItemScope) -> Unit` | `{}` | A composable function to draw above the list. You can use it to draw a search box or a message loading indicator. |
| `footerContent`Type: `@Composable (LazyItemScope) -> Unit` | `{}` | A composable function to draw after the list. You can use it to draw a message loading indicator or a typing indicator on the message list. |
| `itemContent`Type: `@Composable LazyListScope.(MessageUi?, UserId) -> Unit` | `{ message, currentUser -> MessageListContent(message, currentUser, GroupMessageRenderer, DefaultReactionsPickerRenderer, useStickyHeader, presence, onMessageSelected, onReactionSelected) }` | A composable function to draw a message list item. |

### View model

The `MessageViewModel` class is designed to store and manage UI-related data. It contains the logic for getting the list of messages from the repository and getting members' online state. The returned object is mapped to UI data and contains only the data needed to be displayed.

You can initialize `MessageViewModel` with default values using the `MessageViewModel.default()` constructor.

#### Default constructor

The default constructor allows for loading data only from the database. To pull the historical messages from the network, use the [secondary constructor](#secondary-constructor).

```kotlin
@Composable
fun default(
  mediator: MessageRemoteMediator? = null,
): MessageViewModel
```

| Parameter | Default value | Description |
| --- | --- | --- |
| `mediator`Type: `MessageRemoteMediator?` | `null` | `MessageRemoteMediator` implementation to pull the data from the network. |

#### Secondary constructor

The secondary constructor allows you to use a predefined `MessageRemoteMediator` implementation and load messages both from the database and network.

```kotlin
@Composable
fun defaultWithMediator(): MessageViewModel
```

#### Get message by ID

This method returns the representation of `MessageUi.Data` for the specified user `id`. If the message doesn't exist, this method returns `null`.

```kotlin
suspend fun get(id: MessageId): MessageUi.Data?
```

| Parameter | Default value | Description |
| --- | --- | --- |
| `id`Type: `MessageId` | n/a | ID of the message. |

##### Example

```kotlin
get(messageId)
```

#### Get paged list of messages

This method returns the paged list of messages containing `MessageUi.Data`. Messages are sorted by their timetokens: `sorted = arrayOf(Sorted(MessageUi.Data::timetoken.name, Sorted.Direction.DESC))`. The result is updated every time the message data changes in the database. Paging settings are passed to the message view model, with the default value of `private val config: PagingConfig = PagingConfig(pageSize = 10, enablePlaceholders = true)`.

```kotlin
fun getAll(
  channelId: ChannelId,
  filter: Query? = null,
  contentType: String? = null,
  sorted: Array<Sorted> = arrayOf(
    Sorted(
      MessageUi.Data::timetoken.name,
      Sorted.Direction.DESC
    )
  ),
  transform: PagingData<MessageUi>.() -> PagingData<MessageUi> = { this },
): Flow<PagingData<MessageUi>>
```

| Parameter | Default value | Description |
| --- | --- | --- |
| `channelId`Type: `ChannelId` | n/a | ID of the channel. |
| `filter`Type: `Query?` | `null` | Query filter for the database. |
| `contentType`Type: `String?` | `null` | If a message contains any extra content, this field describes its type. Currently, PubNub Chat Components support only text messages. |
| `sorted`Type: `Array<Sorted>` | `arrayOf(Sorted(MessageUi.Data::timetoken.name, Sorted.Direction.DESC))` | Array of sorted objects. |
| `transform`Type: `PagingData<MessageUi>.() -> PagingData<MessageUi>` | `{ this }` | Parameter allowing you to modify the flow of data paging. You can use it to filter and sort the data, or add custom separators, headers, and footers. |

##### Example

```kotlin
getAll(
  channelId = "my-channel",
  transform = {
    insertSeparators { after: MessageUi?, before: MessageUi? ->
        if (
            (before is MessageUi.Data? && after is MessageUi.Data?) &&
            before == null && after != null ||
            before is MessageUi.Data && after is MessageUi.Data &&
            !before.timetoken.isSameDate(after.timetoken)
        ) {
            after as MessageUi.Data
            MessageUi.Separator(after.timetoken.formatDate())
        } else null
    }
})
```

#### Remove all messages

This method removes all messages from the respository for a given channel (`channelId`).

```kotlin
fun removeAll(channelId: ChannelId) = viewModelScope.launch { messageRepository.removeAll(channelId) }
```

##### Example

```kotlin
removeAll("my-channel")
```

#### Copy message content

This method copies the content of a selected message to the clipboard.

```kotlin
fun copy(content: AnnotatedString) {clipboard.setText(content)
```

##### Example

```kotlin
copy(AnnotatedString("someMessage"))
```

#### Get user state

This method returns the `Presence` object when `presenceService` exists, or null otherwise. This object contains a map of `Member` ID and current online/offline state.

```kotlin
fun getPresence(): Presence?
```

### Message Reactions

[Message reactions](https://www.pubnub.com/docs/chat/community-supported/android/message-reactions) implementation is an integral part of the [MessageList](#messagelist) component. This feature allows you to react to messages in chat apps by long-tapping a message and choosing a reaction from a bottom drawer ([Bottom Menu](https://material.io/components/sheets-bottom)) or short-tapping a reaction already added by someone else.

#### View model

`ReactionViewModel` contains the logic for adding and removing message reactions.

```kotlin
@Composable
fun default(): ReactionViewModel
```

To listen for incoming actions and synchronize the history, the `bind()` method is created.

```kotlin
fun bind(channelId: ChannelId, types: Array<String> = arrayOf("reaction")){
    messageReactionService?.bind(types)
    synchronize(channelId)
}
```

Additionally, the `unbind()` method was created to remove the listening.

```kotlin
fun unbind(){
    messageReactionService?.unbind()
}
```

#### Actions

This feature supports the following UI interactions:

* **On a message long tap** - `onMessageSelected` will be called.
* **On a message reaction short tap** - `onReactionSelected` will be called.

#### Example

* onMessageSelected opens up the Bottom Menu with available message actions when you long-tap a message.
* onReactionSelected either adds or removes a selected reaction under a message.

```kotlin
@Composable
    fun View(
        channelId: ChannelId,
    ) {
      // region Content data
      val messageViewModel: MessageViewModel = MessageViewModel.defaultWithMediator()
      val messages = remember(channelId) { messageViewModel.getAll(channelId) }

      val reactionViewModel: ReactionViewModel = ReactionViewModel.default()
      DisposableEffect(channelId){
          reactionViewModel.bind(channelId)
          onDispose {
              reactionViewModel.unbind()
          }
      }
      // endregion

      var menuVisible by remember { mutableStateOf(false) }
        var selectedMessage by remember { mutableStateOf<MessageUi.Data?>(null) }

        val onDismiss: () -> Unit = { menuVisible = false}
        CompositionLocalProvider(LocalChannel provides channelId) {
            Menu(
                visible = menuVisible,
                message = selectedMessage,
                onDismiss = onDismiss,
                onAction = { action ->
                    when (action) {
                        is Copy -> {
                            messageViewModel.copy(AnnotatedString(action.message.text))
                        }
                        is React -> reactionViewModel.reactionSelected(action)
                        else -> {}
                    }
                    onDismiss()
                }
            )

            Content(
                messages = messages,
                onMessageSelected = {
                    selectedMessage = it
                    menuVisible = true
                },
                onReactionSelected = reactionViewModel::reactionSelected,
            )
        }
    }
```

## MessageInput

The `MessageInput` component is responsible for sending messages and showing the typing indicator. Also, it allows you to define actions invoked after message sending confirmation or after receiving an error.

### Parameters

:::note Required parameters
Required parameters don't have default values. If a parameter has a default value, it's optional.
:::

| Parameter | Default value | Description |
| --- | --- | --- |
| `initialText`Type: `String` | `""` | The initial text of the message input. Use it to load a draft message. |
| `placeholder`Type: `String` | `"Type Message"` | A placeholder text which is shown when the message is empty. |
| `typingIndicatorEnabled`Type: `Boolean` | `false` | If set to `true`, it displays the typing indicator above the input message. |
| `typingIndicatorContent`Type: `@Composable ColumnScope.(List<TypingUi>) -> Unit` | `{ typing -> TypingIndicatorContent(typing) }` | A default parameter for a hook which passes the information that should be displayed in the UI when typing. |
| `onSuccess`Type: `(String, Timetoken) -> Unit` | `{ message: String, timetoken: Timetoken -> }` | An action to be called after receiving confirmation that the message was sent. |
| `onBeforeSend`Type: `((String) -> NetworkMessagePayload)?` | `null` | An action to be called before sending a message. Can be used to modify the text message content. |
| `onError`Type: `(Exception) -> Unit` | `{ exception: Exception -> }` | An action to be called after receiving an error. |

### View model

The `MessageInputViewModel` class is designed to store and send messages. It contains the logic for sending messages and setting the typing state. The data is stored in the database and sent to the PubNub service. All the methods are called automatically by the `MessageInput` component.

You can initialize `MessageViewModel` with default values using the `MessageViewModel.default()` constructor.

#### Default constructor

The default constructor allows you to create a view model with custom `TypingService`. To use predefined `TypingService`, please take a look at [secondary constructor](#secondary-constructor).

```kotlin
@Composable
fun default(
    id: UserId = LocalUser.current,
    messageService: MessageService<NetworkMessagePayload> = LocalMessageService.current,
    errorHandler: ErrorHandler = LocalErrorHandler.current,
    typingService: TypingService? = null,
): MessageInputViewModel =
    viewModel(
        factory = MessageInputViewModelFactory(id, messageService, errorHandler, typingService)
    )
```

| Parameter | Default value | Description |
| --- | --- | --- |
| `id`Type: `UserId` | `LocalUser.current` | ID of the current user. The mapping between `UserId` and the actual user is handled by `DomainTypingMapper`. |
| `messageService`Type: `MessageService<NetworkMessagePayload>` | `LocalMessageService.current` | The `MessageService` implementation responsible for sending and listening for messages. |
| `errorHandler`Type: `ErrorHandler` | `LocalErrorHandler.current` | Instance of the error handling class. |
| `typingService`Type: `TypingService?` | `null` | The `TypingService` implementation responsible for collecting and sending data about users who are typing. |

#### Secondary constructor

The secondary constructor allows you to use a predefined `TypingService` implementation that displays the typing indicators.

```kotlin
@Composable
fun defaultWithTypingService(
    id: UserId = LocalUser.current,
    messageService: MessageService<NetworkMessagePayload> = LocalMessageService.current,
): MessageInputViewModel =
    default(id, messageService, LocalErrorHandler.current, LocalTypingService.current)
```

| Parameter | Default value | Description |
| --- | --- | --- |
| `id`Type: `UserId` | `LocalUser.current` | ID of the current user. The mapping between `UserId` and the actual user is handled by `DomainTypingMapper`. |
| `messageService`Type: `MessageService<NetworkMessagePayload>` | `LocalMessageService.current` | The `MessageService` implementation responsible for sending and listening for messages. |

#### Send messages

This method sends a message that is stored in the local database.

| Parameter | Default value | Description |
| --- | --- | --- |
| `text`Type: `String` | n/a | Message to send. |
| `contentType`Type: `String?` | `null` | If a message contains any extra content, this field describes its type. Currently, PubNub Chat Components support only text messages. |
| `content`Type: `Any?` | `null` | Content data. |
| `custom`Type: `Any?` | `null` | Custom payload. |

```kotlin
fun create(
    text: String,
    contentType: String? = null,
    content: Any? = null,
    custom: Any? = null,
)
```

##### Example

```kotlin
send(id = "my-channel", message = "Hello world!")
```

#### Set typing state

This method sends a typing signal to all the subscribers.

```kotlin
fun setTyping(id: ChannelId, isTyping: Boolean)
```

| Parameter | Default value | Description |
| --- | --- | --- |
| `id`Type: `ChannelId` | n/a | ID of the channel |
| `isTyping`Type: `Boolean` | n/a | `true` if user is typing, `false` otherwise |

##### Example

```kotlin
setTyping(id = "my-channel", isTyping = true)
```

### Offline message behavior

When you send a message and the network connection is lost, the message will appear in the message list upon tapping the `Send` button, but other chat users won’t see it. The message won’t reach the server (the [publish method](https://www.pubnub.com/docs/sdks/kotlin/api-reference/publish-and-subscribe#methods) from the Kotlin SDK will fail). Message failure will throw an exception and store these two fields in the local database:

```kotlin
val isSent: Boolean = true,
var exception: String? = null,
```

You can monitor all messages for their status by filtering the `DBMessage` database object by the [isSent field](https://www.pubnub.com/docs/chat/community-supported/android/data-components#default-message-entity).

```kotlin
messageRepository.getAll(
…
filter = Query("isSent LIKE ?", listOf(false)),
…
)
```

You can also use the Android [network state](https://developer.android.com/training/monitoring-device-state/connectivity-status-type) and implement a network callback to receive notifications about any connection status changes.