UI components
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:
Component theming
The default theme implementation provides both light and dark theme for any component. If you want to change them, there are different ways of customizing how the components look.
Override material theme
Since the component theme is based on application material theme, you can override values or create a new theme for an application. The result will be similar to PubNub's light and dark theme implementation. For more information, refer to the Android guide.
Create custom component theme
Another option of customizing the look of a component is to create a custom theme for the component. Once created, you can pass it to the {ComponentName}Theme
function, for example ChannelListTheme
, or directly to CompositionLocalProvider
.
Create custom renderer
You can also create a custom renderer that extends the corresponding component's interface and pass it via the renderer
parameter. Each component has different customization options which you can find under the Theming heading in the component's description. The renderer is responsible for drawing the component on the screen. If you need to change the structure of the view, you can provide a custom implementation of the renderer. Check the renderer interfaces or default implementations to see the available data.
Predefined theme classes
Source of data classes used for theming are included below. Use them to define custom styles for components.
class ButtonTheme(
elevation: ButtonElevation?,
shape: Shape,
border: BorderStroke?,
colors: ButtonColors,
contentPadding: PaddingValues,
text: TextTheme,
modifier: Modifier
)
class IconTheme(
icon: ImageVector,
shape: Shape,
tint: Color,
modifier: Modifier
)
class InputTheme(
shape: Shape,
colors: TextFieldColors,
)
class ShapeTheme(
shape: Shape,
tint: Color,
padding: PaddingValues,
modifier: Modifier
)
class TextTheme(
modifier: Modifier,
color: Color,
fontSize: TextUnit,
fontStyle: FontStyle?,
fontWeight: FontWeight?,
fontFamily: FontFamily?,
letterSpacing: TextUnit,
textDecoration: TextDecoration?,
textAlign: TextAlign?,
lineHeight: TextUnit,
overflow: TextOverflow,
softWrap: Boolean,
maxLines: Int,
style: TextStyle,
)
object TextThemeDefaults {
@Composable
fun title(
modifier: Modifier = Modifier,
color: Color = MaterialTheme.colors.onBackground.copy(alpha = ContentAlpha.high),
fontSize: TextUnit = 16.sp,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = FontWeight.Normal,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Ellipsis,
softWrap: Boolean = true,
maxLines: Int = 1,
style: TextStyle = LocalTextStyle.current,
) = TextTheme(
modifier,
color,
fontSize,
fontStyle,
fontWeight,
fontFamily,
letterSpacing,
textDecoration,
textAlign,
lineHeight,
overflow,
softWrap,
maxLines,
style
)
@Composable
fun subtitle(
modifier: Modifier = Modifier,
color: Color = MaterialTheme.colors.onBackground.copy(alpha = ContentAlpha.medium),
fontSize: TextUnit = 14.sp,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Ellipsis,
softWrap: Boolean = true,
maxLines: Int = 1,
style: TextStyle = LocalTextStyle.current,
) = TextTheme(
modifier,
color,
fontSize,
fontStyle,
fontWeight,
fontFamily,
letterSpacing,
textDecoration,
textAlign,
lineHeight,
overflow,
softWrap,
maxLines,
style
)
@Composable
fun header(
modifier: Modifier = Modifier
.background(MaterialTheme.colors.surface)
.padding(16.dp, 24.dp, 16.dp, 16.dp)
.fillMaxWidth(),
color: Color = MaterialTheme.colors.onBackground.copy(alpha = ContentAlpha.high),
fontSize: TextUnit = 18.sp,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = FontWeight.Normal,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Ellipsis,
softWrap: Boolean = true,
maxLines: Int = 1,
style: TextStyle = LocalTextStyle.current,
) = TextTheme(
modifier,
color,
fontSize,
fontStyle,
fontWeight,
fontFamily,
letterSpacing,
textDecoration,
textAlign,
lineHeight,
overflow,
softWrap,
maxLines,
style
)
}
The ThemeDefaults
class contains the default implementation of a component's style.
object ThemeDefaults {
@Composable
fun messageInput(
modifier: Modifier = Modifier
.fillMaxWidth(),
input: InputTheme = input(),
button: ButtonTheme = button(),
) = MessageInputTheme(modifier, input, button)
@Composable
fun typingIndicator(
modifier: Modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
icon: IconTheme = icon(tint = MaterialTheme.colors.primaryVariant.copy(alpha = ContentAlpha.medium)),
text: TextTheme = text(
fontSize = 12.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.54f)
),
) = TypingIndicatorTheme(modifier, icon, text)
@Composable
fun channelList(
modifier: Modifier = Modifier.fillMaxWidth(),
title: TextTheme = TextThemeDefaults.title(),
description: TextTheme = TextThemeDefaults.subtitle(),
image: Modifier = Modifier
.padding(16.dp, 8.dp, 16.dp, 8.dp)
.size(36.dp),
icon: IconTheme = icon(Icons.Default.Logout),
header: TextTheme = TextThemeDefaults.header(),
) = ChannelListTheme(modifier, title, description, image, icon, header)
@Composable
fun memberList(
modifier: Modifier = Modifier.fillMaxWidth(),
name: TextTheme = TextThemeDefaults.title(),
description: TextTheme = TextThemeDefaults.subtitle(),
image: Modifier = Modifier
.padding(16.dp, 8.dp, 16.dp, 8.dp)
.size(36.dp),
icon: IconTheme = icon(Icons.Default.Logout),
header: TextTheme = TextThemeDefaults.header(),
) = MemberListTheme(modifier, name, description, image, icon, header)
// region Message
@Composable
fun messageList(
modifier: Modifier = Modifier.fillMaxWidth(),
arrangement: Arrangement.Vertical = Arrangement.spacedBy(8.dp),
message: MessageTheme = message(),
messageOwn: MessageTheme = message(
title = messageTitle(MaterialTheme.colors.primary),
shape = messageBackgroundShape(color = MaterialTheme.colors.primary.copy(alpha = 0.4f))
),
separator: TextTheme = separator(),
) = MessageListTheme(modifier, arrangement, message, messageOwn, separator)
@Composable
fun message(
modifier: Modifier = Modifier
.padding(16.dp, 12.dp)
.fillMaxWidth(),
title: TextTheme = messageTitle(),
date: TextTheme = messageDate(),
text: TextTheme = messageText(),
profileImage: ProfileImageTheme = profileImage(),
shape: ShapeTheme = messageBackgroundShape(),
previewShape: ShapeTheme = linkPreviewShape(),
previewImageShape: ShapeTheme = linkPreviewImageShape(),
verticalAlignment: Alignment.Vertical = Alignment.Top,
) = MessageTheme(
modifier,
title,
date,
text,
profileImage,
shape,
previewShape,
previewImageShape,
verticalAlignment
)
@Composable
fun separator(
modifier: Modifier = Modifier.padding(16.dp, 4.dp),
color: Color = MaterialTheme.colors.primaryVariant,
fontSize: TextUnit = 14.sp,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = FontWeight.Normal,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Ellipsis,
softWrap: Boolean = true,
maxLines: Int = 1,
style: TextStyle = LocalTextStyle.current,
) = TextTheme(
modifier = modifier,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
style = style,
)
@Composable
fun profileImage(
modifier: Modifier = Modifier
.padding(0.dp, 0.dp, 12.dp, 0.dp)
.size(32.dp),
indicator: IndicatorTheme = indicator(),
) = ProfileImageTheme(modifier, indicator)
@Composable
fun indicator(
modifier: Modifier = Modifier.size(16.dp),
alignment: Alignment = Alignment.BottomEnd,
activeColor: Color = Color(0xFFb8e986),
inactiveColor: Color = Color(0xFF9b9b9b),
borderStroke: BorderStroke = BorderStroke(2.dp, Color.White),
) = IndicatorTheme(modifier, alignment, activeColor, inactiveColor, borderStroke)
@Composable
fun shape(
modifier: Modifier = Modifier,
color: Color = TextFieldDefaults.textFieldColors().backgroundColor(true).value,
padding: PaddingValues = PaddingValues(0.dp),
shape: Shape,
) = ShapeTheme(shape, color, padding, modifier)
@Composable
fun linkPreviewShape(
shape: Shape = RoundedCornerShape(0.dp, 0.dp, 8.dp, 8.dp),
color: Color = MaterialTheme.colors.surface,
padding: PaddingValues = PaddingValues(8.dp),
) = shape(shape = shape, padding = padding, color = color)
@Composable
fun linkPreviewImageShape(
modifier: Modifier = Modifier
.defaultMinSize(minHeight = 100.dp)
.fillMaxWidth(),
shape: Shape = RoundedCornerShape(0.dp, 8.dp, 0.dp, 0.dp),
color: Color = MaterialTheme.colors.surface,
padding: PaddingValues = PaddingValues(0.dp),
) = shape(modifier = modifier, shape = shape, padding = padding, color = color)
@Composable
fun messageBackgroundShape(
shape: Shape = RoundedCornerShape(0.dp),
color: Color = MaterialTheme.colors.background,
padding: PaddingValues = PaddingValues(0.dp, 6.dp, 0.dp, 0.dp),
) = shape(shape = shape, padding = padding, color = color)
@Composable
private fun messageTitle(
color: Color = MaterialTheme.colors.onBackground.copy(alpha = ContentAlpha.high),
) = text(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
color = color,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
modifier = Modifier,
)
@Composable
private fun messageDate(
color: Color = MaterialTheme.colors.onBackground.copy(alpha = ContentAlpha.medium),
) = text(
fontWeight = FontWeight.Light,
fontSize = 12.sp,
color = color,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
modifier = Modifier.paddingFromBaseline(bottom = 4.sp)
)
@Composable
private fun messageText(
color: Color = MaterialTheme.colors.onBackground.copy(alpha = ContentAlpha.high),
) = text(
fontWeight = FontWeight.Normal,
fontSize = 14.sp,
color = color,
modifier = Modifier,
)
// endregion
@Composable
fun input(
shape: Shape = MaterialTheme.shapes.medium,
colors: TextFieldColors = TextFieldDefaults.textFieldColors(
textColor = MaterialTheme.colors.contentColorFor(MaterialTheme.colors.background), // Workaround for text color not changes after theme switch
),
) = InputTheme(shape, colors)
@Composable
fun button(
elevation: ButtonElevation? = ButtonDefaults.elevation(),
shape: Shape = MaterialTheme.shapes.small,
border: BorderStroke? = null,
colors: ButtonColors = ButtonDefaults.buttonColors(),
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
text: TextTheme = text(),
modifier: Modifier = Modifier
) = ButtonTheme(elevation, shape, border, colors, contentPadding, text, modifier)
@Composable
fun icon(
icon: ImageVector? = null,
shape: Shape = CircleShape,
tint: Color = MaterialTheme.colors.onBackground.copy(alpha = ContentAlpha.medium),//LocalContentColor.current.copy(alpha = ContentAlpha.medium),
modifier: Modifier = Modifier
.size(40.dp)
.padding(8.dp),
) = IconTheme(icon, shape, tint, modifier)
@Composable
fun text(
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
style: TextStyle = LocalTextStyle.current,
) = TextTheme(
modifier,
color,
fontSize,
fontStyle,
fontWeight,
fontFamily,
letterSpacing,
textDecoration,
textAlign,
lineHeight,
overflow,
softWrap,
maxLines,
style
)
}
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.Data> or Map<String?, List<ChannelUi.Data>> or Flow<PagingData<ChannelUi>> | n/a | A paged flow of channels, list of channels, or a map of group name and channel list. |
onSelected | (ChannelUi.Data) -> Unit | { channel: ChannelUi.Data -> } | An action called on channel tap. It's used to navigate to the discussion in a selected channel. |
onAdd | (() -> Unit)? | {} | 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 . |
header | @Composable (LazyItemScope) -> Unit | { scope: LazyItemScope -> } | A composable function to draw above the list. Can be used for a search box. |
footer | @Composable (LazyItemScope) -> Unit | { scope: LazyItemScope -> } | A composable function to draw below the list. You can use it to draw a typing indicator. |
renderer | ChannelRenderer | DefaultChannelRenderer | A renderer used for the channel list and its items. |
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
Parameter | Type | Default value | Description |
---|---|---|---|
id | UserId | LocalPubNub.current.configuration.uuid | Current user ID |
repository | DefaultChannelRepository | 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 |
@Composable
fun default(
id: UserId = LocalPubNub.current.configuration.uuid,
repository: DefaultChannelRepository = LocalChannelRepository.current,
dbMapper: Mapper<DBChannelWithMembers, ChannelUi.Data> = DBChannelMapper(),
): ChannelViewModel
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. Channels are grouped by type and extra ChannelUi.Header
separator is inserted to the list based on the channel type. The result is updated every time the channel data changes in the database.
fun getAll(filter: Query? = null, sorted: Array<Sorted> = emptyArray()): 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. |
Example
getAll()
getAll(filter = Query("type LIKE ?", listOf("direct")))
getAll(sorted = arrayOf(Sorted("type", Sorted.Direction.ASC)))
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.Data>>
Example
getList()
Theming
The default implementation is available in the ThemeDefaults.kt
file:
@Composable
fun channelList(
modifier: Modifier = Modifier.fillMaxWidth(),
title: TextTheme = TextThemeDefaults.title(),
description: TextTheme = TextThemeDefaults.subtitle(),
image: Modifier = Modifier
.size(54.dp)
.padding(8.dp),
icon: IconTheme = icon(Icons.Default.Logout),
) = ChannelListTheme(modifier, title, description, image, icon)
The customization allows you to change the position on screen (modifiers), colors, text sizes, and icons. The default values are taken from Android native components implementations.
To apply the newly created theme, you can use PubNub's helper which propagates the style onto its child elements.
val customTheme = ThemeDefaults.channelList(modifier = Modifier.width(200.dp))
ChannelListTheme(customTheme) {
ChannelList(channels)
}
// the helper
@Composable
fun ChannelListTheme(
theme: ChannelListTheme,
content: @Composable() () -> Unit,
){
CompositionLocalProvider(LocalChannelListTheme provides theme) {
content()
}
}
Channel custom renderer
The default implementation is available in the DefaultChannelRenderer.kt
file. You can implement the following functions:
Function | Default value | Description |
---|---|---|
Channel(name: String, description: String, profileUrl: String, onClick: (() -> Unit)?, onLeave: (() -> Unit)?, modifier: Modifier) | n/a | A composable function to draw a channel item. |
Separator(title: String?, onClick: (() -> Unit)? = null) | n/a | A composable function to draw a separator for channel types. |
Placeholder() | n/a | A composable placeholder used during data loading. |
renderSeparator(scope: LazyListScope, title: String?, onClick: (() -> Unit)? = null) | n/a | A renderer used for sticky headers. |
Actions
This component supports the following UI interactions:
- On channel tap -
onSelected
will be called. - On group add button tap -
onAdd
will be called. Available only with groups of channels. - On 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.Data> or Map<String?, List<MemberUi.Data>> or Flow<PagingData<MemberUi>> | n/a | A paged flow of members, list of members, or a map of group name and member list. |
presence | Presence? | null | A Presence object which holds the online state of users. |
onSelected | (MemberUi.Data) -> Unit | { member: MemberUi.Data -> } | An action to be called on member tap. You can use it to navigate to the user profile. |
header | @Composable (LazyItemScope) -> Unit | { scope: LazyItemScope -> } | A composable function to draw above the list. Can be used for a search box. |
footer | @Composable (LazyItemScope) -> Unit | { scope: LazyItemScope -> } | A composable function to draw after the list. You can use it for the Invite button. |
renderer | MemberRenderer | DefaultMemberRenderer | A renderer used for the member list and its items. |
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
Parameter | Type | Default value | Description |
---|---|---|---|
pubNub | PubNub | LocalPubNub.current | PubNub instance |
repository | DefaultMemberRepository | 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 |
@Composable
fun default(
pubNub: PubNub = LocalPubNub.current,
repository: DefaultMemberRepository = LocalMemberRepository.current,
occupancyService: OccupancyService = LocalOccupancyService.current,
dbMapper: Mapper<DBMemberWithChannels, MemberUi.Data> = DBMemberMapper(),
): MemberViewModel
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 = pubNub.configuration.uuid): MemberUi.Data?
Parameter | Type | Default value | Description |
---|---|---|---|
id | UserId | pubNub.configuration.uuid | 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))): 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. |
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)))
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.
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()
Theming
The default implementation is available in the ThemeDefaults.kt
file:
@Composable
fun memberList(
modifier: Modifier = Modifier.fillMaxWidth(),
name: TextTheme = TextThemeDefaults.title(),
description: TextTheme = TextThemeDefaults.subtitle(),
image: Modifier = Modifier
.size(54.dp)
.padding(8.dp),
icon: IconTheme = icon(Icons.Default.Logout),
) = MemberListTheme(modifier, name, description, image, icon)
The customization allows you to change the position on screen (modifiers), colors, text sizes, and icons. The default values are taken from Android native components implementations.
To apply the newly created theme, you can use PubNub's helper which propagates the style onto its child elements.
val customTheme = ThemeDefaults.memberList(modifier = Modifier.width(200.dp))
MemberListTheme(customTheme) {
MemberList(members)
}
// the helper
@Composable
fun MemberListTheme(
theme: MemberListTheme,
content: @Composable() () -> Unit,
) {
CompositionLocalProvider(LocalMemberListTheme provides theme) {
content()
}
}
Member custom renderer
The default implementation is available in the DefaultMemberRenderer.kt
file. You can implement the following functions:
Function | Default value | Description |
---|---|---|
fun Member(name: String, description: String?, profileUrl: String, online: Boolean?, onClick: () -> Unit, modifier: Modifier) | n/a | A composable function to draw a member item. |
Separator(title: String?, onClick: (() -> Unit)? = null) | n/a | A composable function to draw a separator for members. |
Placeholder() | n/a | A composable placeholder used during data loading. |
renderSeparator(scope: LazyListScope, title: String?, onClick: (() -> Unit)? = null) | n/a | A renderer used for sticky headers. |
Actions
This component supports the following UI interactions:
- On 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. |
onMemberSelected | (UserId) -> Unit | { id -> } | Action to be called on member profile image tap. You can use it to navigate to the user profile. |
presence | Presence? | null | A Presence object which holds a user's online state. |
renderer | MessageRenderer | GroupMessageRenderer | A renderer used for message list items. |
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 loading data only from the database. To pull the historical messages from the network, use the secondary constructor.
@Composable
fun default(
id: ChannelId = LocalChannel.current,
mediator: MessageRemoteMediator? = null,
): MessageViewModel
Parameter | Type | Default value | Description |
---|---|---|---|
id | ChannelId | LocalChannel.current | ID of the channel |
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(id: ChannelId): MessageViewModel
Parameter | Type | Default value | Description |
---|---|---|---|
id | ChannelId | LocalChannel.current | ID of the channel |
Get paged list of messages
This method returns the paged list of MessageUi
data containing the MessageUi.Data
and MessageUi.Separator
items. Messages are separated by the ChannelUi.Separator
item, based on the date. The result is updated every time the message data changes in the database.
fun getAll(): Flow<PagingData<MessageUi>>
Example
getAll()
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?
Theming
The default implementation is available in the ThemeDefaults.kt
file:
@Composable
fun messageList(
modifier: Modifier = Modifier.fillMaxWidth(),
arrangement: Arrangement.Vertical = Arrangement.spacedBy(8.dp),
message: MessageTheme = message(),
messageOwn: MessageTheme = message(
title = messageTitle(MaterialTheme.colors.primary),
shape = messageBackgroundShape(color = MaterialTheme.colors.primary.copy(alpha = 0.4f))
),
separator: TextTheme = separator(),
) = MessageListTheme(modifier, arrangement, message, messageOwn, separator)
The customization allows you to change position on screen (modifiers), colors, text sizes, and icons. The default values are taken from Android native components implementations.
To apply the newly created theme, you can use PubNub's helper which propagates the style onto its child elements.
val customTheme = ThemeDefaults.messageList(modifier = Modifier.width(200.dp))
MessageListTheme(customTheme) {
MessageList(messages, onMemberSelected)
}
// the helper
@Composable
fun MessageListTheme(
theme: MessageListTheme,
content: @Composable() () -> Unit,
) {
CompositionLocalProvider(LocalMessageListTheme provides theme) {
content()
}
}
Message custom renderer
The default implementation is available in the GroupMessageRenderer.kt
file. You can implement the following functions:
Function | Default value | Description |
---|---|---|
Message(messageId: MessageId, currentUserId: UserId, userId: UserId, profileUrl: String, online: Boolean?, title: String, message: AnnotatedString?, attachments: List<Attachment>?, timetoken: Timetoken, navigateToProfile: (UserId) -> Unit) | n/a | A composable function to draw a message item. |
Separator(title: String?, onClick: (() -> Unit)? = null) | n/a | A composable function to draw a separator for members. |
Placeholder() | n/a | A composable placeholder used during data loading. |
Actions
This component supports the following UI interactions:
- On member profile image tap -
onMemberSelected
will be called.
Example
The following example shows to set an action. onMemberSelected
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: MessageViewModel = MessageViewModel.default()
MessageList(
messages = viewModel.getAll(),
onMemberSelected = { id ->
navController.navigate("members/${id}")
},
)
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.
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. |
typingIndicator | Boolean | false | If set to true , displays the typing indicator above the input message. |
typingIndicatorRenderer | TypingIndicatorRenderer | DefaultTypingIndicatorRenderer | A renderer used to show the typing indicator. |
onSuccess | (String, Timetoken) -> Unit | { message: String, timetoken: Timetoken -> } | An action to be called after receiving confirmation that the message was sent. |
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 to create a view model with custom TypingService
. To use predefined TypingService
, please take a look at secondary constructor.
@Composable
fun default(
messageService: MessageService<DBMessage> = LocalMessageService.current,
id: UserId = LocalPubNub.current.configuration.uuid,
typingService: TypingService? = null,
): MessageInputViewModel
Parameter | Type | Default value | Description |
---|---|---|---|
messageService | MessageService<DBMessage> | LocalMessageService.current | The MessageService implementation responsible for sending and listening for messages. |
id | UserId | LocalPubNub.current.configuration.uuid | ID of the current user |
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(
messageService: MessageService<DBMessage> = LocalMessageService.current,
id: UserId = LocalPubNub.current.configuration.uuid,
): MessageInputViewModel
Parameter | Type | Default value | Description |
---|---|---|---|
messageService | MessageService<DBMessage> | LocalMessageService.current | The MessageService implementation responsible for sending and listening for messages. |
id | UserId | LocalPubNub.current.configuration.uuid | ID of the current user |
Send messages
This method sends a message to all subscribers of the passed channel. The message is stored in the local database and its state is updated after receiving confirmation or an error.
Parameter | Type | Default value | Description |
---|---|---|---|
id | ChannelId | n/a | ID of the channel |
message | String | n/a | Message to send |
type | @NetworkMessageType String | NetworkMessage.Type.DEFAULT | Type of the message. Can be one of the NetworkMessage.Type values. |
attachments | List<NetworkMessage.Attachment>? | null | List of attachments to send |
onSuccess | (String, Timetoken) -> Unit | { message, result -> } | An action to be called after successfully sending a message. |
onError | (Exception) -> Unit | { exception -> } | An action to be called after receiving an error. |
fun send(
id: ChannelId,
message: String,
@NetworkMessageType type: String = NetworkMessage.Type.DEFAULT,
attachments: List<NetworkMessage.Attachment>? = null,
onSuccess: (String, Timetoken) -> Unit = { message, result -> },
onError: (Exception) -> Unit = { exception -> }
)
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)
Theming
The default implementation is available in the ThemeDefaults.kt
file:
@Composable
fun input(
shape: Shape = MaterialTheme.shapes.medium,
colors: TextFieldColors = TextFieldDefaults.textFieldColors(
textColor = MaterialTheme.colors.contentColorFor(MaterialTheme.colors.background),
),
) = InputTheme(shape, colors)
The customization allows you to change position on screen (modifiers), colors, text sizes, and icons. The default values are taken from Android native components implementations.
To apply the newly created theme, you can use PubNub's helper which propagates the style onto its child elements.
val customTheme = ThemeDefaults.input(shape = RoundedCornerShape(50))
MessageInputTheme(customTheme) {
MessageInput()
}
// the helper
@Composable
fun MessageInputTheme(
theme: MessageInputTheme,
content: @Composable() () -> Unit,
) {
CompositionLocalProvider(LocalMessageInputTheme provides theme) {
content()
}
}
Typing indicator custom renderer
The default implementation is available in the DefaultTypingIndicatorRenderer.kt
file. You can implement the following functions:
Function | Default value | Description |
---|---|---|
fun TypingIndicator(data: List<Typing>) | n/a | A composable function to draw the typing indicator. |
The Typing
data contains information about user, channel, typing state, and the last signal timestamp.
data class Typing(
val userId: UserId,
val channelId: ChannelId,
val isTyping: Boolean,
val timestamp: Long = System.currentTimeMillis().timetoken,
)