Notes: 11. Understand Themes in Android
Jetpack Compose lets you style your Android apps using Material Design 3 which is the latest design system created by Google. We do this by defining styles or using the predefined ones.
A style is simply a collection of configurations that specifies the design for a composable. You can apply the style to a composable just like a button or a Text. With this, whenever you change a configuration in the style definition, all the composables that uses that specific style gets updated. This helps prevent code smells as a single style can be applied to multiple composables.
Now a style focuses on a composable. What if we want to style our entire app, what do we use?
Well, we use a Theme to do that. A theme in Jetpack Compose basically provides the design system for your app’s branding which is applied to an entire app.
Styles and themes are designed to work together to design the appearance of your app. They offer the benefit of separation of concerns because the file for the appearance of your app is declared in a different file which is separate from your code logic and layout structure.
Jetpack Compose uses the MaterialTheme composable function to configure the theming for Android apps. Material components like Text, Button and the rest, uses the values provided here as their default styling.
And this function accepts four(4) arguments:
- colorScheme
- typography
- shapes
- and the content to be displayed which is going to be a composable
So in a nutshell, Material based theming in Android simply lets you customize your app’s color, text and shapes to match your product’s brand identity.
There are two(2) themes files in any Android project you create:
-
Theme.kt- which is used to style the composables in your Android app. And the -
themes.xml- which is used to configure and style the Android system related elements. Things like the status bar, splash screen, and so on. I mean, the Android OS elements that are not composables. This was how you styled apps when Views were used in Android. But this file is still useful as you’ll see now and later on in the course.
To access the theme used in your project, go ahead and open up the AndroidManifest.xml file. If you look at the application node, you can see it has an attribute named android:theme. Here it says the theme is Theme.Bullseye. Themes are declared using the style syntax inside the same file and that’s why you have style in its path. You can also add a theme for an activity too by creating a theme for it and assigning it to the android:theme property.
Now, to open up the theme file, hold down the Cmd key and then click on it. This opens up the themes.xml file which contains the light theme.
If you remember, we modified this theme to hide the status bar in the last part. We did that by adding in the Fullscreen style to the parent’s definition. Thethemes.xml file can be used to style non-composable elements like the status bar.
Now its time to style the composables in our app. The themes.xml file used to be where we created styles for Android apps when using Views. But since Jetpack Compose became the modern way we build Android apps, styling is configured in the Theme.kt file.
The styling related files for composables are contained inside the ui.theme package. Go ahead and open it up if its not already open. You can see we have the Color.kt file which contains the colors for the app. We also have the Type.kt which is used to define the typography styles. And finally, we have Theme.kt file which has the theming configurations. This file uses values from both the Colors and Type file.
Go ahead and open it up.
First, you have the color schemes for both light mode and dark modes. These values are gotten from the Color.kt file. We’ll be changing them in a bit though.
Next, we have the BullseyeTheme composable function. This is the main theme of our compose-based app. This is where all the configurations are done. It might look intimidating at first sight, but not to worry, I’ll break it down.
You just need to keep one thing in mind, the MaterialTheme composable we talked about needs three(3) things: the color scheme, typography and shapes. And the colorScheme is the first thing inside the BullseyeTheme.
In here, it uses a when conditional statement to determine the type of color scheme to use. The first condition checks to see if dynamic color is enabled and if the app is running on Android 12 and up. If both this conditions are true, then it uses the dynamic color palette.
Dynamic color is a new feature in Android 12 and up. It simply creates a color scheme for our app based on the device wallpaper. This helps to make our app have a similar color palette to match the wallpaper.
But we don’t want this for our app. Currently the dynamicColor boolean parameter is set to true but before we change it to false, run your app to open up the emulator. This color matches the wallpaper of the emulator. The colors we have been using so far is not from the Color.kt file. Our app uses dynamic color.
If I click on the home button, you can see the wallpaper has a sky blue and black color. Dynamic color creates a color scheme based on this and that is why you see components like the button has a darker shade of blue and this could be because of the black color in the wallpaper.
Alright, open back Bullseye.
Then change the dynamicColor’s default value to false:
dynamicColor: Boolean = false,
And instantly, the colors of the components in our app changes to use the defined colors in the Color.kt file.
The second condition checks to see if the device is in dark mode and if yes, it returns the dark color scheme. And the value of the darkTheme boolean condition is derived from the isSystemInDarkTheme() function from the parameter above.
Finally, if any of the two conditions above returns false, then return the light color scheme.
The next code block simply sets the color of the status bar and also sets the foreground color of the status bar based on the theme applied. This is done to make the content on the status bar be visible enough based on the background color of the status bar.
Now the last block of code is where the MaterialTheme composable function we talked about is returned. Remember, I said it accepts four (4) arguments which are colorScheme, typography, shapes and the content to be displayed.
If you notice the shapes argument is not passed here and this is simply because we are using the defaults provided by the MaterialTheme. If we decided to create custom shapes just like we did for the colors and typography then we’ll have to pass it in.
So the BullseyeTheme provides the MaterialTheme to the content and its descendants. Now you might be thinking, but where do we use the BulleyeTheme in our app? Well, we use it inside the MainActivity file. Let’s open it up.
You can see that BullseyeTheme is set as the root content of the Activity. Inside it, we use the Surface composable to set the background color of the app. Finally, we return the GameScreen, which is what we can see in the UI. With this setup, the GameScreen and its descendants have access to the theme’s values and this is why you see the app is styled based on the default values of the theme. Plus, we can also reassign composables to use different styles from the theme.
Okay, let’s start customizing the look of the Bullseye app. First, let’s change the color palette of the app. Open up the Color.kt file. You can see the colors being used here and by the side you have a visual box of the color.
Paste in the following code:
val Red = Color(0xFFFF5252)
val Black = Color(0xFF000000)
val White = Color(0xFFFFFFFF)
val Grey = Color(0xFF808080)
These are the colors we’ll be using for Bullseye. Next, let’s update the theme to use them. Head back to the Theme.kt file.
Then update the light and dark color schemes to use the color constants we just defined like so:
private val DarkColorScheme = darkColorScheme(
primary = Red,
secondary = Grey,
tertiary = White,
onPrimary = White
)
private val LightColorScheme = lightColorScheme(
primary = Red,
secondary = Grey,
tertiary = Black,
onPrimary = White,
)
We set the color schemes for different color roles. Do note that this is how material design names their colors. For example, the primary color is the main color of the app and onPrimary is the color that will be used by a UI element that is on top the primary color. In our case, if a box has the red primary color, then the text on top of it will be white. You get the idea. Not to worry, I’ve provided a link to how Material design defines color in the author notes just in case you want to learn more.
Alright, run your app to see the changes. And there you have it, Bullseye is updated to use the new color scheme.
Let’s see what it looks like in dark mode. Pull down the status bar. Then pull down the notifications window. Tap the settings icon at the bottom right corner. This opens up the settings screen. And make sure your emulator is in landscape mode.
Then scroll to the display setting. Tap it. Scroll to the dark theme. Then select the switch to activate dark mode. Next, tap the overview button in the emulator window to show the list of open apps. Select Bullseye. And you can see how our app looks in dark mode. Looks nice, right?
Okay, let’s turn off dark mode. Head back to Bullseye. And our app returns back to the light theme. Cool!!!
Now we’re done understanding how themes work and have set our custom color palette, let’s work on styling texts in the next episode.