androidkotlinandroid-jetpack-composeandroid-navigation-bar

BottomNavigation not showing chosen colours


I am pretty new to Kotlin. I have a BottomNavigation wrapped in a Scaffold. The tabs background color change works well. But, though the icons are actually white, it appears black on the app, though I set the selectedContentColor to Color.White.

And unselectedContentColor to Color.White.copy(alpha = 0.7f).

It does not even show whether or not I am in one tab or the other. They all just remain the same.

This is the code in my MainActivity.kt:

var selectedTab by remember { mutableIntStateOf(0) }
val tabs = listOf("Home", "Media", "Videos", "Contact")
val icons = listOf(
    painterResource(id = R.drawable.ic_home),
    painterResource(id = R.drawable.ic_media_link),
    painterResource(id = R.drawable.ic_video_library),
    painterResource(id = R.drawable.ic_account_circle),
)

Scaffold(
    bottomBar = {
        BottomNavigation(
            backgroundColor = Color(0xff6c247c),
            contentColor = Color.White,
        ) {
            tabs.forEachIndexed { index, title ->
                BottomNavigationItem(
                    icon = { Icon(painter = icons[index], contentDescription = title) },
                    label = { Text(title) },
                    selected = selectedTab == index,
                    onClick = { selectedTab = index },
                    alwaysShowLabel = true,
                    selectedContentColor = Color.White,
                    unselectedContentColor = Color.White.copy(alpha = 0.7f),
                )
            }
        }
    }
) {
    when (selectedTab) {
        0 -> HomeView()
        1 -> MediaView()
        2 -> VideosView()
        3 -> ContactView()
    }
}

And my theme.xml

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">

    <style name="Theme.HSMApp" parent="android:Theme.Material.Light.NoActionBar" >
        <item name="colorPrimary">@color/primaryColor</item>
        <item name="colorPrimaryVariant">@color/primaryVariantColor</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/secondaryColor</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
    </style>

</resources>

I also have this is in my Theme.kt

package com.phela.hsmapp.ui.theme

import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat

private val DarkColorScheme = darkColorScheme(
    primary = Purple80,
    secondary = PurpleGrey80,
    tertiary = Pink80
)

private val LightColorScheme = lightColorScheme(
    primary = Purple40,
    secondary = PurpleGrey40,
    tertiary = Pink40

    /* Other default colors to override
    background = Color(0xFFFFFBFE),
    surface = Color(0xFFFFFBFE),
    onPrimary = Color.White,
    onSecondary = Color.White,
    onTertiary = Color.White,
    onBackground = Color(0xFF1C1B1F),
    onSurface = Color(0xFF1C1B1F),
    */
)

@Composable
fun HSMAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }

        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }
    val view = LocalView.current
    if (!view.isInEditMode) {
        SideEffect {
            val window = (view.context as Activity).window
            window.statusBarColor = colorScheme.primary.toArgb()
            WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
        }
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}

What should I change?


Solution

  • As already hinted in the comments this is caused by mixing Compose components of Material 2 and Material 3. I would strongly advise to not use the answer of the linked question, though: It downgrades your Material 3 components to Material 2. Since you also use other Material 3 components this will inevitably result in further complications.

    Instead remove all Material 2 components in your app and rely entirely on Material 3. Look through your gradle files (or version catalog if you use that) for any dependencies that start with androidx.compose.material:. They provide the old Material 2 components. Remove them and look for alternatives that start with androidx.compose.material3:.

    In your example the BottomNavigation and BottomNavigationItem are part of androidx.compose.material:material. Replace that dependency with the Material 3 equivalent androidx.compose.material3:material3. The Compose components are largey similar but most of them differ in certain aspects. In your case this means that BottomNavigation becomes NavigationBar and BottomNavigationItem becomes NavigationBarItem. Also, the color parameters changed. With the new Material 3 components it will look like this:

    NavigationBar(
        containerColor = Color(0xff6c247c),
        contentColor = Color.White,
    ) {
        tabs.forEachIndexed { index, title ->
            NavigationBarItem(
                icon = { Icon(painter = icons[index], contentDescription = title) },
                label = { Text(title) },
                selected = selectedTab == index,
                onClick = { selectedTab = index },
                alwaysShowLabel = true,
                colors = NavigationBarItemDefaults.colors().copy(
                    selectedIconColor = Color.White,
                    selectedTextColor = Color.White,
                    unselectedIconColor = Color.White.copy(alpha = 0.7f),
                    unselectedTextColor = Color.White.copy(alpha = 0.7f),
                ),
            )
        }
    }
    

    That said, with Material 3 you should use a theme as defined in your Theme.kt. This way you you won't manually define the colors for each component, instead the components retrieve the colors they need from the color scheme defined in the theme. This will also automatically take care of changing colors when switching to dark mode.

    Currently you only have primary, secondary and tertiary defined. The other color variants are derived from these colors. You can also specify them explicitly, though:

    (The NavigationBar's contentColor isn't used anywhere so I omitted it.)

    Your LightColorScheme could be defined like this:

    private val LightColorScheme = lightColorScheme(
        primary = Purple40,
        secondary = PurpleGrey40,
        tertiary = Pink40,
        surfaceContainer = Color(0xff6c247c),
        onSecondaryContainer = Color.White,
        onSurface = Color.White,
        onSurfaceVariant = Color.White.copy(alpha = 0.7f),
    )
    

    You should also define approrpiate colors for DarkColorScheme that will be used when the user switches to dark mode.

    You can now drop all colors from the navigation bar like this:

    NavigationBar {
        tabs.forEachIndexed { index, title ->
            NavigationBarItem(
                icon = { Icon(painter = icons[index], contentDescription = title) },
                label = { Text(title) },
                selected = selectedTab == index,
                onClick = { selectedTab = index },
                alwaysShowLabel = true,
            )
        }
    }
    

    Now, keep in mind that all the colors defined in the theme also affect other composables, not just the navigation bar. This is by design so the overall look of the app is consistent across all components. You might want to tweak the values to improve it. If you don't want to define all color values manually you can use the Material Theme Builder. It's a website you can use to automatically generate and download the Theme.kt file with matching colors for some presets you define.


    Having said all that you might want to consider replacing the entire Scaffold with a NavigationSuiteScaffold. That builds upon NavigationBar and NavigationBarItem so it uses the same components under the hood and the same theme is used, but it also supports other kinds of navigation components, like a NavigationRail (placed on the side) or a NavigationDrawer (small bar at the side, can be drawn up). Which of these will be used is then decided during runtime depending on the available screen size as recommended in Navigation in Material 3.

    Now, keep in mind that this is still experimental so the API may change in the future. As it is right now you could replace your entire Scaffold with this:

    NavigationSuiteScaffold(
        navigationSuiteItems = {
            tabs.forEachIndexed { index, title ->
                item(
                    icon = { Icon(painter = icons[index], contentDescription = title) },
                    label = { Text(title) },
                    selected = selectedTab == index,
                    onClick = { selectedTab = index },
                    alwaysShowLabel = true,
                )
            }
        },
    ) {
        Column {
            when (selectedTab) {
                0 -> HomeView()
                1 -> MediaView()
                2 -> VideosView()
                3 -> ContactView()
            }
        }
    }
    

    You need to add the gradle dependency androidx.compose.material3:material3-adaptive-navigation-suite-android.