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.

ParameterTypeDefault valueDescription
channelsList<ChannelUi> or Flow<PagingData<ChannelUi>>n/aA list or a paged flow of channels.
onSelected(ChannelUi.Data) -> Unit{}An action called on channel tap. It's used to navigate to the discussion in a selected channel.
onAdd(() -> Unit)?nullAn action called after add button tap. It's used to create a new channel. Available only with groups of channels.
onLeave((ChannelUi.Data) -> Unit)?nullAn 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.
useStickyHeaderBooleanfalseA parameter that defines if you use the sticky headers feature.
headerContent@Composable (LazyItemScope) -> Unit{}A composable function to draw above the list. You can use it to draw a search box.
footerContent@Composable (LazyItemScope) -> Unit{}A composable function to draw below the list. You can use it to draw a typing indicator.
itemContent@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.

@Composable
fun default(
resources: Resources,
id: UserId = LocalUser.current,
repository: ChannelRepository = LocalChannelRepository.current,
dbMapper: Mapper<DBChannelWithMembers, ChannelUi.Data> = DBChannelMapper(),
): ChannelViewModel
ParameterTypeDefault valueDescription
resourcesResourcesn/aAndroid object
idUserIdLocalUser.currentCurrent user ID
repositoryChannelRepository<DBChannel, DBChannelWithMembers>LocalChannelRepository.currentThe ChannelRepository implementation responsible for CRUD operations (Create, Read, Update, Delete) on channel objects in the database.
dbMapperMapper<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.

fun get(id: ChannelId): ChannelUi.Data?
ParameterTypeDefault valueDescription
idChannelIdn/aID of the channel.
Example
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.

fun getAll(filter: Query? = null, sorted: Array<Sorted> = emptyArray(), transform: PagingData<ChannelUi>.() -> PagingData<ChannelUi> = { this }): Flow<PagingData<ChannelUi>>
ParameterTypeDefault valueDescription
filterQuery?n/aQuery filter for the database.
sortedArray<Sorted>emptyArray()Array of sorted objects.
transformPagingData<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
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
}
show all 16 lines

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.

fun getList(): Map<String?, List<ChannelUi>>
Example
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.

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

Required parameters

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

ParameterTypeDefault valueDescription
membersList<MemberUi> or Flow<PagingData<MemberUi>>n/aA list or a paged flow of members.
presencePresence?nullA Presence object which holds the online state of users.
onSelected(MemberUi.Data) -> Unit{}An action to be called on member tap. You can use it to navigate to the user profile.
useStickyHeaderBooleanfalseA parameter that defines if you use the sticky headers feature.
headerContent@Composable (LazyItemScope) -> Unit{}A composable function to draw above the list. You can use it for a search box.
footerContent@Composable (LazyItemScope) -> Unit{}A composable function to draw after the list. You can use it for the Invite button.
itemContent@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.

@Composable
fun default(
userId: UserId = LocalUser.current,
repository: MemberRepository<DBMember, DBMemberWithChannels> = LocalMemberRepository.current,
occupancyService: OccupancyService = LocalOccupancyService.current,
dbMapper: Mapper<DBMemberWithChannels, MemberUi.Data> = DBMemberMapper(),
): MemberViewModel
ParameterTypeDefault valueDescription
userIdUserIdLocalUser.currentCurrent user ID
repositoryMemberRepository<DBMember, DBMemberWithChannels>LocalMemberRepository.currentMemberRepository implementation, responsible for CRUD operations (Create, Read, Update, Delete) on member objects in database.
occupancyServiceOccupancyServiceLocalOccupancyService.currentOccupancyService implementation, responsible for resolving occupancy for channels.
dbMapperMapper<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.

fun getMember(id: UserId = this.userId): MemberUi.Data?
ParameterTypeDefault valueDescription
idUserIdthis.userIdID of the user. The default value is the current user ID.
Example
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.

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>>
ParameterTypeDefault valueDescription
idChannelIdnullID of the channel
filterQuery?nullQuery filter for the database
sortedArray<Sorted>arrayOf(Sorted(MemberUi.Data::name.name, Sorted.Direction.ASC)Array of sorted objects. The default value is sorted ascending by a member name.
transformPagingData<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
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
show all 17 lines

Get list of members

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.

ParameterTypeDefault valueDescription
idChannelIdnullID of the channel
Example
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.

fun getListGroup(id: ChannelId? = null): List<MemberUi.Data>
ParameterTypeDefault valueDescription
idChannelIdnullID of the channel
Example
getListGroup()
getListGroup('my-channel')

Get user state

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
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.

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:

Required parameters

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

ParameterTypeDefault valueDescription
messagesFlow<PagingData<MessageUi>>n/aA paged flow of messages
modifierModifierModifierA modifier object which defines the on screen position.
presencePresence?nullA Presence object which holds a user's online state.
onMessageSelected((MessageUi.Data) -> Unit)?nullOption that opens up the Bottom Menu with available message actions when you long-tap a message.
onReactionSelected((React) -> Unit)?nullAction that is to be called on chosing a reaction.
useStickyHeaderBooleanfalseA parameter that defines if you use the sticky headers feature.
headerContent@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@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@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.

@Composable
fun default(
mediator: MessageRemoteMediator? = null,
): MessageViewModel
ParameterTypeDefault valueDescription
mediatorMessageRemoteMediator?nullMessageRemoteMediator 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.

@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.

suspend fun get(id: MessageId): MessageUi.Data?
ParameterTypeDefault valueDescription
idMessageIdn/aID of the message.
Example
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).

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>>
ParameterTypeDefault valueDescription
channelIdChannelIdn/aID of the channel.
filterQuery?nullQuery filter for the database.
contentTypeString?nullIf a message contains any extra content, this field describes its type. Currently, PubNub Chat Components support only text messages.
sortedArray<Sorted>arrayOf(Sorted(MessageUi.Data::timetoken.name, Sorted.Direction.DESC))Array of sorted objects.
transformPagingData<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
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).

fun removeAll(channelId: ChannelId) = viewModelScope.launch { messageRepository.removeAll(channelId) }
Example
removeAll("my-channel")

Copy message content

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

fun copy(content: AnnotatedString) {clipboard.setText(content)
Example
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.

fun 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.

@Composable
fun default(): ReactionViewModel

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

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

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

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.

@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()
}
}
show all 49 lines

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

Required parameters

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

ParameterTypeDefault valueDescription
initialTextString""The initial text of the message input. Use it to load a draft message.
placeholderString"Type Message"A placeholder text which is shown when the message is empty.
typingIndicatorEnabledBooleanfalseIf set to true, it displays the typing indicator above the input message.
typingIndicatorContent@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(String, Timetoken) -> Unit{ message: String, timetoken: Timetoken -> }An action to be called after receiving confirmation that the message was sent.
onBeforeSend((String) -> NetworkMessagePayload)?nullAn action to be called before sending a message. Can be used to modify the text message content.
onError(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.

@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)
)
ParameterTypeDefault valueDescription
idUserIdLocalUser.currentID of the current user. The mapping between UserId and the actual user is handled by DomainTypingMapper.
messageServiceMessageService<NetworkMessagePayload>LocalMessageService.currentThe MessageService implementation responsible for sending and listening for messages.
errorHandlerErrorHandlerLocalErrorHandler.currentInstance of the error handling class.
typingServiceTypingService?nullThe 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.

@Composable
fun defaultWithTypingService(
id: UserId = LocalUser.current,
messageService: MessageService<NetworkMessagePayload> = LocalMessageService.current,
): MessageInputViewModel =
default(id, messageService, LocalErrorHandler.current, LocalTypingService.current)
ParameterTypeDefault valueDescription
idUserIdLocalUser.currentID of the current user. The mapping between UserId and the actual user is handled by DomainTypingMapper.
messageServiceMessageService<NetworkMessagePayload>LocalMessageService.currentThe MessageService implementation responsible for sending and listening for messages.

Send messages

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

ParameterTypeDefault valueDescription
textStringn/aMessage to send.
contentTypeString?nullIf a message contains any extra content, this field describes its type. Currently, PubNub Chat Components support only text messages.
contentAny?nullContent data.
customAny?nullCustom payload.
fun create(
text: String,
contentType: String? = null,
content: Any? = null,
custom: Any? = null,
)
Example
send(id = "my-channel", message = "Hello world!")

Set typing state

This method sends a typing signal to all the subscribers.

fun setTyping(id: ChannelId, isTyping: Boolean)
ParameterTypeDefault valueDescription
idChannelIdn/aID of the channel
isTypingBooleann/atrue if user is typing, false otherwise
Example
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 from the Kotlin SDK will fail). Message failure will throw an exception and store these two fields in the local database:

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.

messageRepository.getAll(

filter = Query("isSent LIKE ?", listOf(false)),

)

You can also use the Android network state and implement a network callback to receive notifications about any connection status changes.

Last updated on