UI theming for PubNub Chat Components for Android
PubNub Chat Components for Android rely on the Material Theme from Jetpack Compose that defines default values for your application color, typography, and shape attributes, allowing your app to have a consistent look and feel.
App theme
Compose libraries are used across our components, their default themes, and renderers that define how the components are drawn on the screen. Chat Provider applies the default themes to all components that make references to them. This happens at the start of every app built with PubNub Chat Components for Android.
Our Getting Started (GS) app applies the default Material Theme in the Theme.kt
file:
MaterialTheme(
colors = colors,
typography = Typography,
shapes = Shapes,
) {
ChatProvider(pubNub) {
content()
}
}
Light and dark themes
Light and dark themes, same as in Jetpack Compose, are defined in components by providing different pairs of colors (primary, secondary, etc.) that are used respectively in light and dark themes of your app (the so-called LightColorPalette
and DarkColorPalette
).
This is how the GS app uses both palettes to define the app theme:
@Composable
fun AppTheme(
pubNub: PubNub,
database: DefaultDatabase = Database.initialize(LocalContext.current),
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit,
) {
val colors = if (darkTheme) DarkColorPalette
else LightColorPalette
Change light and dark palettes
To override these default color palettes in your app, simply provide different values for specific parameters in both light and dark color sets.
See overrides from the GS app in the Theme.kt
file:
private val DarkColorPalette = darkColors(
primary = Light4,
primaryVariant = Amaranth,
secondary = Light4,
onPrimary = Amaranth,
)
private val LightColorPalette = lightColors(
primary = Light4,
primaryVariant = Amaranth,
onPrimary = Amaranth,
secondary = Light4,
onSecondary = Amaranth,
onSurface = DustyGray,
onBackground = MineShaft,
Variables
Specific values for variables like Light4
or Amaranth
are defined in the Color.kt
file in the GS app.
You can use the Material palette generator to help you choose the most appropriate light and dark sets of colors for your app.
Change shapes and typography
Similarly to the colors, you can override the default values for shapes and typography provided by the Material Theme. Our GS app differentiates the following types of shapes in the Shape.kt
file:
val Shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(0.dp)
)
It also specifies the typography for long-form writing (body1) used in the GS app (Type.kt
):
val Typography = Typography(
body1 = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
)
)
Component themes
With Material Theme supporting the implementation of PubNub Chat Components for Android implementation, we defined how each of our components should look and behave. For this purpose, we created separate themes for each component and default renderers that specify how these components behave in a final app.
All these component themes are separate Android classes and have a unified naming convention of {ComponentName}Theme
, like TypingIndicatorTheme
.
Default themes and renderers
You can see all default component themes in specific component folders in the chat-components-android
repository. Each component folder contains a theme file (like TypingIndicatorTheme.kt
) and a renderer file under the renderer
folder (like TypingIndicatorRenderer.kt
with its default implementation specified in DefaultTypingIndicatorRenderer.kt
).
Each component theme has a specified list of parameters it takes. These parameters define what the component looks like and only these parameters can be customized.
Just like Chat Provider handles the default app theme, component themes are applied by PubNub local theme providers, such as LocalTypingIndicatorTheme
.
Let's analyze how these default component themes are composed based on the TypingIndicatorTheme
class.
class TypingIndicatorTheme(
modifier: Modifier,
icon: IconTheme,
text: TextTheme,
)
As you can see, the TypingIndicatorTheme
class takes the modifier
, icon
, and text
parameters. Each of them has a hierarchical structure and references a variable that is specified in one common ThemeDefaults
object in the ThemeDefaults.kt
file in the repository.
See the TypingIndicatorTheme
example and the composable function in which it is specified:
object ThemeDefaults {
...
@Composable
fun typingIndicator(
modifier: Modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
icon: IconTheme = IconThemeDefaults.icon(
tint = MaterialTheme.colors.primaryVariant.copy(
alpha = ContentAlpha.medium
)
),
text: TextTheme = TextThemeDefaults.text(
fontSize = 12.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.54f)
show all 18 linesThe typingIndicator
function takes:
-
modifier
which is a native Compose object that allows you to decorate your composable function and change its layout, behavior, and appearance by changing the given element's padding, width, or hight. Each theme for a given PubNub Chat Component for Android contains a modifier and allows you to customize the component to your needs. In this example, thetypingIndicator
function uses the Compose modifier to set the maximum width of the typing indicator and change the padding to 12 dp. -
icon
referencesIconTheme
and overrides theIconThemeDefaults
theme for an icon. Apart from default themes for components, we also provide the default themes for common objects used across multiple components, such as buttons, icons, shapes, or texts. See all of them defined in thecommon
folder. In this example, thetypingIndicator
function uses theIconThemeDefaults
theme for the icon but overrides the importance hierarchy of the Material Theme primary color for the tint to medium using the ContentAlpha object from Jetpack Compose. -
text
, similarly toicon
, references and overrides our in-house default theme. In this example, it specifies that thetypingIndicator
must useTextThemeDefaults
withfontSize
of12.sp
and Material ThemeonSurface
color at 54% opacity.
PubNub themes
All in-house PubNub themes for components and common elements have a unified naming convention and end with the Theme
suffix, like TextTheme
.
Customize component theme
Now that you know how these component and element themes work in PubNub Chat Components for Android, see how you can override the default values they provide in your app.
Customization allows you to change such parameters as the position on the screen (through modifiers), colors, text sizes, and icons. To customize the look of a component, create a custom theme for the component in which you override one or a few default parameters specified for the component in the ThemeDefaults.kt
file. You then need to pass the new custom theme to the {ComponentName}Theme
function, for example ChannelListTheme
, using the Android native helper called CompositionLocalProvider
. To propagate the new style onto all child elements, call a specific PubNub local provider for the theme (in this example it would be LocalChannelListTheme
).
You can do the same to override the default values for the default element themes and customize values for such elements as buttons, icons, or shapes. See the common
folder for the list of all available themes.
Change message list width
The ThemeDefaults
object specifies that the message list is drawn at full width through the modifier value of Modifier = Modifier.fillMaxWidth()
:
object ThemeDefaults {
...
@Composable
fun messageList(
modifier: Modifier = Modifier.fillMaxWidth(),
arrangement: Arrangement.Vertical = Arrangement.spacedBy(8.dp),
message: MessageTheme = message(),
messageOwn: MessageTheme = message(
title = TextThemeDefaults.messageTitle(MaterialTheme.colors.primary),
shape = ShapeThemeDefaults.messageBackground(
color = MaterialTheme.colors.primary.copy(
alpha = 0.4f
)
)
),
show all 18 linesTo override the default maximum width of the message list in your app, create a custom theme in which you change the modifier
value to 200.dp
:
val customTheme = ThemeDefaults.messageList(modifier = Modifier.width(200.dp))
MessageListTheme(customTheme) {
MessageList(messages)
}
Pass the new value to the MessageListTheme
composable function using the CompositionLocalProvider
helper and the LocalMessageListTheme
theme provider:
@Composable
fun MessageListTheme(
theme: MessageListTheme,
content: @Composable() () -> Unit,
) {
CompositionLocalProvider(LocalMessageListTheme provides theme) {
content()
}
}
Change input shape
The InputThemeDefaults
object specifies that the shape of the input box (shape
) uses the default medium shape provided by the Material Theme:
object InputThemeDefaults {
@Composable
fun input(
shape: Shape = MaterialTheme.shapes.medium,
colors: TextFieldColors = TextFieldDefaults.textFieldColors(
textColor = MaterialTheme.colors.contentColorFor(MaterialTheme.colors.background),
),
) = InputTheme(shape, colors)
}
Override the shape of the input box to apply 50% to all four corners:
val customTheme = ThemeDefaults.input(shape = RoundedCornerShape(50))
MessageInputTheme(customTheme) {
MessageInput()
}
Pass the new value to the helper:
@Composable
fun MessageInputTheme(
theme: MessageInputTheme,
content: @Composable() () -> Unit,
) {
CompositionLocalProvider(LocalMessageInputTheme provides theme) {
content()
}
}
Change message title color
The ThemeDefaults
object specifies that the title of your own message (messageOwn
) sent in a channel should use the default color provided by the Material Theme:
object ThemeDefaults {
...
@Composable
fun messageList(
modifier: Modifier = Modifier.fillMaxWidth(),
arrangement: Arrangement.Vertical = Arrangement.spacedBy(8.dp),
message: MessageTheme = message(),
messageOwn: MessageTheme = message(
title = TextThemeDefaults.messageTitle(MaterialTheme.colors.primary),
shape = ShapeThemeDefaults.messageBackground(
color = MaterialTheme.colors.primary.copy(
alpha = 0.4f
)
)
),
show all 17 linesChange the default title color to red:
val customTheme = ThemeDefaults.messageList(messageOwn = message(title = messageTitle(Color.Red))
)
MessageListTheme(customTheme) {
MessageList(messages)
}
Pass the new value to the helper:
@Composable
fun MessageListTheme(
theme: MessageListTheme,
content: @Composable() () -> Unit,
) {
CompositionLocalProvider(LocalMessageListTheme provides theme) {
content()
}
}
Custom renderers
Each component has a default renderer that specifies how the component should behave and be drawn in an app built with PubNub Chat Components for Android. All component renderers, together with their default implementations, are defined in a given component folder under the renderer
subfolder.
-
ChannelList
The default implementation of
ChannelRenderer
is available in theDefaultChannelRenderer.kt
file. You can implement the following functions:Function Default value Description Channel(name: String, description: String?, modifier: Modifier, profileUrl: String?, onClick: (() -> Unit)?, onLeave: (() -> Unit)?
n/a A composable function to draw a channel item. Placeholder()
n/a A composable placeholder used during data loading. Separator(title: String?, onClick: (() -> Unit)?)
n/a A composable function to draw a separator for channel types. -
MemberList
The default implementation of
MemberRenderer
is available in theDefaultMemberRenderer.kt
file. You can implement the following functions:Function Default value Description 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)
n/a A composable function to draw a separator for members. Placeholder()
n/a A composable placeholder used during data loading. -
MessageList
The default implementation of
MessageRenderer
is available in theGroupMessageRenderer.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?, timetoken: Timetoken, reactions: List<ReactionUi>, onMessageSelected: (() -> Unit)?, onReactionSelected: ((Reaction) -> Unit)?, reactionsPickerRenderer: ReactionsRenderer
n/a A composable function to draw a message item. Placeholder()
n/a A composable placeholder used during data loading. Separator(text: String)
n/a A composable function to draw a separator for members. -
Message reactions (part of
MessageList
)The default implementation of
ReactionsRenderer
is available in theDefaultReactionsPickerRenderer.kt
file. You can implement the following functions:Function Default value Description fun Picker(onSelected: (Reaction) -> Unit)
n/a Composable function to draw a reactions picker. fun PickedList(currentUserId: UserId, reactions: List<ReactionUi>, onSelected: (Reaction) -> Unit)
n/a Composable function to draw all the reactions under the message. -
MessageInput
The default implementation of
TypingIndicatorRenderer
is available in theDefaultTypingIndicatorRenderer.kt
file. You can implement the following functions:Function Default value Description TypingIndicator(data: List<TypingUi>)
n/a A composable function to draw the typing indicator. The
TypingUi
data contains information about the user, typing state, and the last signal timestamp.data class TypingUi(
val user: MemberUi.Data,
val isTyping: Boolean,
val timestamp: Long = System.currentTimeMillis().timetoken,
)
Create a custom renderer
If you need to change the structure of the view, you can create a custom renderer that extends the corresponding component's interface and pass it using the {componentName}Renderer
parameter. Each component has different customization options. Check the renderer interfaces and default implementations to see the available data and functions you can customize to your app needs.
For example, if you want to create a custom renderer for TypingIndicator
that is a part of the MessageInput
component, create a separate file, like MyCustomTypingIndicatorRenderer.kt
, with a new configuration for a custom MyCustomTypingIndicatorRenderer
and pass it through the typingIndicatorContent
parameter when invoking the MessageInput
component in your chat app:
MessageInput(
…
typingIndicatorContent = { typing ->
MyCustomTypingIndicatorRenderer.TypingIndicator(typing)
},
…
)
Other customization options
If you use PubNub Chat Components to add chat functionality to your existing application, you would need to create the chat view in your app and customize its look to your needs. To add additional files, like images or icons, and static content to your Android project, import them in Android Studio to the respective subfolder of the res
directory. This way all referenced resources will be kept externally and only referenced in your app code when needed.
Add chat string reference
For example, you can add a common Chat
string value to the values/strings.xml
file and reference it in your app when adding a new view for your chat app:
<resources>
<string name="chat">Chat</string>
</resources>
Add chat icon image
You can also add a chat icon to be displayed in the app's bottom navigation menu. To do that, select an SVG image and import it into your Android Studio project as a drawable resource. Android Studio will convert it into an XML file. Read the official Android documentation to learn how to do that.