Our last post focused on optiÂmizÂing UI sysÂtem impleÂmenÂtaÂtion by breakÂing down design into smallÂer pieces, idenÂtiÂfyÂing the UI eleÂments and comÂpoÂnents, and using proÂtoÂcol-oriÂentÂed proÂgramÂming in iOS. As I menÂtioned here.
This time around, we’ll give Android the spotÂlight by buildÂing a samÂple app that can switch themes using respecÂtive dark mode. We’ll also disÂcuss approachÂes that can make Android UI develÂopÂment more efficient.
Theme and Style in Android #
GenÂerÂalÂly we folÂlow themes and styles as much as posÂsiÂble for conÂsisÂtenÂcy. Before divÂing into the samÂple app, let’s see how the Android develÂopÂer guide defines themes and styles:
- A style is a colÂlecÂtion of attribÂutÂes that specÂiÂfy the appearÂance for a sinÂgle
View
. A style can specÂiÂfy attribÂutÂes such as font colÂor, font size, backÂground colÂor, and much more. - A theme is a type of style that’s applied to an entire app, activÂiÂty, or view hierÂarÂchy — not just an indiÂvidÂual view. When you apply your style as a theme, every view in the app or activÂiÂty applies each style attribute that it supÂports. Themes can also apply styles to non-view eleÂments, such as the staÂtus bar and winÂdow background.
The botÂtom line? ApplyÂing a style to a view impacts only that view and not its chilÂdren. HowÂevÂer, if you apply a theme to a view it will be applied to all view/non-view elements.
Dark Theme #
StartÂing with Android Q, users can switch the device into dark theme via sysÂtem setÂtings -> DisÂplay -> Dark theme. To supÂport the dark theme funcÂtionÂalÂiÂty, you must set the app’s theme to inherÂit from a DayNight
theme.
Google recÂomÂmends updatÂing the app theme to inherÂit from the Theme.MaterialComponents.DayNight
(or one of its descenÂdants). For example:
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">
<!-- Customize your theme here -->
</style>
In most casÂes, switchÂing to dark theme doesn’t just about invertÂing the white into black:
There are sevÂerÂal prinÂciÂples we need to follow:
- DarkÂen with grey.
- Apply limÂitÂed colÂor accents in dark theme.
- MeetÂing the accesÂsiÂbilÂiÂty colÂor conÂtrast standards.
So we also need to define sepÂaÂrate theme attribÂutÂes withÂin each theme, which can be inherÂitÂed from style name="AppTheme" parent="Theme.MaterialComponents.Light"
in res/values/themes.xml
file:
<style name="AppTheme" parent="Theme.MaterialComponents.Light">
<!-- Customize your light theme here -->
</style>
Then define your dark theme in res/values-night/themes.xml
:
<style name="AppTheme" parent="Theme.MaterialComponents">
<!-- Customize your dark theme here -->
</style>
For most apps that use AppCompat
themes, if you want to increÂmenÂtalÂly add mateÂrÂiÂal comÂpoÂnents you have to add the theme attribÂutÂes to the existÂing app theme. You can find all new attribÂutÂes here.
From Android 10 onwards you can set android:forceDarkAllowed="true"
in the activity’s theme, so that even the app withÂout DayNight
can autoÂmatÂiÂcalÂly apply dark theme. Just make sure everyÂthing looks right before you turn it on.
MulÂtiÂple themes with dark mode
The samÂple we’re going to build is a bit difÂferÂent. In this case, we’re planÂning on sevÂerÂal themes.
This means havÂing sevÂerÂal DayNight
themes. Let’s define our basic theme first:
<style name="AppTheme" parent="Theme.AppCompat.Light">
<item name="colorControlDisabled">@color/grey_200</item>
<item name="colorControlDisabledDark">@color/grey_300</item>
</style>
<style name="AppTheme.Basic">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
colorControlDisabled
and colorControlDisableDark
are cusÂtom theme attributes.
You can define cusÂtom theme attribÂutÂes in res/values/attrs.xml
like this:
<resources>
<attr name="themePrimary" format="reference" />
<attr name="themeOnPrimary" format="reference" />
<attr name="themePrimaryDark" format="reference" />
<attr name="themeOnPrimaryDark" format="reference"/>
<attr name="themeAccent" format="reference" />
<attr name="themeSecondary" format="reference"/>
<attr name="themeSecondaryDark" format="reference"/>
<attr name="themeOnSecondary" format="reference"/>
<attr name="colorControlDisabledDark" format="reference"/>
<attr name="colorControlDisabled" format="reference"/>
<attr name="themeColorControlHighlight" format="reference"/>
<attr name="themeColorControlHighlightDark" format="reference"/>
<attr name="themeBackground" format="reference"/>
<attr name="themeOnBackground" format="reference"/>
<attr name="themeSurface" format="reference"/>
<attr name="themeOnSurface" format="reference"/>
</resources>
Now define the red theme:
<style name="AppTheme.Basic.Red">
<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>
</style>
Next, we define dark mode for the red theme:
<style name="AppTheme.Basic.Red.Dark">
<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>
</style>
We can see AppTheme.Basic.Red.Dark
inherÂits from AppTheme.Basic.Red
, which overÂrides the attribÂutÂes that need to change in dark mode:
<!-- The primary branding color for the app. By default, this is the color
applied to the action bar background. -->
<attr name="colorPrimary" format="color" />
<!-- Dark variant of the primary branding color. By default, this is the color
applied to the status bar (via statusBarColor) and navigation bar
(via navigationBarColor). -->
<attr name="colorPrimaryDark" format="color" />
<!-- The background used by framework controls. -->
<attr name="controlBackground" format="reference" />
If you’re lookÂing to dig deepÂer, here are all the origÂiÂnal attribÂutÂes in AppÂComÂpat theme.
Use this same approach to define othÂer colÂor themes and their dark modes for ​“orange”, ​“blue”, ​“green”, ​“purÂple”, ​“teal”, or whatÂevÂer you decide to name it.
Next, we need to style our views. For conÂsisÂtenÂcy, carÂry out themes and styles as much as posÂsiÂble, and keep Android’s style hierÂarÂchy in mind. The list is ordered from highÂest preceÂdence to lowÂest to help you deterÂmine which attribÂutÂes to apply:
- ApplyÂing charÂacÂter- or paraÂgraph-levÂel styling via text spans to
TextView
-derived classÂes - ApplyÂing attribÂutÂes programmatically
- ApplyÂing indiÂvidÂual attribÂutÂes directÂly to a view
- ApplyÂing a style to a view
- Default styling
- ApplyÂing a theme to a colÂlecÂtion of views, an activÂiÂty, or your entire app
- ApplyÂing cerÂtain view-speÂcifÂic styling, such as setÂting a
TextAppearance
on aTextView
So, wherÂevÂer we need to change the theme in our samÂple app, we need to apply the theme attribÂutÂes. For examÂple, using a graÂdiÂent drawÂable gradient_background.xml as the backÂground, will look like this:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="?colorPrimary"
android:endColor="?colorPrimaryDark"
android:angle="0"/>
</shape>
Now a card view with a backÂground changed by theme:
<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:
<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 colÂor will always be pink_​600 no matÂter what text colÂor is set in texÂtApÂpearÂance or what theme you use. For things you nevÂer want to change, apply indiÂvidÂual attribÂutÂes directly.
Okay, now that our themes and views are set up, we can call SetÂTheme in MainActivity.kt
. Here is the code snippet:
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:
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 someÂwhat tedious process, our app now supÂports mulÂtiÂple themes with respecÂtive dark mode.
In app develÂopÂment, requireÂments, design, and impleÂmenÂtaÂtion (espeÂcialÂly UI) are prone to change. The first thing we need to keep in mind is to avoid creÂatÂing styles with hardÂcodÂed valÂues. SepÂaÂratÂing UI code by first definÂing theme, styles, and cusÂtom view comÂpoÂnents results in a powÂerÂful cenÂtral conÂtrol and reduces the amount of code and improve reusabilÂiÂty and testability.
In the long run underÂstandÂing how style/​view hierÂarÂchy works withÂin a speÂcifÂic operÂatÂing sysÂtem helps to creÂate a clear hierÂarÂchy impleÂmenÂtaÂtion, while ultiÂmateÂly makÂing code more reusable.
Next up in our How to Build a BetÂter App UI ArchiÂtecÂture series: CreÂatÂing a cusÂtom view in Android. We can’t wait to share our insights.
Looking for more like this?
Sign up for our monthly newsletter to receive helpful articles, case studies, and stories from our team.
A 3-part framework for getting your software project approved internally
September 25, 2024Explore this strategic approach to securing internal buy-in for your custom software projects. The framework emphasizes starting with a lean business case, engaging key stakeholders across the organization to align economic, operational, and technical considerations, and embracing an iterative learning process to make informed decisions.
Read moreUX Writing Tips
February 3, 2023Kai shares a few tips he's collected on how to write for user interfaces.
Read more