Customising Android app UI with themes

Maxim Kostenko
Revolut Tech
Published in
7 min readJul 19, 2023

Keeping an app fresh and engaging is important to provide users with an enjoyable experience. One effective method is to add a personal touch to your app by supporting different themes in the application.

In this article, we’ll explore the various options that developers have to enhance user experience with the themes. We’ll also share our company’s experience in building themes, and the technical hurdles we faced during Android development.

Let’s dive in!

Depending on your app and company goals, you have several options to consider when implementing theming in your Android app. Here are a few:

  • Switching between light and dark mode: This has become almost a necessity on mobile platforms due to users’ preference for dark mode
  • Implementing dynamic colours. Android 12 introduced Dynamic Color, allowing you to adapt your app’s colour scheme to match the user’s wallpaper
  • Going above and beyond, as we did in Revolut: We offer all the options mentioned above, and also provide our customers with the ability to choose colour-based themes in the app. This allows them to select their favourite colour from a predefined palette, and the app’s appearance changes to match it

By leveraging these options, you can create an app with a look and feel that aligns with your company’s branding and goals.

In this article, we’ll build a simple app that takes advantage of all the mentioned customisations options:

Switching between light and dark mode

With many platforms now offering system-wide dark mode, implementing a dark theme in your app has become almost an industry standard. While there are numerous resources available for implementing dark mode, we won’t delve into all the technical details here.

That said, if you’re interested in learning more about implementing a dark theme in Android, you can refer to the official guide.

In our demo app, we support dark and light mode by defining two styles:

Light:

Dark:

As we put themes to the separate folders (values and values-night), the system will pick up the required theme automatically, depending on user settings.

Both styles inherit Theme.MaterialComponents.DayNight.NoActionBar, which provides support for light and dark modes for basic UI elements such as backgrounds and text. Additionally, we define our own colorPrimary and colorSecondary to give our app a unique look.

We use AppCompatDelegate.setDefaultNightMode to switch between light and dark mode in runtime:

Here is an example of how the app looks with support for both dark and light modes:

If you’re interested in seeing the full screen layout and code, you can check out the demo project on GitHub.

Applying dynamic colours

Android 12 introduces extended personalisation capabilities. Users can now choose a wallpaper, and the system will automatically adapt its colours to match the wallpaper. Additionally, apps can support wallpaper colours using the DynamicColors API.

Demo from official documentation

The simplest way to enable dynamic colours in an Android app is to use the Theme.Material3.DynamicColors.DayNight theme. But, using it requires migrating to the Material3 theme, which may not always be desirable because it not only changes the colours but also alters the appearance of components according to Material3 guidelines.

If we want to support dynamic colours without changing the app’s theme, we can specify a dynamicColorThemeOverlay for our theme:

Light theme style

Dark theme style

To apply the styling, we need to call DynamicColors API in the Application’s onCreate:

And that’s how the app looks with dynamic colours enabled:

📢 Heads up! It’s crucial to follow Material Design naming conventions for the dynamic colours overlay to work properly. The overlay applies a set of dynamic colours on top of the current theme and replaces the color tokens it is aware of, such ascolorPrimary and colorSecondary. You can learn more about material color tokens in the official doc.

Changing colours in runtime

With the approach described above, an app will always apply dynamic colours if they’re available.

But what if we want to have a more flexible way to work with themes? Some apps may want to maintain their branding by default and not rely on dynamic colours completely, giving users the option to select a theme they want.

At Revolut, we give our customers the ability to change the app’s theme at any time:

Although, this non-standard approach can be quite tricky to implement. After customers select a theme, we need to tell the app to recreate all the views, so they apply the new colours. The Android framework handles that for us when we need to change from the light to dark theme using AppCompatDelegate.setDefaultNightMode. This method will recreate activities under the hood.

But there is no way to force color changes to support switching to dynamic colors in the runtime. The good news is that nobody stops us from checking how AppCompatDelegate.setDefaultNightMode works and reproducing its approach:

Yes, this method simply loops through each activity and calls applyDayNight forcing activities to recreate! sActivityDelegates is a static set that collects all activity references.

The mentioned API is private, so to make our solution work we’ll draw inspiration from Android’s approach:

ColorThemesController registers ActivityLifecycleCallbacks to collect all activities to a single set. The applyColorTheme method will store a selected theme in the static field and trigger recreation for all the activities.

The most interesting part happens in onActivityCreated callback. It applies a selected theme to the newly created activity. DynamicColors.applyToActivityIfAvailable(activity) sets the dynamic colours. And for default theme, we’ll do nothing to keep default activity theme.

The logic starts working after we initialise ColorThemesController in the App class:

Now we can invoke ColorThemesController.applyColorTheme() to make a change in runtime:

Let’s check how that works in action:

You can find the full code of the app in the GitHub repository.

Custom themes

Dynamic colours are available on Android only, which leads to differences in personalisation capabilities between platforms if your app is distributed for iOS as well.

For those reasons, at Revolut, we didn’t stop at dynamic colour support. We also offer custom colour themes, allowing users to choose the main colour of the app:

It will be quite easy to modify our sample app setup to support a custom theme.

Let’s check how can we bring a custom Orange theme to the app.

First step will be to write a theme overlay which we’ll apply on top of the default theme. You can find more info on how theme overlays work in the following article. But in short, a theme overlay defines colours to replace in the given theme. It will add new colours to the theme if they are not present there. It’s like adding keys to the HashMap.

Here is a theme overlay for the light mode that replaces colorPrimary of our base theme:

And for the dark mode:

Now we’ll modify our ColorThemesController to support the new theme:

The Orange theme applied to activity using activity.theme.applyStyle(R.style.ColorThemeOverlay_Orange, true) . This call sets an overlay on top of the current activity theme, as we wanted.

Last touch will be to trigger ColorThemesController.applyColorTheme when orange theme selected:

Now our demo app is ready to switch between default, dynamic and the custom Orange theme, maximising customers personalisation capabilities.

Conclusion

In this article, we’ve explored a variety of ways to personalise your app’s appearance. By supporting dark mode, you can cater to the preferences of many users, or you can choose to use dynamic colours to make sure your app stays in sync with the system. Custom overlays offer cross-platform styles that are accessible to everyone.

Each app is one-of-a-kind, and every app has its own unique styling needs. With the right tools and techniques, you can create an experience that aligns with your app’s objectives and keeps users happy by offering a look and feel they enjoy. By implementing the approaches discussed in this article, you can make your app’s styling more adaptable and user-friendly.

--

--