Building Better App UI Architecture- Android Themes
January 13, 2020
Our last post focused on optimizing UI system implementation by breaking down design into smaller pieces, identifying the UI elements and components, and using protocol-oriented programming in iOS. As I mentioned here.
This time around, we’ll give Android the spotlight by building a sample app that can switch themes using respective dark mode. We’ll also discuss approaches that can make Android UI development more efficient.
Theme and Style in Android
Generally we follow themes and styles as much as possible for consistency. Before diving into the sample app, let’s see how the Android developer guide defines themes and styles:
- A style is a collection of attributes that specify the appearance for a single
View
. A style can specify attributes such as font color, font size, background color, and much more. - A theme is a type of style that's applied to an entire app, activity, or view hierarchy—not just an individual view. When you apply your style as a theme, every view in the app or activity applies each style attribute that it supports. Themes can also apply styles to non-view elements, such as the status bar and window background.
The bottom line? Applying a style to a view impacts only that view and not its children. However, if you apply a theme to a view it will be applied to all view/non-view elements.
Dark Theme
Starting with Android Q, users can switch the device into dark theme via system settings -> Display -> Dark theme. To support the dark theme functionality, you must set the app’s theme to inherit from a DayNight
theme.
Google recommends updating the app theme to inherit from the Theme.MaterialComponents.DayNight
(or one of its descendants). For example:
`
xml
<!-- Customize your theme here -->
`
In most cases, switching to dark theme doesn’t just about inverting the white into black:
There are several principles we need to follow:
- Darken with grey.
- Apply limited color accents in dark theme.
- Meeting the accessibility color contrast standards.
So we also need to define separate theme attributes within each theme, which can be inherited from
style name="AppTheme" parent="Theme.MaterialComponents.Light"
in res/values/themes.xml
file:
`
xml
`
Then define your dark theme in res/values-night/themes.xml
:
`
xml
`
For most apps that use AppCompat
themes, if you want to incrementally add material components you have to add the theme attributes to the existing app theme. You can find all new attributes here.
From Android 10 onwards you can set android:forceDarkAllowed="true"
in the activity's theme, so that even the app without DayNight
can automatically apply dark theme. Just make sure everything looks right before you turn it on.
Multiple themes with dark mode
The sample we’re going to build is a bit different. In this case, we’re planning on several themes.
This means having several DayNight
themes. Let’s define our basic theme first:
`
xml
`
colorControlDisabled
and colorControlDisableDark
are custom theme attributes.
You can define custom theme attributes in res/values/attrs.xml
like this:
`
html
`
Now define the red theme:
`
xml
<item name="colorPrimary">@color/red_500</item>
<item name="colorPrimaryDark">@color/red_800</item>
<item name="colorAccent">@color/red_900</item>
<item name="themePrimary">@color/red_500</item>
<item name="themePrimaryDark">@color/red_800</item>
<item name="themeAccent">@color/red_900</item>
<item name="themeOnPrimary">@color/white</item>
<item name="themeSecondary">@color/red_100</item>
<item name="themeSecondaryDark">@color/red_200</item>
<item name="themeOnSecondary">@color/white</item>
<item name="themeBackground">@color/white</item>
<item name="themeOnBackground">@color/black</item>
<item name="themeSurface">@color/white</item>
<item name="themeOnSurface">@color/black</item>
<item name="themeColorControlHighlight">@color/red_800</item>
<item name="themeColorControlHighlightDark">@color/red_900</item>
`
Next, we define dark mode for the red theme:
`
xml
<item name="colorPrimary">@color/grey_700</item>
<item name="colorPrimaryDark">@color/black</item>
<item name="themeSurface">@color/grey_700</item>
<item name="themeOnSurface">@color/white</item>
<item name="android:colorBackground">@color/black</item>
`
We can see AppTheme.Basic.Red.Dark
inherits from AppTheme.Basic.Red
, which overrides the attributes that need to change in dark mode:
`
xml
`
If you’re looking to dig deeper, here are all the original attributes in AppCompat theme.
Use this same approach to define other color themes and their dark modes for “orange", “blue", “green", “purple", "teal", or whatever you decide to name it.
Next, we need to style our views. For consistency, carry out themes and styles as much as possible, and keep Android's style hierarchy in mind. The list is ordered from highest precedence to lowest to help you determine which attributes to apply:
- Applying character- or paragraph-level styling via text spans to
TextView
-derived classes - Applying attributes programmatically
- Applying individual attributes directly to a view
- Applying a style to a view
- Default styling
- Applying a theme to a collection of views, an activity, or your entire app
- Applying certain view-specific styling, such as setting a
TextAppearance
on aTextView
So, wherever we need to change the theme in our sample app, we need to apply the theme attributes. For example, using a gradient drawable gradient_background.xml as the background, will look like this:
`
xml
<?xml version="1.0" encoding="utf-8"?>
<gradient
android:startColor="?colorPrimary"
android:endColor="?colorPrimaryDark"
android:angle="0"/>
`
Now a card view with a background changed by theme:
`
xml
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_view_margin"
app:cardBackgroundColor="?attr/themeSurface"
>
`
You can then define a textView like this:
`
xml
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="8dp"
android:text="@string/app_title"
android:background="?colorPrimary"
android:textColor="@color/pink_600"
android:textAppearance="@style/MyAppTextAppearance.Header1"
/>
`
The text color will always be pink_600 no matter what text color is set in textAppearance or what theme you use. For things you never want to change, apply individual attributes directly.
Okay, now that our themes and views are set up, we can call SetTheme in MainActivity.kt
.
Here is the code snippet:
`
kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (getPreferences(Activity.MODE_PRIVATE).getBoolean("isNightMode", false)) {
when(getSavedTheme()) {
R.style.AppTheme_Basic_Orange -> setTheme(R.style.AppTheme_Basic_Orange_Dark)
R.style.AppTheme_Basic_Red -> setTheme(R.style.AppTheme_Basic_Red_Dark)
R.style.AppTheme_Basic_Blue -> setTheme(R.style.AppTheme_Basic_Blue_Dark)
R.style.AppTheme_Basic_Green -> setTheme(R.style.AppTheme_Basic_Green_Dark)
R.style.AppTheme_Basic_Purple -> setTheme(R.style.AppTheme_Basic_Purple_Dark)
R.style.AppTheme_Basic_Teal -> setTheme(R.style.AppTheme_Basic_Teal_Dark)
}
} else {
setTheme(getSavedTheme())
}
// …
}
`
Save/get the theme to user preference:
`
kotlin
private fun saveTheme(value: String) {
val pref = getPreferences(Activity.MODE_PRIVATE)
pref.edit().putString("theme", value).apply()
recreate()
}
private fun getSavedTheme(): Int { val theme = getPreferences(Activity.MODE_PRIVATE).getString("theme", BLUE) when (theme) {
ORANGE -> return R.style.AppTheme_Basic_Orange
RED -> return R.style.AppTheme_Basic_Red
BLUE -> return R.style.AppTheme_Basic_Blue
GREEN -> return R.style.AppTheme_Basic_Green
PURPLE -> return R.style.AppTheme_Basic_Purple
TEAL -> return R.style.AppTheme_Basic_Teal
else -> return R.style.AppTheme_Basic_Orange
}
}
`
After a somewhat tedious process, our app now supports multiple themes with respective dark mode.
In app development, requirements, design, and implementation (especially UI) are prone to change. The first thing we need to keep in mind is to avoid creating styles with hardcoded values. Separating UI code by first defining theme, styles, and custom view components results in a powerful central control and reduces the amount of code and improve reusability and testability.
In the long run understanding how style/view hierarchy works within a specific operating system helps to create a clear hierarchy implementation, while ultimately making code more reusable.
Next up in our How to Build a Better App UI Architecture series: Creating a custom view in Android. We can’t wait to share our insights.
Stay in the loop with our latest content!
Select the topics you’re interested to receive our new relevant content in your inbox. Don’t worry, we won’t spam you.

Putting a Kettle On: Server-Side Swift with Vapor
March 23, 2020One of my goals this year is to make a concerted effort to try out Vapor for server development. As a framework for making server applications in Swift, I’ve had my eye on Vapor since the 3.0 beta period many moons ago. The release of Vapor 4 seemed like as good a time as any to dive back in.
Read more
App Architecture Series: Building a Better User Interface
October 21, 2019At Michigan Software Labs, we don’t simply write code to satisfy requirements. We find ways to solve problems efficiently and effectively. Knowing that a well-structured application comes down to a set of techniques and patterns, I have been exploring design pattern theories and how they shape our thinking.
Read more
Are You Solving the Right Problem?
February 26, 2020In Art’s Principles, renowned architect Art Gensler described part of his Design Thinking consultation process as “finding the right problem to solve.” This resonated with me, particularly as I’ve thought about how Michigan Software Labs interacts with clients in the Product Strategy Phase; something I believe sets us apart from other software development companies.
Read more