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 | Type | Default value | Description |
---|---|---|---|
channels | List<ChannelUi> or Flow<PagingData<ChannelUi>> | n/a | A 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)? | 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) -> 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 . |
useStickyHeader | Boolean | false | A 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
Parameter | Type | Default value | Description |
---|---|---|---|
resources | Resources | n/a | Android object |
id | UserId | LocalUser.current | Current user ID |
repository | ChannelRepository<DBChannel, DBChannelWithMembers> | LocalChannelRepository.current | The ChannelRepository implementation responsible for CRUD operations (Create, Read, Update, Delete) on channel objects in the database. |
dbMapper | 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
.
fun get(id: ChannelId): ChannelUi.Data?
Parameter | Type | Default value | Description |
---|---|---|---|
id | ChannelId | n/a | ID 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>>
Parameter | Type | Default value | Description |
---|---|---|---|
filter | Query? | n/a | Query filter for the database. |
sorted | Array<Sorted> | emptyArray() | Array of sorted objects. |
transform | 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
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 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.
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 theonLeave
action 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.
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.
Parameter | Type | Default value | Description |
---|---|---|---|
members | List<MemberUi> or Flow<PagingData<MemberUi>> | n/a | A list or a paged flow of members. |
presence | Presence? | null | A 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. |
useStickyHeader | Boolean | false | A 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
Parameter | Type | Default value | Description |
---|---|---|---|
userId | UserId | LocalUser.current | Current user ID |
repository | MemberRepository<DBMember, DBMemberWithChannels> | LocalMemberRepository.current | MemberRepository implementation, responsible for CRUD operations (Create, Read, Update, Delete) on member objects in database. |
occupancyService | OccupancyService | LocalOccupancyService.current | OccupancyService implementation, responsible for resolving occupancy for channels. |
dbMapper | 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
.
fun getMember(id: UserId = this.userId): MemberUi.Data?
Parameter | Type | Default value | Description |
---|---|---|---|
id | UserId | this.userId | ID 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>>
Parameter | Type | Default value | Description |
---|---|---|---|
id | ChannelId | null | ID of the channel |
filter | Query? | null | Query filter for the database |
sorted | 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 | 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
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 linesGet 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.
Parameter | Type | Default value | Description |
---|---|---|---|
id | ChannelId | null | ID 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>
Parameter | Type | Default value | Description |
---|---|---|---|
id | ChannelId | null | ID 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.
Parameter | Type | Default value | Description |
---|---|---|---|
messages | Flow<PagingData<MessageUi>> | n/a | A paged flow of messages |
modifier | Modifier | Modifier | A modifier object which defines the on screen position. |
presence | Presence? | null | A Presence object which holds a user's online state. |
onMessageSelected | ((MessageUi.Data) -> Unit)? | null | Option that opens up the Bottom Menu with available message actions when you long-tap a message. |
onReactionSelected | ((React) -> Unit)? | null | Action that is to be called on chosing a reaction. |
useStickyHeader | Boolean | false | A 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
Parameter | Type | Default value | Description |
---|---|---|---|
mediator | 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.
@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?
Parameter | Type | Default value | Description |
---|---|---|---|
id | MessageId | n/a | ID 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>>
Parameter | Type | Default value | Description |
---|---|---|---|
channelId | ChannelId | n/a | ID of the channel. |
filter | Query? | null | Query filter for the database. |
contentType | String? | null | If a message contains any extra content, this field describes its type. Currently, PubNub Chat Components support only text messages. |
sorted | Array<Sorted> | arrayOf(Sorted(MessageUi.Data::timetoken.name, Sorted.Direction.DESC)) | Array of sorted objects. |
transform | 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
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 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 | Type | Default value | Description |
---|---|---|---|
initialText | String | "" | The initial text of the message input. Use it to load a draft message. |
placeholder | String | "Type Message" | A placeholder text which is shown when the message is empty. |
typingIndicatorEnabled | Boolean | false | If 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)? | null | An 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)
)
Parameter | Type | Default value | Description |
---|---|---|---|
id | UserId | LocalUser.current | ID of the current user. The mapping between UserId and the actual user is handled by DomainTypingMapper . |
messageService | MessageService<NetworkMessagePayload> | LocalMessageService.current | The MessageService implementation responsible for sending and listening for messages. |
errorHandler | ErrorHandler | LocalErrorHandler.current | Instance of the error handling class. |
typingService | 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.
@Composable
fun defaultWithTypingService(
id: UserId = LocalUser.current,
messageService: MessageService<NetworkMessagePayload> = LocalMessageService.current,
): MessageInputViewModel =
default(id, messageService, LocalErrorHandler.current, LocalTypingService.current)
Parameter | Type | Default value | Description |
---|---|---|---|
id | UserId | LocalUser.current | ID of the current user. The mapping between UserId and the actual user is handled by DomainTypingMapper . |
messageService | 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 | Type | Default value | Description |
---|---|---|---|
text | String | n/a | Message to send. |
contentType | String? | null | If a message contains any extra content, this field describes its type. Currently, PubNub Chat Components support only text messages. |
content | Any? | null | Content data. |
custom | Any? | null | Custom 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)
Parameter | Type | Default value | Description |
---|---|---|---|
id | ChannelId | n/a | ID of the channel |
isTyping | Boolean | n/a | true 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.