Creating a reusable actions menu in Jetpack Compose
In this story we will learn how to implement a reusable composable which we can add to the top app bar in our app’s screens to show relevant menu items. The final result will look as shown below:
Creating the menu
First we will see how we can implement this menu using the existing tools in Jetpack Compose, and later we will refactor it to make it reusable, so that we do not have to keep duplicating the same code each time we need to add an actions menu to a new screen.
On Jetpack Compose most root screens will use a Scaffold, which is a slot composable that accepts, among other items, a Top Bar composable and a content composable. For the Top Bar we would typically use the
TopAppBar composable from Material3, which itself is a slot composable, accepting a Title composable, a Navigation Icon composable and an Actions composable. The Actions composable is where we will place our menu action items.
Let’s start by creating the skeleton for our app with a simple Top Bar, initially without any action items:
This displays our app bar and a text for content:
Adding the action menu
Next we will add the actions menu. The
TopAppBar from Material3, as mentioned, has a slot for the actions menu, which is a composable on a
RowScope, so that the items we add here will display in a row, right aligned.
For our sample case, we want to display 2 items always visible, and we want to have other items available via an overflow button, so we need to add 4 items here, the 2 action items that are always visible, the overflow item, and finally the drop down menu.
Let’s see the code that we need in order to have these items displayed as an action menu and then we’ll walk it to explain how it works:
- We have to keep track of the menu state, whether it’s open or not, so we use a
mutableStatevariable for that.
- We provide a composable for the
actionsslot in the
- The actions content composable has a
RowScopeas receiver, so we place the action menu items in the order we want them displayed, first the 2 items that we want visible, using an
IconButtonfor each of them.
- Next we add the overflow item, similar to the 2 visible items. The
onClickfor this item will toggle the visibility of the overflow menu, so we toggle the value of
- The overflow menu is implemented using a
DropdownMenu, and we use our
menuExpandedflag to indicate whether it should be open or not. Likewise, when the user dismisses the drop down menu, we set the flag to false.
- Within the
DropdownMenuItems for each action item we want displayed.
So, as we can see, it is not complex to add an actions menu to the
TopAppBar, but if we have multiple screens where we need to do this it can become quite tedious, so let’s refactor this so that we can create an actions menu without all this boilerplate.
Creating the reusable actions menu component
What we need
We will model our actions menu component on the XML menus from the view system. Below we can see a typical menu for an app:
<?xml version="1.0" encoding="utf-8"?>
We can see that, for each action item, we will need:
- a title
- the display mode (always shown, shown if room, never shown)
- an icon, for items always shown or those shown if room is available
Not shown above, but we will also need the following, as we are using an
IconButton with an
Icon to represent the action items:
- a click handler
- a content description for the item
Defining the action items
We can see from above that we have 3 different display modes, with items sharing a set of properties (title, click listener) regardless of display mode, so this would be a good candidate to represent as a
sealed class, so let’s do just that, defining a hierarchy of sealed classes to represent our action items:
- We define our
sealed classfor the action items. Because we do not have any state in the base class, we actually define it as an
- The interface defines the common properties for all action items, a title and a click handler.
- Next we define the concrete classes, starting with the
AlwaysShownwhich adds the
iconto the base class.
- We do the same with the
- And finally we define the
NeverShownclass, which does not have any additional properties and simply implements those from the interface.
We can see above that the
ShownIfRoom classes have the same properties, so we can aggregate these under a new
sealed interface that will define the 2 new properties, the
contentDescription and the
icon. Let’s do that and reorganize our hierarchy of action item classes:
- We define a new
IconMenuItem, inheriting from
ActionMenuItemthat adds the properties common to both
ShownIfRoomclasses now inherit from the new interface,
Now that we have a way to represent the action menu items, let’s create our menu composable to display them.
Creating the actions menu composable
The first thing we have to figure out is how we will display the action menu items. We have items that will always be shown in the
TopAppBar, others that will never be shown there (only shown in the drop down menu), and a 3rd category of items that might or might not be shown in the
To determine whether an item of type
ShownIfRoom will be shown or not we have to set an upper limit on the number of action items we are willing to display in the
TopAppBar. Once we have agreed on that upper limit, we need to count all the items that are always shown and, if that number is below our upper limit, we can then move items of type
ShownIfRoom to be in the
TopAppBar instead of in the overflow menu. However, we need to also take into account that, if we have overflow items, then we need to display the overflow action item in the
TopAppBar, taking away one of the available slots. Our logic then needs to be as follows:
- Count the number of items that are always shown in the
- Determine if we have overflow items to show in the drop down menu.
- Determine the number of available slots in the
TopAppBar, based on the always displayed items and whether we have an overflow action item.
- If we have available slots, promote items of type
- Move any remaining
ShownIfRoomitems to the drop down menu.
Let’s create a method that will receive a list of
ActionMenuItems, and will split them into 2 buckets, one for items that are always shown in the
TopAppBar and another for items that are relegated to the drop down menu:
- First we define a data class to hold the result, we have 2 sets of action items, those that are shown in the
TopAppBarand those that are in the overflow drop down menu. Note that the list for the always shown items is of type
ActionMenuItem.IconMenuItembecause we can accept
- Our method to split the items receives a list of
ActionMenuItems, the interface representing our action menu items, and the number of items we want to show in the
- Next we split the items into 3 buckets, leveraging their types, one of the advantages of using
sealed classes to represent the action items. We convert the lists for
ShownIfRoomitems to mutable lists because we may need to promote some
- Next we determine if we will have overflow items — we do if we have items of type
NeverShown(as these always go to the drop down), or if the number of
AlwaysShownitems plus the number of
ShownIfRoomitems exceeds the maximum number of visible items, minus 1, to account for the overflow action item. Basically, what we try to do here is promote all
ShownIfRoomitems to be
AlwaysShownitems and see whether their fit or not.
- Now we calculate how many slots have been used up, that’s the number of
AlwaysShownitems, plus 1 if we have overflow items.
- Next we calculate how many available slots we have in the
TopAppBar— that’s the maximum allowed, minus the
AlwaysShownitems, minus 1 if we have to show the overflow action menu.
- Here we check if we have available slots, and whether we have items of type
ShownIfRoomthat we can promote to the
- If that’s the case, we get a sublist from the
ShownIfRoomlist for the number of available slots we have, we add those to the
AlwaysShownlist, and remove them from the
ShownIfRoomlist. In other words, we promote as many items from the
ShownIfRoomlist to the
AlwaysShownlist as available slots we have.
- Finally we have our result, the items that will display in the
TopAppBarare those that were of type
AlwaysShownplus the items of type
ShownIfRoomthat were promoted, and the overflow items are the reminder of the
ShownIfRoomitems, plus all of the
NeverShownitems that were always supposed to be in the drop down menu.
Now that we have split our actions menu items into the 2 buckets, it’s just a matter of displaying them in a composable, so let’s see how we can do that:
- We define our
ActionsMenucomposable accepting a list of
ActionMenuItem, a flag that indicates if the drop down menu is open, a callback for when the user taps on the overflow menu icon, and the number of visible items we want to show in the
TopAppBar. Note that we make our composble stateless by hosting the flag that determines whether the drop down menu is open.
- Next we split our action menu items into the 2 buckets we described earlier. We use a
rememberhere so that we do not keep doing this at each recomposition, the buckets are good as long as the
maxVisibleItemsdo not change, so we use those 2 attributes as keys on the
- After this we iterate over the
AlwaysShownitems and add them to the
TopAppBar, using the icon and content description for each of them.
- Next we check if we have overflow items for the drop down menu.
- If that’s the case, we add the overflow action menu item, and we use the
onToggleOverflowmethod as the click handler.
- The next step is to add the
DropdownMenucomposable to host the drop down items. We pass the
isOpenflag and the callback to toggle the menu.
- Finally, for each item in the overflow we add a
DropdownMenuItemusing the title and the click handler.
And with this our reusable action items menu composable is complete, we can use it as shown below:
- We define a flag to persist the state of the overflow menu — as we did in the original implementation.
- We add our
actionsslot on the
- We provide a list of action items, some of type
AlwaysShown, others of type
ShownIfRoom, and finally others of type
NeverShownwhich are always relegated to the overflow menu.
- Here we provide the current drop down state, open or closed, for the overflow menu.
- And finally this is the callback to toggle the state of the drop down menu.
Thanks for reading, I hope you found this useful and can use it in your apps. Below is the full implementation with some previews, for different scenarios mixing the 3 types of action menu items. Happy coding!