Jetpack Compose Bottom Navigation bar - how to configure color for background, icon and text

1.6k views Asked by At

Created a simple Jetpack Compose app straight out of the Android Studio template and added a bottom navigation bar. Did not specify/override any color anywhere. However, bottom nav bar labels show black-on-purple. I did not change ui/theme/Color.kt. Dark mode not enabled in the device.

enter image description here

I need to change the label text color contrasting to the background (depending up light/dark mode etc.). Where and how do I configure this?

Manifest refers to theme:

    <style name="Theme.MyApp" parent="android:Theme.Material.Light.NoActionBar" />

Here is the code:

setContent {
    MyAppTheme {
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colorScheme.background
        ) { 
            val homeViewModel: HomeViewModel by viewModels()
            val navController = rememberNavController()
            val items = listOf(
                Screen.Home,
                Screen.Profile,
            )   
                
            Scaffold(
                bottomBar = { 
                    BottomNavigation {
                        val navBackStackEntry by navController.currentBackStackEntryAsState()
                        val currentDestination = navBackStackEntry?.destination
                        items.forEach { screen ->  
                            BottomNavigationItem(
                                icon = { Icon(screen.icon, contentDescription = null) },
                                label = { 
                                    Text(
                                        stringResource(screen.resourceId),
                                        style = Typography.labelSmall
                                    )   
                                },  
                                selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
                                onClick = { 
                                    navController.navigate(screen.route) {
                                        popUpTo(navController.graph.findStartDestination().id) {
                                            saveState = true
                                        }   
                                        launchSingleTop = true
                                        restoreState = true
                                    }   
                                },  
                            )   
                        }   
                    }   
                }   
            )   
        }
    }
}
2

There are 2 answers

0
Megh Lath On

First you need to define/support dynamic theme in your compose app:

AppTheme.kt
@Composable
fun MainAppTheme(
    isDarkMode: Boolean,
    content: @Composable () -> Unit
) {
    MaterialTheme(colorScheme = if (isDarkMode) themeDarkColorScheme else themeLightColorScheme) {
        CompositionLocalProvider(
            LocalDarkMode provides isDarkMode,
            LocalAppColorScheme provides if (isDarkMode) appDarkColorScheme else appLightColorScheme,
            content = content
        )
    }
}

object AppTheme {
    val colorScheme: AppColorScheme
        @Composable
        @ReadOnlyComposable
        get() = LocalAppColorScheme.current
}

AppColorScheme.kt

private val primaryColor = Color(0xFFFF9800)

private val secondaryLightColor = Color(0xFF34495E)
private val secondaryVariantLightColor = Color(0x5234495E)

private val secondaryDarkColor = Color(0xFFCEE5FF)
private val secondaryVariantDarkColor = Color(0x66CEE5FF)

private val tertiaryDarkColor = Color(0xFF58633A)
private val tertiaryLightColor = Color(0xFFDCE8B4)

private val containerHighLightColor = Color(0x2934495E)
private val containerNormalLightColor = Color(0x1434495E)
private val containerLowLightColor = Color(0x0A34495E)

private val containerHighDarkColor = Color(0x3DCEE5FF)
private val containerNormalDarkColor = Color(0x29CEE5FF)
private val containerLowDarkColor = Color(0x14CEE5FF)

private val textPrimaryLightColor = Color(0xDE000000)
private val textSecondaryLightColor = Color(0x99000000)
private val textDisabledLightColor = Color(0x66000000)

private val textPrimaryDarkColor = Color(0xFFFFFFFF)
private val textSecondaryDarkColor = Color(0xCCFFFFFF)
private val textDisabledDarkColor = Color(0x99FFFFFF)

private val outlineLightColor = Color(0x14000000)
private val outlineDarkColor = Color(0x14FFFFFF)

private val surfaceLightColor = Color(0xFFFFFFFF)
private val surfaceDarkColor = Color(0xFF121212)

private val awarenessAlertColor = Color(0xFFCA2F27)
private val awarenessPositiveColor = Color(0xFF47A96E)
private val awarenessWarningColor = Color(0xFFD39800)

internal val themeLightColorScheme = lightColorScheme().copy(
    primary = primaryColor,
    onPrimary = textPrimaryLightColor,
    background = surfaceLightColor,
    onBackground = textPrimaryLightColor,
    onSecondary = textPrimaryLightColor
)

internal val appLightColorScheme = AppColorScheme(
    primary = primaryColor,
    secondary = secondaryLightColor,
    secondaryVariant = secondaryVariantLightColor,
    tertiary = tertiaryDarkColor,
    tertiaryVariant = tertiaryLightColor,
    outline = outlineLightColor,
    surface = surfaceLightColor,
    textPrimary = textPrimaryLightColor,
    textSecondary = textSecondaryLightColor,
    textDisabled = textDisabledLightColor,
    outlineInverse = outlineDarkColor,
    textInversePrimary = textPrimaryDarkColor,
    textInverseDisabled = textDisabledDarkColor,
    textInverseSecondary = textSecondaryDarkColor,
    containerInverseHigh = containerHighDarkColor,
    containerNormalInverse = containerNormalDarkColor,
    secondaryInverseVariant = secondaryVariantDarkColor,
    containerHigh = containerHighLightColor,
    containerNormal = containerNormalLightColor,
    containerLow = containerLowLightColor
)

internal val themeDarkColorScheme = darkColorScheme().copy(
    primary = primaryColor,
    onPrimary = textPrimaryDarkColor,
    background = surfaceDarkColor,
    onBackground = textPrimaryDarkColor,
    onSecondary = textPrimaryDarkColor
)

internal val appDarkColorScheme = AppColorScheme(
    primary = primaryColor,
    secondary = secondaryDarkColor,
    secondaryVariant = secondaryVariantDarkColor,
    tertiary = tertiaryLightColor,
    tertiaryVariant = tertiaryDarkColor,
    outline = outlineDarkColor,
    surface = surfaceDarkColor,
    textPrimary = textPrimaryDarkColor,
    textSecondary = textSecondaryDarkColor,
    textDisabled = textDisabledDarkColor,
    outlineInverse = outlineLightColor,
    textInversePrimary = textPrimaryLightColor,
    textInverseDisabled = textDisabledLightColor,
    textInverseSecondary = textSecondaryLightColor,
    containerInverseHigh = containerHighLightColor,
    containerNormalInverse = containerNormalLightColor,
    secondaryInverseVariant = secondaryVariantLightColor,
    containerHigh = containerHighDarkColor,
    containerNormal = containerNormalDarkColor,
    containerLow = containerLowDarkColor
)

val LocalDarkMode = staticCompositionLocalOf {
    false
}

val LocalAppColorScheme = staticCompositionLocalOf {
    appLightColorScheme
}

data class AppColorScheme(
    val primary: Color,
    val tertiary: Color,
    val tertiaryVariant: Color,
    val secondary: Color,
    val secondaryVariant: Color,
    val surface: Color,
    val outline: Color,
    val textPrimary: Color,
    val textSecondary: Color,
    val textDisabled: Color,
    val outlineInverse: Color,
    val textInversePrimary: Color,
    val textInverseSecondary: Color,
    val textInverseDisabled: Color,
    val containerInverseHigh: Color,
    val containerNormalInverse: Color,
    val secondaryInverseVariant: Color,
    val containerHigh: Color,
    val containerNormal: Color,
    val containerLow: Color,
    val positive: Color = awarenessPositiveColor,
    val alert: Color = awarenessAlertColor,
    val warning: Color = awarenessWarningColor,
    val onPrimary: Color = textPrimaryDarkColor,
    val onPrimaryVariant: Color = textPrimaryLightColor,
    val onSecondary: Color = textSecondaryDarkColor,
    val onDisabled: Color = textDisabledLightColor
) {
    val containerNormalOnSurface: Color
        get() {
            return containerNormal.compositeOver(surface)
        }
}

Now apply this MainAppTheme in place of MyAppTheme:

setContent {

    // Apply your logic to toggle darkMode/lightMode conditions. This is basic one
    val isDarkModeEnabled = when (resources?.configuration?.uiMode?.and(UI_MODE_NIGHT_MASK)) {
                UI_MODE_NIGHT_YES, UI_MODE_NIGHT_UNDEFINED -> {
                    true
                }

                UI_MODE_NIGHT_NO -> {
                    false
                }
            }

    MainAppTheme(isDarkMode = isDarkModeEnabled) {
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colorScheme.background
        ) { 
            val homeViewModel: HomeViewModel by viewModels()
            val navController = rememberNavController()
            val items = listOf(
                Screen.Home,
                Screen.Profile,
            )   
                
            Scaffold(
                bottomBar = { 
                    BottomNavigation(
                        backgroundColor = AppTheme.colorScheme.containerNormalOnSurface,
                        contentColor = AppTheme.colorScheme.primary
                    ) {
                        val navBackStackEntry by navController.currentBackStackEntryAsState()
                        val currentDestination = navBackStackEntry?.destination
                        items.forEach { screen ->  
                            BottomNavigationItem(
                                icon = { Icon(screen.icon, contentDescription = null) },
                                label = { 
                                    Text(
                                        stringResource(screen.resourceId),
                                        style = Typography.labelSmall
                                    )   
                                },  
                                selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
                                selectedContentColor = **Specify color**,
                                unselectedContentColor = **Specify color**,
                                onClick = { 
                                    navController.navigate(screen.route) {
                                        popUpTo(navController.graph.findStartDestination().id) {
                                            saveState = true
                                        }   
                                        launchSingleTop = true
                                        restoreState = true
                                    }   
                                },  
                            )   
                        }   
                    }   
                }   
            )   
        }
    }
}
0
Gold Yon War On

You can use backgroundColor and contentColor like below:

BottomNavigation(
    modifier: Modifier = Modifier,
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    elevation: Dp = BottomNavigationDefaults.Elevation,
    content: @Composable RowScope.() -> Unit
)