The CptNavigationBar is a Material3-based bottom navigation component that provides primary navigation between the app’s main sections. It integrates with the navigation system and maintains visual selection state.
Overview
Package: es.mobiledev.commonandroid.ui.component.navigationBar
File: commonAndroid/src/main/java/es/mobiledev/commonandroid/ui/component/navigationBar/CptNavigationBar.kt:12
A composable function that renders a bottom navigation bar with selectable items corresponding to app modules.
API Reference
CptNavigationBar
@Composable
fun CptNavigationBar (
selectedModule: NavigationModule ,
modifier: Modifier = Modifier,
onClickModule: ( AppScreens ) -> Unit ,
)
Parameters
The currently selected navigation module, used to highlight the active tab
modifier
Modifier
default: "Modifier"
Modifier to be applied to the navigation bar container
onClickModule
(AppScreens) -> Unit
required
Callback invoked when a navigation item is clicked, receives the destination screen
Implementation Details
Visual Design
Uses MaterialTheme.colorScheme.primary as the background color
Selected: MaterialTheme.colorScheme.onPrimary
Unselected: MaterialTheme.colorScheme.outlineVariant
Navigation Items
The navigation bar displays items for the following screens:
Screen Configuration
Icon Mapping
val screens = listOf (
AppScreens.Home,
AppScreens.Test,
)
Only modules with hasOwnTab = true in the NavigationModule enum are displayed in the navigation bar.
Usage Examples
Basic Usage
@Composable
fun MyApp () {
val navController = rememberNavController ()
var currentModule by remember { mutableStateOf (NavigationModule.HOME) }
Scaffold (
bottomBar = {
CptNavigationBar (
selectedModule = currentModule,
onClickModule = { screen ->
navController. navigate (screen)
currentModule = screen.module
}
)
}
) { paddingValues ->
// App content
}
}
With Navigation Controller
@Composable
fun AppNavigation (navController: NavHostController ) {
val currentScreen by navController. currentBackStackEntryAsState ()
val currentModule = currentScreen?.destination?.route?. let { route ->
// Derive module from route
} ?: NavigationModule.HOME
ScreenWrapper (
bottomBar = {
CptNavigationBar (
selectedModule = currentModule,
modifier = Modifier,
onClickModule = { screen ->
navController. navigate (screen) {
popUpTo (screen) { inclusive = true }
}
},
)
},
showBottomBar = true
) { paddingValues ->
NavHost (navController, startDestination = AppScreens.Home) {
// Navigation graph
}
}
}
Conditional Display
@Composable
fun ConditionalNavBar (showNavBar: Boolean ) {
if (showNavBar) {
CptNavigationBar (
selectedModule = NavigationModule.HOME,
onClickModule = { screen ->
// Handle navigation
}
)
}
}
CptNavigationBarItem
File: commonAndroid/src/main/java/es/mobiledev/commonandroid/ui/component/navigationBar/CptNavigationBarItem.kt:18
Internal composable for individual navigation items.
@Composable
fun RowScope . CptNavigationBarItem (
@DrawableRes iconRes: Int ,
isSelected: Boolean ,
onClickModule: () -> Unit,
)
Parameters
Drawable resource ID for the item’s icon
Whether this item is currently selected
Callback invoked when this item is clicked
Item Styling
Uses R.dimen.navigation_bar__item_height from resources
Transparent (no background indicator)
Icon Colors
NavigationBarItemDefaults.colors()
NavigationBarItemDefaults. colors (
selectedIconColor = MaterialTheme.colorScheme.onPrimary,
unselectedIconColor = MaterialTheme.colorScheme.outlineVariant,
indicatorColor = Color.Transparent,
)
Integration with Navigation
NavigationModule Enum
enum class NavigationModule ( val hasOwnTab: Boolean = false ) {
LAUNCHER,
HOME (hasOwnTab = true ),
TEST (hasOwnTab = true ),
ARTICLE_DETAIL
}
Only modules with hasOwnTab = true appear in the navigation bar. Detail screens like ARTICLE_DETAIL and initialization screens like LAUNCHER are excluded.
AppScreens Integration
@Serializable
sealed interface AppScreens {
val module: NavigationModule
val hasTopBar: Boolean
val hasBottomBar: Boolean
@Serializable
data object Home : AppScreens {
override val module = NavigationModule.HOME
override val hasBottomBar = true
}
}
Behavioral Features
Click Handling
Selection State
Navigation Action
val isSelected = selectedModule == screen.module
CptNavigationBarItem (
isSelected = isSelected,
onClickModule = {
if ( ! isSelected) {
onClickModule (screen)
}
}
)
Clicking an already-selected item does nothing, preventing unnecessary navigation. onClickModule = { screen ->
navController. navigate (screen) {
popUpTo (screen) {
inclusive = true
}
}
}
Clears the backstack to prevent navigation loops.
Customization
Adding New Navigation Items
To add a new item to the navigation bar:
Define the module:
enum class NavigationModule ( val hasOwnTab: Boolean = false ) {
// ...
PROFILE (hasOwnTab = true ), // New module
}
Create the screen:
@Serializable
data object Profile : AppScreens {
override val module = NavigationModule.PROFILE
override val hasBottomBar = true
}
Add icon mapping:
private fun getCurrentModuleIcon (module: NavigationModule ): Int =
when (module) {
NavigationModule.HOME -> R.drawable.ic_cpt_home
NavigationModule.TEST -> R.drawable.ic_cpt_favorites_outlined
NavigationModule.PROFILE -> R.drawable.ic_cpt_profile // New
else -> R.drawable.ic_cpt_home
}
Add to screens list:
val screens = listOf (
AppScreens.Home,
AppScreens.Test,
AppScreens.Profile, // New
)
Styling Customization
CptNavigationBar (
selectedModule = currentModule,
modifier = Modifier
. shadow ( 8 .dp) // Add elevation
. background (Color.White), // Override background
onClickModule = { /* ... */ }
)
Be cautious when overriding the background color as it may conflict with the theme’s color scheme and affect accessibility.
Accessibility
The component uses Material3’s NavigationBarItem which includes:
Proper touch target sizes (minimum 48dp)
Content descriptions for screen readers
Keyboard navigation support
Focus indicators
Icon (
painter = painterResource (id = iconRes),
contentDescription = null , // Handled by NavigationBarItem
)
Best Practices
Limit Items Keep navigation items between 3-5 for optimal UX. Too many items reduce usability.
Consistent Icons Use consistent icon styles (all outlined or all filled) for visual harmony.
Clear Labels While this implementation uses icons only, consider adding text labels for clarity.
State Management Always track and display the correct selected state to avoid user confusion.
Common Issues
Navigation Bar Not Showing
// Check that the screen has hasBottomBar = true
ScreenWrapper (
showBottomBar = currentScreen.hasBottomBar, // Must be true
bottomBar = { CptNavigationBar ( .. .) }
)
Wrong Item Selected
// Ensure selectedModule matches current screen
val currentSelectedModule by
currentScreen. getCurrentSelectedModule ()
. collectAsStateWithLifecycle ()
CptNavigationBar (
selectedModule = currentSelectedModule, // Keep in sync
// ...
)
See Also