Android

Building Better App UI Architecture- Android Themes

January 13, 2020
Building Better App UI Architecture- Android Themes

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 `

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 `

Next, we define dark mode for the red theme:

`xml `

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 a TextView

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.

Mei Huang
Mei Huang
Software Developer

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
Development

Putting a Kettle On: Server-Side Swift with Vapor

March 23, 2020

One 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
Development

App Architecture Series: Building a Better User Interface

October 21, 2019

At 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?
Team

Are You Solving the Right Problem?

February 26, 2020

In 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
View more articles