Exploring Jetpack Compose: TopAppBar
This was originally posted at joebirch.co
In many screens of our applications it’s likely that we’re making use of a Toolbar / AppBar within our Android applications. When it comes to building apps with Jetpack Compose, we’re going to want to recreate this component. In this article we’re going to take a look at the Top App Bar component which allows us to do so.
There is a supporting video for this blog post if you would prefer to learn about the Top App Bar through that medium:
The TopAppBar component is often used as the header for our screen — displaying a navigational title along with menu components or any other decorations that the design of our application requires. Within Jetpack Compose, this component can be created via two different functions. The first takes three arguments:
- title — the title to be displayed within the app bar. This is required
- color — the color to be used for the background of the toolbar. Optional, if not provided then the themes primary color will be used.
- navigationIcon — the icon to be displayed at the start of the app bar
@Composable
fun TopAppBar(
title: @Composable() () -> Unit,
color: Color = MaterialTheme.colors().primary,
navigationIcon: @Composable() (() -> Unit)? = null
)
Let’s take a look at building our own TopAppBar — as noted above, we must at least provide a title to do so:
TopAppBar(
title = { Text(text = "AppBar") }
)
You may notice from above that this title takes a @Composable reference, this means that we are not restricted to a Text component alone. Maybe we want to show some form of decoration or custom component inside of our AppBar. For example, we could display a Row consisting of multiple child components. Whilst the below is not something you’d likely need to do, it serves a simple example:
TopAppBar(
title = {
Row(children = {
Text(text = "AppBar")
Text(text = " With another child")
})
}
)
Whilst the TopAppBar uses the primary color from our theme, in some cases we may want to change the color. We can do this by providing a Color reference for the color argument:
TopAppBar(
title = { Text(text = "AppBar") },
color = Color.White
)
As it is, our Top App Bar looks great! However, the function we are using to compose our TopAppBar also allows us to provide a navigationIcon to be displayed at the start of our bar, before the title. Here we can pass a navigation icon in the form of an AppBarIcon instance.
The AppBarIcon is a Composable that contains a clickable container with a built in ripple effect, hosting our provided image. Other than the image, an onClick handler is provided so that we can react to click events on our icon. We won’t handle this here as it’s out of scope of this post.
TopAppBar(
title = { Text(text = "AppBar") },
color = Color.White,
navigationIcon = {
AppBarIcon(
icon = imageResource(
id = R.drawable.ic_menu_black_24dp)
) {
// Open nav drawer
}
}
)
Under the hood this AppBarIcon handle the sizing of the icon for us, using the predefined diameter for app bar icons. Whilst we could achieve this ourselves, this handy helper composable takes some of this work from us.
Container(width = ActionIconDiameter, height = ActionIconDiameter) {
Ripple(bounded = false) {
Clickable(onClick = onClick) {
SimpleImage(icon)
}
}
}
With the above we’ve been able to create a simple TopAppBar that hosts the title we wish to display, along with a navigation icon should be wish to display one. In some cases however, we’re going to want to show actions within our TopAppBar — this is where the second Composable method comes in for the TopAppBar:
@Composable
fun <T> TopAppBar(
title: @Composable() () -> Unit,
actionData: List<T>,
color: Color = MaterialTheme.colors().primary,
navigationIcon: @Composable() (() -> Unit)? = null,
action: @Composable() (T) -> Unit
// TODO: support overflow menu
here with the remainder of the list
)
From the above we can already see some similarities — we can still set the title, color and navigationIcon for our TopAppBar. Here though we see the addition of two more properties — actionData and action, along with a generic declaration for our function, <T>.
When it comes to app bar actions previously, these were defined within XML menu files and loaded as our menu for a screen. Here we provided an ID along with maybe some text or an icon reference. When it comes to the TopAppBar things work a little bit differently. Instead of providing a menu resource we can provide a collection of the type we declare for <T>. This could be a collection of strings, integer resources or a class. Let’s take a little look at an example of what this could look like.
For my type T I’m going to define a new sealed class called MenuAction — when it comes to handling mixed labels/icons, overflow menus and also click listeners it will be easier to handle things when not using a primitive data type. For now we’re just going to define a single action called Share:
sealed class MenuAction(
@StringRes val label: Int,
@DrawableRes val icon: Int) {
object Share : MenuAction(R.string.share, R.drawable.ic_share)}
I can then go ahead and add this as my type <T>:
TopAppBar<MenuAction>(
title = { Text(text = "AppBar") },
color = Color.White,
navigationIcon = {
AppBarIcon(icon = imageResource(
id = R.drawable.ic_menu_black_24dp)) {
// Open nav drawer
}
}
)
With this defined as the type for our TopAppBar action, we can no go ahead and add the actionData — this is essentially a list of menu items that we wish to be shown within our TopAppBar. For now we’ll just provide a single MenuAction item to be shown:
TopAppBar<MenuAction>(
title = { Text(text = "AppBar") },
color = Color.White,
navigationIcon = {
AppBarIcon(icon = imageResource(
id = R.drawable.ic_menu_black_24dp)) {
// Open nav drawer
}
},
actionData = listOf(MenuAction.Save)
)
Now that our TopAppBar knows what menu items are to be displayed we can go and add the action argument. This action is used to build a composable for each of the menu items that is being used. So if we had multiple MenuAction items returned in our actionData, then this action would be called for each of those, building a composable in the process. Here we’ll use the same AppBarIcon class from before to build a composable using our MenuAction reference:
TopAppBar<MenuAction>(
title = { Text(text = "AppBar") },
color = Color.White,
navigationIcon = {
AppBarIcon(icon = imageResource(
id = R.drawable.ic_menu_black_24dp)) {
// Open nav drawer
}
},
actionData = listOf(MenuAction.Save),
action = { menuAction ->
AppBarIcon(icon = imageResource(
id = menuAction.icon)) {
// Handle action click
}
}
)
With that in place, we now have a TopAppBar that supports menu items. As mentioned above, we can support multiple menu actions — currently the support for overflow menus within the API has not been completed, so the behaviour here may not be as expected.
In this article we’ve taken a dive into the TopAppBar component from Jetpack Compose and how we can use it within our applications. Whilst it’s quite a small component, it provides a lot of flexibility to allow us to create app bars that suite the needs of our applications. Do you have any questions on how to use this component, or any thoughts you have from using it already? Please reach out here or on twitter!