Skip to content

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

Anatomy

Tablet Landscape Modal

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.

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:

  1. First Approach: Use the NavigationDrawerData approach for a data-driven and dynamically created composables. This approach limits customization, for example the use of Modifiers of the sub components/composables in the Navigation Drawer. Custom theming is still supported.
  2. 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:

Customization

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:

Customization

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 of FioriModalNavigationDrawer(), FioriPermanentNavigationDrawer() or FioriDismissibleNavigationDrawer().

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:

Customization via Custom Theme

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 NavigationDrawerItemColorswhich 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:


Last update: February 20, 2023