The CptTopBar is a Material3 TopAppBar component that displays the app’s branding and provides access to the navigation drawer. It serves as the primary branding element and navigation entry point at the top of the screen.
Overview
Package: es.mobiledev.commonandroid.ui.component.topBar
File: commonAndroid/src/main/java/es/mobiledev/commonandroid/ui/component/topBar/CptTopBar.kt:16
A simple, stateless composable that renders the application’s top bar with consistent branding.
API Reference
CptTopBar
@OptIn (ExperimentalMaterial3Api:: class )
@Composable
fun CptTopBar (
modifier: Modifier = Modifier,
)
Parameters
modifier
Modifier
default: "Modifier"
Modifier to be applied to the TopAppBar container
This component currently has no configurable parameters beyond the modifier. Future versions may add parameters for navigation callbacks or dynamic titles.
Implementation Details
Structure
The top bar consists of two main elements:
Left-side drawer icon button
Icon: R.drawable.ic_cpt_drawer
Action: Currently TODO - intended for opening navigation drawer
Center-aligned app logo
Image: R.drawable.topbar_cpt_title
Content Description: null (decorative)
Source Code
@OptIn (ExperimentalMaterial3Api:: class )
@Composable
fun CptTopBar (
modifier: Modifier = Modifier,
) {
TopAppBar (
title = {
Image (
painter = painterResource (R.drawable.topbar_cpt_title),
contentDescription = null ,
)
},
navigationIcon = {
IconButton (
onClick = { /* TODO: Add navigation */ },
) {
Icon (
painter = painterResource (R.drawable.ic_cpt_drawer),
contentDescription = null ,
)
}
},
modifier = modifier,
)
}
The component uses @OptIn(ExperimentalMaterial3Api::class) as TopAppBar is currently experimental in Material3.
Usage Examples
Basic Usage
@Composable
fun MyScreen () {
Scaffold (
topBar = { CptTopBar () }
) { paddingValues ->
// Screen content
Column (modifier = Modifier. padding (paddingValues)) {
Text ( "Screen content" )
}
}
}
With ScreenWrapper
@Composable
fun AppScreen () {
ScreenWrapper (
topBar = { CptTopBar () },
showTopAppBar = true ,
) { paddingValues ->
// Content automatically padded
Box (modifier = Modifier. padding (paddingValues)) {
// Your UI
}
}
}
In Navigation Setup
@Composable
fun AppNavigation (navController: NavHostController ) {
val currentScreen by navController. currentBackStackEntryAsState ()
val showTopBar = currentScreen?.destination?.route
?. let { it != "launcher" } ?: false
ScreenWrapper (
topBar = { CptTopBar () },
showTopAppBar = showTopBar,
bottomBar = { /* ... */ },
) { paddingValues ->
NavHost (
navController = navController,
startDestination = AppScreens.Home,
modifier = Modifier. padding (paddingValues)
) {
// Navigation destinations
}
}
}
Conditional Display
@Composable
fun DynamicTopBar (screen: AppScreens ) {
if (screen.hasTopBar) {
CptTopBar ()
}
}
Customization
Adding Navigation Callback
To make the drawer icon functional:
@OptIn (ExperimentalMaterial3Api:: class )
@Composable
fun CptTopBar (
modifier: Modifier = Modifier,
onNavigationClick: () -> Unit = {}, // Add callback parameter
) {
TopAppBar (
title = {
Image (
painter = painterResource (R.drawable.topbar_cpt_title),
contentDescription = null ,
)
},
navigationIcon = {
IconButton (
onClick = onNavigationClick, // Use callback
) {
Icon (
painter = painterResource (R.drawable.ic_cpt_drawer),
contentDescription = "Open navigation drawer" ,
)
}
},
modifier = modifier,
)
}
Usage with callback:
val drawerState = rememberDrawerState (DrawerValue.Closed)
val scope = rememberCoroutineScope ()
ModalNavigationDrawer (
drawerState = drawerState,
drawerContent = { /* Drawer content */ }
) {
Scaffold (
topBar = {
CptTopBar (
onNavigationClick = {
scope. launch {
drawerState. open ()
}
}
)
}
) { paddingValues ->
// Content
}
}
Custom Colors
@OptIn (ExperimentalMaterial3Api:: class )
@Composable
fun CptTopBar (
modifier: Modifier = Modifier,
) {
TopAppBar (
title = { /* ... */ },
navigationIcon = { /* ... */ },
colors = TopAppBarDefaults. topAppBarColors (
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
navigationIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
),
modifier = modifier,
)
}
Dynamic Title
For screens that need different titles:
@OptIn (ExperimentalMaterial3Api:: class )
@Composable
fun CptTopBar (
title: @Composable () -> Unit = {
Image (
painter = painterResource (R.drawable.topbar_cpt_title),
contentDescription = null ,
)
},
modifier: Modifier = Modifier,
) {
TopAppBar (
title = title,
navigationIcon = { /* ... */ },
modifier = modifier,
)
}
Usage:
CptTopBar (
title = {
Text (
text = "Article Details" ,
style = MaterialTheme.typography.titleLarge
)
}
)
Integration Points
With AppNavigation
@Composable
fun AppNavigation (navController: NavHostController ) {
var currentScreen: AppScreens by remember {
mutableStateOf (AppScreens.Launcher)
}
val showTopAppBar by remember {
derivedStateOf { currentScreen.hasTopBar }
}
ScreenWrapper (
topBar = { CptTopBar () },
showTopAppBar = showTopAppBar, // Controlled by screen
// ...
)
}
With BaseScreen
@Composable
fun MyFeatureScreen () {
BaseScreen (
topBar = { CptTopBar () },
isLoading = false ,
) { paddingValues ->
// Screen content
}
}
Styling and Theming
Material3 Defaults
The component inherits styling from Material3’s TopAppBar:
64dp (default Material3 TopAppBar height)
MaterialTheme.colorScheme.surface (default)
MaterialTheme.colorScheme.onSurface (default)
0dp when scrolled to top, 3dp when scrolled
@OptIn (ExperimentalMaterial3Api:: class )
@Composable
fun MyScreen () {
val scrollBehavior = TopAppBarDefaults. pinnedScrollBehavior ()
Scaffold (
topBar = {
CptTopBar (
modifier = Modifier
// Connect to scroll behavior for elevation
)
},
modifier = Modifier. nestedScroll (scrollBehavior.nestedScrollConnection)
) { paddingValues ->
LazyColumn (modifier = Modifier. padding (paddingValues)) {
// Scrollable content
}
}
}
Accessibility
Current State
The current implementation has contentDescription = null for both the logo and drawer icon. This should be updated for better accessibility.
Recommended Improvements
TopAppBar (
title = {
Image (
painter = painterResource (R.drawable.topbar_cpt_title),
contentDescription = "Compose Project Template" , // Add description
)
},
navigationIcon = {
IconButton (
onClick = { /* TODO */ },
) {
Icon (
painter = painterResource (R.drawable.ic_cpt_drawer),
contentDescription = "Open navigation menu" , // Add description
)
}
},
)
Testing Accessibility
@Test
fun topBarAccessibility () {
composeTestRule. setContent {
CptTopBar ()
}
// Verify navigation icon is accessible
composeTestRule
. onNodeWithContentDescription ( "Open navigation menu" )
. assertExists ()
. assertHasClickAction ()
}
Preview Support
The component includes preview annotations:
@PreviewLightDark
@Composable
private fun Preview () {
CptTopBar (
modifier = Modifier,
)
}
The @PreviewLightDark annotation generates previews for both light and dark themes automatically.
Best Practices
Keep it Simple The top bar should remain uncluttered. Limit actions to 2-3 icons maximum.
Consistent Branding Use the same top bar across all main screens for consistent brand identity.
Proper Padding Always apply the padding from Scaffold to avoid content being hidden behind the bar.
Accessibility Always provide content descriptions for icons and images.
Common Patterns
Standard App Bar
With Actions
Collapsing
@Composable
fun StandardScreen () {
Scaffold (
topBar = { CptTopBar () }
) { paddingValues ->
Content (Modifier. padding (paddingValues))
}
}
Most common usage pattern. TopAppBar (
title = { /* Logo */ },
navigationIcon = { /* Drawer */ },
actions = {
IconButton (onClick = { /* Search */ }) {
Icon (Icons.Default.Search, "Search" )
}
IconButton (onClick = { /* Settings */ }) {
Icon (Icons.Default.Settings, "Settings" )
}
}
)
Add action buttons on the right side. @OptIn (ExperimentalMaterial3Api:: class )
val scrollBehavior = TopAppBarDefaults
. exitUntilCollapsedScrollBehavior ()
Scaffold (
topBar = {
LargeTopAppBar (
title = { Text ( "Title" ) },
scrollBehavior = scrollBehavior
)
},
modifier = Modifier
. nestedScroll (scrollBehavior.nestedScrollConnection)
)
For collapsing effect on scroll.
See Also