Introduction
- This is going to be a 3 part series where I show how to set up a dark mode with jetpack compose. The three parts are:
1) Setting up dark mode for single view
2) Setting up dark mode for entire app
3) Animating color transition
Github link
What we are building today
- Today we are going to be making this dark mode toggle:
Finding your colors
- If you are like me, you probably don't know a lot about colors and design. Thankfully, Google has provided us with 2 tools to help with just that:
1) The color picker
2) The color display tool
- After you have used those tools to find the appropriate colors for your app, create a new file called
Colors.kt
and add your choosen colors to it: ```
val primaryLight =Color(0xFFbfd5ef)
val primaryLightVariant =Color(0xFFf2ffff)
val lightSecondary = Color(0xFFefd8bf)
val lightSecondaryVariant = Color(0xFFefd8bf)
val Black2 = Color(0xFF000000)
val White2= Color(0xFFFFFFFF)
val RedErrorDark = Color(0xFFB00020)
val RedErrorLight = Color(0xFFEF5350)
val primaryDark =Color(0xFF102840)
val primaryDarkVariant =Color(0xFF00001a)
val darkSecondary = Color(0xFF402810)
val darkSecondaryVariant = Color(0xFF200000)
- The name of these colors are not super important but we will be using them when creating our theme
### Creating the theme:
- First of all, if you are unfamiliar with Theming in Jetpack compose. I would highly recommend you read the [Custom Theming codelab](https://developer.android.com/codelabs/jetpack-compose-theming?hl=en#0) and the [replacing material systems article](https://developer.android.com/jetpack/compose/designsystems/custom#replacing-systems).
- So Jetpack compose has an implementation of the [Material Designs](https://m3.material.io/) in a class called `MaterialTheme`. This type of implementation contains the Material designs, color, typography and shape attributes. When we customize these values they are automatically reflected in the Material components we use(like the Scaffold )
- We need to create a new file called `Theme.kt` and place this code inside of it:
private val LightThemeColors = lightColors(
primary = primaryLight,
primaryVariant = primaryLightVariant,
onPrimary = Black2,
secondary = lightSecondary,
secondaryVariant = lightSecondaryVariant,
onSecondary = Black2,
error = RedErrorDark,
onError = RedErrorLight,
)
private val DarkThemeColors = darkColors(
primary = primaryDark,
primaryVariant = primaryDarkVariant,
onPrimary = White2,
secondary = darkSecondary,
secondaryVariant = darkSecondaryVariant,
onSecondary = White2,
error = RedErrorLight,
onError = RedErrorLight,
//surface = Color(0xFF3c506b),
)
@Composable
fun AppTheme(
darkTheme: Boolean,
content: @Composable () -> Unit,
) {
MaterialTheme(
colors = if (darkTheme) DarkThemeColors else LightThemeColors,
content= content
)
}
- First I would like to draw our attention to the `lightColors()` and `darkColors()` functions. These are used to build our Material Design color system. We will use this to provide a sensible default so we don't always have to specify colors. Keen eyed viewers will notice that there is no `surface` or `background` parameter specified for the color functions. This is intentional on my part to demonstrate how they will be automatically generated for us. We are able to see the `surface` generation when we use the `Scaffold` composable later in this tutorial. The color of the Scaffolds drawer is determined by the `surface` color.
### Replacing the default Material color system
- Now to override the default color theme we use this code:
@Composable
fun AppTheme(
darkTheme: Boolean,
content: @Composable () -> Unit,
) {
MaterialTheme(
colors = if (darkTheme) DarkThemeColors else LightThemeColors,
content= content
)
}
- With the code above we are centralizing the styling by creating our own composable that wraps and configures a MaterialTheme. This gives us a single place to specify out theme customization and allows us to easily reuse it. The colors we use will be determined by the boolean value of `darkTheme`. In this tutorial the `darkTheme` value is stored inside of a viewModel.
### Using our custom themes with Scaffold and Switch
- Assuming we have a normal Scaffold like so:
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
drawerGesturesEnabled = scaffoldState.drawerState.isOpen,
topBar = {
TopAppBar(
title = { Text("Calf Tracker") },
navigationIcon = {
IconButton(
onClick = {
scope.launch { scaffoldState.drawerState.open() }
}
) {
Icon(Icons.Filled.Menu, contentDescription = "Toggle navigation drawer")
}
}
)
},
drawerContent = {
SwitchDrawer()
}
)
- Now I will not be going into much default about the Scaffold. However, I will point out that everything inside the `drawerContent`, is going to be shown when the drawer is open and its color is determined by the `surface` parameter of the color functions we created earlier. The `SwitchDrawer()` is a composable that we have to create:
@Composable
fun SwitchDrawer(viewModel: WeatherViewModel = viewModel()){
var switchState by remember { mutableStateOf(true) }
Row(
Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly
) {
Text("Dark mode", style = TextStyle(fontSize = 18.sp),modifier = Modifier.weight(1f))
Spacer(Modifier.width(8.dp))
Switch(
checked = switchState,
onCheckedChange ={
switchState=it
viewModel.setDarkMode()
},//called when it is clicked
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = MaterialTheme.colors.primary,
checkedTrackColor = MaterialTheme.colors.secondary,
uncheckedTrackColor = MaterialTheme.colors.secondary,
)
)
}
}
- Firstly, the `switchState` is a `MutableState` that the Switch component uses to determine what position it is(open or close). The `onCheckedChange` parameter is a callback that is called when the switch is clicked on:
onCheckedChange ={
switchState=it
viewModel.setDarkMode()
}
- As you can see we pass it a lambda expression and in this context the `it` is a boolean value determining if the switch has been clicked or not. The `viewModel.setDarkMode()` is the method stored in the viewModel that is used to trigger the actual color changes. It does so by simply changing a Boolean value over and over again
- The colors parameter is used to display the colors of the actual switch:
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = MaterialTheme.colors.primary,
checkedTrackColor = MaterialTheme.colors.secondary,
uncheckedTrackColor = MaterialTheme.colors.secondary,
)
- Since we are using the `MaterialTheme.colors,` which we have overridden in our theme. The color of the Switch will change depending if we are in dark mode or not.
###Using our Custom theme
- So to actually use our custom themes we need to wrap our custom theme around the base composable like so:
AppTheme(viewModel.uiState.value.darkMode){
ScaffoldView()
}
- The `viewModel.uiState.value.darkMode` is just a boolean value stored inside a viewModel that gets toggled by `viewModel.setDarkMode()`.
- In order to actually make the colors change we must use the MaterialTheme:
Column(modifier = Modifier.background(MaterialTheme.colors.primary)){}
- Now as long as this Column is nested inside of our `AppTheme` anytime the switch is toggled, it will change the colors to be either light or dark, depending on what was defined inside the `lightColors()` and `darkColors()` functions.
###Conclusion
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on [Twitter](https://twitter.com/AndroidTristan).
Top comments (0)