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
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.
Required parameters
Required parameters don't have default values. If a parameter has a default value, it's optional.
| Parameter | Default value | Description |
|---|---|---|
channelsType: List<ChannelUi> or Flow<PagingData<ChannelUi>> | n/a | A list or a paged flow of channels. |
onSelectedType: (ChannelUi.Data) -> Unit | {} | An action called on channel tap. It's used to navigate to the discussion in a selected channel. |
onAddType: (() -> Unit)? | null | An action called after add button tap. It's used to create a new channel. Available only with groups of channels. |
onLeaveType: ((ChannelUi.Data) -> Unit)? | 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. |
useStickyHeaderType: Boolean | false | A parameter that defines if you use the sticky headers feature. |
headerContentType: @Composable (LazyItemScope) -> Unit | {} | A composable function to draw above the list. You can use it to draw a search box. |
footerContentType: @Composable (LazyItemScope) -> Unit | {} | A composable function to draw below the list. You can use it to draw a typing indicator. |
itemContentType: @Composable LazyListScope.(ChannelUi?) -> Unit | { 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.
1@Composable
2fun default(
3 resources: Resources,
4 id: UserId = LocalUser.current,
5 repository: ChannelRepository = LocalChannelRepository.current,
6 dbMapper: Mapper<DBChannelWithMembers, ChannelUi.Data> = DBChannelMapper(),
7): ChannelViewModel
| Parameter | Default value | Description |
|---|---|---|
resourcesType: Resources | n/a | Android object |
idType: UserId | LocalUser.current | Current user ID |
repositoryType: ChannelRepository<DBChannel, DBChannelWithMembers> | LocalChannelRepository.current | The ChannelRepository implementation responsible for CRUD operations (Create, Read, Update, Delete) on channel objects in the database. |
dbMapperType: 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.
1fun get(id: ChannelId): ChannelUi.Data?
| Parameter | Default value | Description |
|---|---|---|
idType: ChannelId | n/a | ID of the channel. |
Example
1get("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.
1fun getAll(filter: Query? = null, sorted: Array<Sorted> = emptyArray(), transform: PagingData<ChannelUi>.() -> PagingData<ChannelUi> = { this }): Flow<PagingData<ChannelUi>>
| Parameter | Default value | Description |
|---|---|---|
filterType: Query? | n/a | Query filter for the database. |
sortedType: Array<Sorted> | emptyArray() | Array of sorted objects. |
transformType: 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
1getAll()
2getAll(filter = Query("type LIKE ?", listOf("direct")))
3getAll(sorted = arrayOf(Sorted("type", Sorted.Direction.ASC)))
4getAll(transform = {
5 insertSeparators { after: MessageUi?, before: MessageUi? ->
6 if (
7 (before is MessageUi.Data? && after is MessageUi.Data?) &&
8 before == null && after != null ||
9 before is MessageUi.Data && after is MessageUi.Data &&
10 !before.timetoken.isSameDate(after.timetoken)
11 ) {
12 after as MessageUi.Data
13 MessageUi.Separator(after.timetoken.formatDate())
14 } else null
15 }
show all 16 linesGet 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.
1fun getList(): Map<String?, List<ChannelUi>>
Example
1getList()
Actions
This component supports the following UI interactions:
- On a channel tap -
onSelectedwill be called. - On a group add button tap -
onAddwill be called. Available only with groups of channels. - On a leave icon tap -
onLeavewill be called. Available if theonLeaveaction is notnull.
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.
1val navController = rememberNavController()
2val viewModel: ChannelViewModel = ChannelViewModel.default()
3
4ChannelList(
5 channels = viewModel.getAll(),
6 onSelected = { id ->
7 navController.navigate("channel/${id}")
8 },
9)
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
Required parameters
Required parameters don't have default values. If a parameter has a default value, it's optional.
| Parameter | Default value | Description |
|---|---|---|
membersType: List<MemberUi> or Flow<PagingData<MemberUi>> | n/a | A list or a paged flow of members. |
presenceType: Presence? | null | A Presence object which holds the online state of users. |
onSelectedType: (MemberUi.Data) -> Unit | {} | An action to be called on member tap. You can use it to navigate to the user profile. |
useStickyHeaderType: Boolean | false | A parameter that defines if you use the sticky headers feature. |
headerContentType: @Composable (LazyItemScope) -> Unit | {} | A composable function to draw above the list. You can use it for a search box. |
footerContentType: @Composable (LazyItemScope) -> Unit | {} | A composable function to draw after the list. You can use it for the Invite button. |
itemContentType: @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.
1@Composable
2fun default(
3 userId: UserId = LocalUser.current,
4 repository: MemberRepository<DBMember, DBMemberWithChannels> = LocalMemberRepository.current,
5 occupancyService: OccupancyService = LocalOccupancyService.current,
6 dbMapper: Mapper<DBMemberWithChannels, MemberUi.Data> = DBMemberMapper(),
7): MemberViewModel
| Parameter | Default value | Description |
|---|---|---|
userIdType: UserId | LocalUser.current | Current user ID |
repositoryType: MemberRepository<DBMember, DBMemberWithChannels> | LocalMemberRepository.current | MemberRepository implementation, responsible for CRUD operations (Create, Read, Update, Delete) on member objects in database. |
occupancyServiceType: OccupancyService | LocalOccupancyService.current | OccupancyService implementation, responsible for resolving occupancy for channels. |
dbMapperType: 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.
1fun getMember(id: UserId = this.userId): MemberUi.Data?
| Parameter | Default value | Description |
|---|---|---|
idType: UserId | this.userId | ID of the user. The default value is the current user ID. |
Example
1getMember("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.
1fun 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 |
|---|---|---|
idType: ChannelId | null | ID of the channel |
filterType: Query? | null | Query filter for the database |
sortedType: 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. |
transformType: 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
1getAll()
2getAll('my-channel')
3getAll(filter = Query("${MemberUi.Data::name.name} LIKE ?", "Android"))
4getAll(sorted = arrayOf(Sorted(MemberUi.Data::name.name, Sorted.Direction.DESC)))
5getAll(transform = {
6 insertSeparators { after: MessageUi?, before: MessageUi? ->
7 if (
8 (before is MessageUi.Data? && after is MessageUi.Data?) &&
9 before == null && after != null ||
10 before is MessageUi.Data && after is MessageUi.Data &&
11 !before.timetoken.isSameDate(after.timetoken)
12 ) {
13 after as MessageUi.Data
14 MessageUi.Separator(after.timetoken.formatDate())
15 } else null
show all 17 linesGet list of members
1fun 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 |
|---|---|---|
idType: ChannelId | null | ID of the channel |
Example
1getList()
2getList('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.
1fun getListGroup(id: ChannelId? = null): List<MemberUi.Data>
| Parameter | Default value | Description |
|---|---|---|
idType: ChannelId | null | ID of the channel |
Example
1getListGroup()
2getListGroup('my-channel')
Get user state
1fun 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
1getPresence()
Actions
This component supports the following UI interactions:
- On a member tap -
onSelectedwill 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.
1val navController = rememberNavController()
2val viewModel: MemberViewModel = MemberViewModel.default()
3
4MemberList(
5 members = viewModel.getAll(),
6 presence = viewModel.getPresence(),
7 onSelected = { member ->
8 navController.navigate("members/${member.id}")
9 }
10)
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:
Required parameters
Required parameters don't have default values. If a parameter has a default value, it's optional.
| Parameter | Default value | Description |
|---|---|---|
messagesType: Flow<PagingData<MessageUi>> | n/a | A paged flow of messages |
modifierType: Modifier | Modifier | A modifier object which defines the on screen position. |
presenceType: Presence? | null | A Presence object which holds a user's online state. |
onMessageSelectedType: ((MessageUi.Data) -> Unit)? | null | Option that opens up the Bottom Menu with available message actions when you long-tap a message. |
onReactionSelectedType: ((React) -> Unit)? | null | Action that is to be called on chosing a reaction. |
useStickyHeaderType: Boolean | false | A parameter that defines if you use the sticky headers feature. |
headerContentType: @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. |
footerContentType: @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. |
itemContentType: @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.
1@Composable
2fun default(
3 mediator: MessageRemoteMediator? = null,
4): MessageViewModel
| Parameter | Default value | Description |
|---|---|---|
mediatorType: 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.
1@Composable
2fun 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.
1suspend fun get(id: MessageId): MessageUi.Data?
| Parameter | Default value | Description |
|---|---|---|
idType: MessageId | n/a | ID of the message. |
Example
1get(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).
1fun getAll(
2 channelId: ChannelId,
3 filter: Query? = null,
4 contentType: String? = null,
5 sorted: Array<Sorted> = arrayOf(
6 Sorted(
7 MessageUi.Data::timetoken.name,
8 Sorted.Direction.DESC
9 )
10 ),
11 transform: PagingData<MessageUi>.() -> PagingData<MessageUi> = { this },
12): Flow<PagingData<MessageUi>>
| Parameter | Default value | Description |
|---|---|---|
channelIdType: ChannelId | n/a | ID of the channel. |
filterType: Query? | null | Query filter for the database. |
contentTypeType: String? | null | If a message contains any extra content, this field describes its type. Currently, PubNub Chat Components support only text messages. |
sortedType: Array<Sorted> | arrayOf(Sorted(MessageUi.Data::timetoken.name, Sorted.Direction.DESC)) | Array of sorted objects. |
transformType: 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
1getAll(
2 channelId = "my-channel",
3 transform = {
4 insertSeparators { after: MessageUi?, before: MessageUi? ->
5 if (
6 (before is MessageUi.Data? && after is MessageUi.Data?) &&
7 before == null && after != null ||
8 before is MessageUi.Data && after is MessageUi.Data &&
9 !before.timetoken.isSameDate(after.timetoken)
10 ) {
11 after as MessageUi.Data
12 MessageUi.Separator(after.timetoken.formatDate())
13 } else null
14 }
15})
Remove all messages
This method removes all messages from the respository for a given channel (channelId).
1fun removeAll(channelId: ChannelId) = viewModelScope.launch { messageRepository.removeAll(channelId) }
Example
1removeAll("my-channel")
Copy message content
This method copies the content of a selected message to the clipboard.
1fun copy(content: AnnotatedString) {clipboard.setText(content)
Example
1copy(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.
1fun getPresence(): Presence?
Message Reactions
Message reactions implementation is an integral part of the 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) or short-tapping a reaction already added by someone else.
View model
ReactionViewModel contains the logic for adding and removing message reactions.
1@Composable
2fun default(): ReactionViewModel
To listen for incoming actions and synchronize the history, the bind() method is created.
1fun bind(channelId: ChannelId, types: Array<String> = arrayOf("reaction")){
2 messageReactionService?.bind(types)
3 synchronize(channelId)
4}
Additionally, the unbind() method was created to remove the listening.
1fun unbind(){
2 messageReactionService?.unbind()
3}
Actions
This feature supports the following UI interactions:
- On a message long tap -
onMessageSelectedwill be called. - On a message reaction short tap -
onReactionSelectedwill be called.
Example
-
onMessageSelectedopens up the Bottom Menu with available message actions when you long-tap a message. -
onReactionSelectedeither adds or removes a selected reaction under a message.
1@Composable
2 fun View(
3 channelId: ChannelId,
4 ) {
5 // region Content data
6 val messageViewModel: MessageViewModel = MessageViewModel.defaultWithMediator()
7 val messages = remember(channelId) { messageViewModel.getAll(channelId) }
8
9 val reactionViewModel: ReactionViewModel = ReactionViewModel.default()
10 DisposableEffect(channelId){
11 reactionViewModel.bind(channelId)
12 onDispose {
13 reactionViewModel.unbind()
14 }
15 }
show all 49 linesMessageInput
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
Required parameters
Required parameters don't have default values. If a parameter has a default value, it's optional.
| Parameter | Default value | Description |
|---|---|---|
initialTextType: String | "" | The initial text of the message input. Use it to load a draft message. |
placeholderType: String | "Type Message" | A placeholder text which is shown when the message is empty. |
typingIndicatorEnabledType: Boolean | false | If set to true, it displays the typing indicator above the input message. |
typingIndicatorContentType: @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. |
onSuccessType: (String, Timetoken) -> Unit | { message: String, timetoken: Timetoken -> } | An action to be called after receiving confirmation that the message was sent. |
onBeforeSendType: ((String) -> NetworkMessagePayload)? | null | An action to be called before sending a message. Can be used to modify the text message content. |
onErrorType: (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.
1@Composable
2fun default(
3 id: UserId = LocalUser.current,
4 messageService: MessageService<NetworkMessagePayload> = LocalMessageService.current,
5 errorHandler: ErrorHandler = LocalErrorHandler.current,
6 typingService: TypingService? = null,
7): MessageInputViewModel =
8 viewModel(
9 factory = MessageInputViewModelFactory(id, messageService, errorHandler, typingService)
10 )
| Parameter | Default value | Description |
|---|---|---|
idType: UserId | LocalUser.current | ID of the current user. The mapping between UserId and the actual user is handled by DomainTypingMapper. |
messageServiceType: MessageService<NetworkMessagePayload> | LocalMessageService.current | The MessageService implementation responsible for sending and listening for messages. |
errorHandlerType: ErrorHandler | LocalErrorHandler.current | Instance of the error handling class. |
typingServiceType: 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.
1@Composable
2fun defaultWithTypingService(
3 id: UserId = LocalUser.current,
4 messageService: MessageService<NetworkMessagePayload> = LocalMessageService.current,
5): MessageInputViewModel =
6 default(id, messageService, LocalErrorHandler.current, LocalTypingService.current)
| Parameter | Default value | Description |
|---|---|---|
idType: UserId | LocalUser.current | ID of the current user. The mapping between UserId and the actual user is handled by DomainTypingMapper. |
messageServiceType: 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 |
|---|---|---|
textType: String | n/a | Message to send. |
contentTypeType: String? | null | If a message contains any extra content, this field describes its type. Currently, PubNub Chat Components support only text messages. |
contentType: Any? | null | Content data. |
customType: Any? | null | Custom payload. |
1fun create(
2 text: String,
3 contentType: String? = null,
4 content: Any? = null,
5 custom: Any? = null,
6)
Example
1send(id = "my-channel", message = "Hello world!")
Set typing state
This method sends a typing signal to all the subscribers.
1fun setTyping(id: ChannelId, isTyping: Boolean)
| Parameter | Default value | Description |
|---|---|---|
idType: ChannelId | n/a | ID of the channel |
isTypingType: Boolean | n/a | true if user is typing, false otherwise |
Example
1setTyping(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 from the Kotlin SDK will fail). Message failure will throw an exception and store these two fields in the local database:
1val isSent: Boolean = true,
2var exception: String? = null,
You can monitor all messages for their status by filtering the DBMessage database object by the isSent field.
1messageRepository.getAll(
2…
3filter = Query("isSent LIKE ?", listOf(false)),
4…
5)
You can also use the Android network state and implement a network callback to receive notifications about any connection status changes.