The Fiori Navigation Drawer in Jetpack Compose¶
The Android SDK provides a Material3
based Navigation Drawer that is built using the latest Jetpack Compose libraries. The SDK provides a FioriNavigationDrawer
that is made up of several building block composables. Under the hood, it wraps the Material3
ModalNavigationDrawer
, PermanentNavigationDrawer
, and DismissibleNavigationDrawer
.
Anatomy of the Fiori Navigation Drawer¶
Using the Fiori Navigation Drawer¶
The Navigation Drawer has a few different implementations depending on the screen size. At its simplest, it can be used in your app like any Composable
function:
Although the implementation is trivial, it is important to note how the use of the Material3
Scaffold
is used under these situations:
Compact Width Screen Sizes¶
val scrollState = rememberScrollState()
val drawerState = rememberDrawerState(DrawerValue.Closed)
FioriModalNavigationDrawer(
scrollState = scrollState,
drawerState = drawerState,
drawerData = drawerData,
content = {
Scaffold( ... )
},
)
Medium and Expanded Screen Sizes¶
val scrollState = rememberScrollState()
val drawerState = rememberDrawerState(DrawerValue.Open)
Scaffold( ... ) {
FioriTabletNavigiationDrawer(
scrollState = scrollState,
drawerData = drawerData,
modifier = Modifier.padding(top = 32.dp), //to account for the appbar
drawerState = drawerState,
content = {
// Screen Content
}
)
}
On a medium/expanded screen size, the Navigation Drawer is a part of the screen content, thus it must be part of the Scaffold
's content
parameter.
Navigation Drawer Content¶
As mentioned before, Material3
provides ModalNavigationDrawer
, PermanentNavigationDrawer
, and DismissibleNavigationDrawer
. To make sure that these are still usable in the SDK, the FioriModalNavigationDrawer
, FioriPermanentNavigationDrawer
, and FioriDismissibleNavigationDrawer
have been provided to wrap these classes while maintaining respect for all parameters. In addition, all three provided navigation drawers come with a few extra features/parameters:
Profile Header Composable¶
This optional parameter will be placed at the top of the drawer content. Although it can be any composable, the SDK also provides a composable out-of-the-box called DrawerProfileHeader
. It takes a mandatory ImageThumbnail
for the image as well as two optional composables that represent the name
and details
text:
@Composable
fun DrawerProfileHeader(
imageThumbnail: ImageThumbnail,
modifier: Modifier = Modifier,
nameText: (@Composable () -> Unit)? = null,
detailsText: (@Composable () -> Unit)? = null,
styles: FioriNavigationDrawerStyles = FioriDrawerDefaults.styles()
) {
...
}
The SDK also provides out-of-the-box NameText
and DetailsText
composables to use as the parameters for DrawerProfileHeader
. For example:
DrawerProfileHeader(
imageThumbnail = ImageThumbnail( ... ),
nameText = {
NameText(text = "John Doe")
},
detailsText = {
DetailsText(text = "Maintenance Lead")
}
)
Headline Text Composable¶
This is also an optional Composable
parameter and sits below the profile header (if present) and above the drawer content (if present). For example:
HeadlineText("Sample Headline")
Drawer Content Composable¶
This is also an optional Composable
parameter and sits below both the profile header and headline (if either/or are present).
Although this is an optional parameter, it is recommended to use this to hold the navigation items. There currently is no out-of-the-box composable for this parameter due to how different each drawer content may be. For example, how many, and the ordering of navigation items, section headers, and dividers may differ from application to application. Fortunately, the SDK provides out-of-the-box composables for dividers, section headers. and navigation items.
Fiori Navigation Drawer Item Composable¶
The FioriNavigationDrawerItem
is a wrapper for the for the Material3
NavigationDrawerItem
. It has the same parameters except instead of taking one icon it takes two icons (selected icon and unselected icon):
@Composable
fun FioriNavigationDrawerItem(
label: @Composable () -> Unit,
selected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
unselectedIcon: (@Composable () -> Unit)? = null,
selectedIcon: (@Composable () -> Unit)? = null,
badge: (@Composable () -> Unit)? = null,
shape: Shape? = null,
colors: NavigationDrawerItemColors = FioriDrawerDefaults.NavigationDrawerItemColors(),
interactionSource: MutableInteractionSource = remember{ MutableInteractionSource() }
) {
...
}
Drawer Divider Composable¶
There is also a DrawerDivider
composable that may be used to separate different categories of navigation drawer items.
DrawerDivider()
Section Header Composable¶
The SectionHeader
composable may be used to separate different categories of navigation drawer items. Unless it is the first SectionHeader
in the list, it is recommended to have a divider above it. For example:
SectionHeader(text = "Sample Section Header")
Assembling the Navigation Drawer Content¶
There are two ways to assemble the content for a Navigation Drawer. The first way is utilizing the NavigationDrawerData
class and passing that as a parameter into any of the out-of-the-box composables. The second way is by manually declaring composables. Based on the application needs, here are some guidelines on which approach to use in a given situation:
- First Approach: Use the
NavigationDrawerData
approach for a data-driven and dynamically created composables. This approach limits customization, for example the use ofModifiers
of the sub components/composables in the Navigation Drawer. Custom theming is still supported. - Second Approach: Build manually using composables if freedom of customization is desired. This approach allows the application to use any composable(s) for the drawer content.
Assembling the Navigation Drawer Content Using Navigation Drawer Data¶
Here is an example of implementing NavigationDrawerData
. This approach is purely data-driven and object-based and does not utilize composables:
val navigationDrawerDataSample = FioriDrawerData(
profileHeaderData = FioriDrawerProfileHeaderData(
profileAvatar = ImageThumbnail(
ImageShape.ROUNDEDCORNER,
imageType = ImageType.RESOURCE,
resId = R.drawable.angular_square,
imageDesc = "Image Description"
),
nameText = "John Doe",
detailsText = "Maintenance Lead"
),
headline = "Location 294830",
navigationItems = listOf(
FioriDrawerItemData(
type = DrawerItemType.SectionHeader,
sectionHeaderText = "Task Manager"
),
FioriDrawerItemData(
type = DrawerItemType.DestinationButton,
destination = FioriNavigationItemData(
selectedIcon = FioriIcon(
iconType = IconType.IMAGEVECTOR,
imageVector = Icons.Rounded.AddCircle,
contentDescription = "Create Job Selected"
),
unselectedIcon = FioriIcon(
iconType = IconType.IMAGEVECTOR,
imageVector = Icons.Outlined.AddCircle,
contentDescription = "Create Job Unselected"
),
label = "Create a Job",
onClick = {},
)
),
FioriDrawerItemData(
type = DrawerItemType.DestinationButton,
destination = FioriNavigationItemData(
selectedIcon = FioriIcon(
iconType = IconType.IMAGEVECTOR,
imageVector = Icons.Rounded.DateRange,
contentDescription = "Job Schedule Selected"
),
unselectedIcon = FioriIcon(
iconType = IconType.IMAGEVECTOR,
imageVector = Icons.Outlined.DateRange,
contentDescription = "Job Schedule Unselected"
),
label = "Job Schedule",
onClick = {},
)
),
FioriDrawerItemData(
type = DrawerItemType.DestinationButton,
destination = FioriNavigationItemData(
selectedIcon = FioriIcon(
iconType = IconType.IMAGEVECTOR,
imageVector = Icons.Rounded.List,
contentDescription = "Active Workers Selected"
),
unselectedIcon = FioriIcon(
iconType = IconType.IMAGEVECTOR,
imageVector = Icons.Outlined.List,
contentDescription = "Active Workers Unselected"
),
label = "Active Workers",
onClick = {},
)
),
FioriDrawerItemData(
type = DrawerItemType.DestinationButton,
destination = FioriNavigationItemData(
selectedIcon = FioriIcon(
iconType = IconType.IMAGEVECTOR,
imageVector = Icons.Rounded.Warning,
contentDescription = "Warning Selected"
),
unselectedIcon = FioriIcon(
iconType = IconType.IMAGEVECTOR,
imageVector = Icons.Outlined.Warning,
contentDescription = "Warning Unselected"
),
hasBadge = true,
badgeText = "2",
label = "Critical",
onClick = {},
)
),
FioriDrawerItemData(
type = DrawerItemType.Divider
),
FioriDrawerItemData(
type = DrawerItemType.SectionHeader,
sectionHeaderText = "Job Updates"
),
FioriDrawerItemData(
type = DrawerItemType.DestinationButton,
destination = FioriNavigationItemData(
selectedIcon = FioriIcon(
iconType = IconType.IMAGEVECTOR,
imageVector = Icons.Rounded.Notifications,
contentDescription = "Notifications Selected"
),
unselectedIcon = FioriIcon(
iconType = IconType.IMAGEVECTOR,
imageVector = Icons.Outlined.Notifications,
contentDescription = "Notifications Unselected"
),
label = "Notifications",
onClick = {},
hasBadge = true,
badgeText = "49",
)
),
FioriDrawerItemData(
type = DrawerItemType.DestinationButton,
destination = FioriNavigationItemData(
selectedIcon = FioriIcon(
iconType = IconType.IMAGEVECTOR,
imageVector = Icons.Rounded.Email,
contentDescription = "Chat Selected"
),
unselectedIcon = FioriIcon(
iconType = IconType.IMAGEVECTOR,
imageVector = Icons.Outlined.Email,
contentDescription = "Chat Unselected"
),
label = "Chat",
onClick = {},
hasBadge = true,
badgeText = "8"
)
)
)
)
And pass the FioriNavigationDrawerData
instance as a parameter:
val navigationDrawerDataSample = ...
val scrollState = rememberScrollState()
val drawerState = rememberDrawerState(DrawerValue.Closed)
AppTheme {
FioriModalNavigationDrawer(
scrollState = scrollState,
drawerState = drawerState,
drawerData = navigationDrawerDataSample,
content = {
Scaffold( ... )
},
)
}
Result:
Assembling the Navigation Drawer Content Using Composables¶
Starting with the profile header composable:
val profileHeader : @Composable () -> Unit =
{
DrawerProfileHeader(
imageThumbnail = ImageThumbnail(
ImageShape.ROUNDEDCORNER,
imageType = ImageType.RESOURCE,
resId = R.drawable.angular_square,
imageDesc = "Image Description"
),
nameText = {
NameText(text = "John Doe")
},
detailsText = {
DetailsText(text = "Maintenance Lead")
}
)
}
Along with a headline composable:
val headline : @Composable () -> Unit =
{
HeadlineText("Location 294830")
}
Here is an example of using FioriNavigationDrawerItem
, SectionHeader
, and the DrawerDivider
composables together in any order to formulate the rest of the navigation drawer content.
@Composable
fun DrawerContent(
selectedItem: Int,
onCLick: ((Int) -> Unit)
) {
SectionHeader(text = "Task Manager")
FioriNavigationDrawerItem(
label = { DrawerLabel(text = "Create a Job") },
selected = 0 == selectedItem,
onClick = { onCLick(0) },
unselectedIcon = { Icon(Icons.Outlined.AddCircle, contentDescription = "")},
selectedIcon = { Icon(Icons.Rounded.AddCircle, contentDescription = "" )}
)
FioriNavigationDrawerItem(
label = { DrawerLabel(text = "Job Schedule") },
selected = 1 == selectedItem,
onClick = { onCLick(1) },
unselectedIcon = { Icon(Icons.Outlined.DateRange, contentDescription = "")},
selectedIcon = { Icon(Icons.Rounded.DateRange, contentDescription = "" )}
)
FioriNavigationDrawerItem(
label = { DrawerLabel(text = "Active Workers") },
selected = 2 == selectedItem,
onClick = { onCLick(2) },
unselectedIcon = { Icon(Icons.Outlined.List, contentDescription = "")},
selectedIcon = { Icon(Icons.Rounded.List, contentDescription = "" )}
)
FioriNavigationDrawerItem(
label = { DrawerLabel(text = "Critical") },
selected = 3 == selectedItem,
onClick = { onCLick(3) },
unselectedIcon = { Icon(Icons.Outlined.Warning, contentDescription = "")},
selectedIcon = { Icon(Icons.Rounded.Warning, contentDescription = "" )},
badge = { BadgeLabel(text = "2") }
)
DrawerDivider()
SectionHeader(text = "Job Updates")
FioriNavigationDrawerItem(
label = { DrawerLabel(text = "Notifications") },
selected = 4 == selectedItem,
onClick = { onCLick(4) },
unselectedIcon = { Icon(Icons.Outlined.Notifications, contentDescription = "")},
selectedIcon = { Icon(Icons.Rounded.Notifications, contentDescription = "" )},
badge = { BadgeLabel(text = "49") }
)
FioriNavigationDrawerItem(
label = { DrawerLabel(text = "Chat") },
selected = 5 == selectedItem,
onClick = { onCLick(5) },
unselectedIcon = { Icon(Icons.Outlined.Email, contentDescription = "")},
selectedIcon = { Icon(Icons.Rounded.Email, contentDescription = "" )},
badge = { BadgeLabel(text = "8") }
)
}
Putting it all together:
FioriModalNavigationDrawer(
drawerState = drawerState,
scrollState = scrollState,
profileHeader = {
profileHeader()
},
headlineText = {
headline()
},
drawerContent = {
DrawerContent(
selectedItem = selectedItem,
onCLick = {
selectedItem = it
if (dismissOnClick) {
scope.launch { drawerState.close() }
}
}
)
},
content = {
Scaffold( ... )
}
)
Result:
Customizing Navigation Drawer Colors¶
There are two ways that you can customize the colors of the Navigation Drawer.
- Using a custom theme.
- By providing custom colors to the
Composable
function ofFioriModalNavigationDrawer()
,FioriPermanentNavigationDrawer()
orFioriDismissibleNavigationDrawer()
.
Using a Custom Theme to Customize the Navigation Drawer¶
Instead of using FioriHorizonTheme
, use FioriCustomTheme
when creating the component to apply a custom theme. For example, in the CustomThemeNavDrawerPreview()
, we read in a custom theme with a different set of theme colors and then apply FioirCustomTheme
:
@Preview("Custom Theme Nav Drawer")
@Composable
private fun CustomThemeNavDrawerPreview() {
// Read in the custom colors from JSON file in assets/ folder
val customColors = parseCustomColorsFromAssets(LocalContext.current, "custom-theme.json")
if (customColors != null) {
FioriCustomTheme(
customColors = customColors,
customLogo = null
) {
val data = remember { drawerData }
FioriTabletDrawer(scrollState = scrollState, data = data)
}
}
}
It displays the Navigation Drawer as follows:
Setting the Color Parameters¶
The SDK provides an interface of Composable
colors called FioriNavigationDrawerColors
and implementing this interface allows the application to apply colors to different parts of the Navigation Drawer. These include:
- containerColor: Defines the background color of the Navigation Drawer.
- scrimColor: Defines the scrim color, which is only present in a
ModalNavigationDrawer
. - nameTextColor: Defines the name text color, typically used with the Profile Header of the Navigation Drawer.
- detailsTextColor: Defines the details text color, typically used with the Profile Header of the Navigation Drawer.
- headlineTextColor: Defines the headline text color.
- sectionHeaderTextColor: Defines the section header text color.
An application can set their own colors by implementing the FioriNavigationDrawerColors
interface and passing it as a parameter of the FioriModalNavigationDrawer
:
FioriModalNavigationDrawer(
...
colors: FioriNavigationDrawerColors = AppNavigationDrawerDefaults.colors(),
...
)
Apart from the Navigation Drawer colors, the SDK also provides a default implementation of the Material3
NavigationDrawerItemColors
which focus solely on the navigation items:
- selectedIconColor: Defines the icon color of the selected item.
- unselectedIconColor: Defines the icon color of an unselected item.
- selectedTextColor: Defines the text color of the selected item.
- unselectedTextColor: Defines the text color of an unselected item.
- selectedContainerColor: Defines the container color of the selected item.
- unselectedContainerColor: Defines the container color of an unselected item.
- selectedBadgeColor: Defines the badge color of the selected item.
- unselectedBadgeColor: Defines the badge color of an unselected item.
- selectedRippleColor: Defines the ripple color of the selected item.
- unselectedRippleColor: Defines the ripple color of an unselected item.
FioriNavigationDrawerItem(
...
colors = AppNavigationDrawerItemDefaults.NavigationDrawerItemColors(),
...
)
Further Reading¶
Here are additional resources on the Material3
Navigation Drawer:
- Navigation Drawer Design Specifications
ModalNavigationDrawer
PermanentNavigationDrawer
DismissibleNavigationDrawer
NavigationDrawerItem