A lot of things have changed since Android Lollipop was released in regards to how visual assets are handled in apps. In addition to vectorDrawables, which was a huge change to the platform, there are other features that make application theming and styling easier to implement and maintain. This article will focus on using theme attributes with vector drawables to show how to easily support different app branding and multiple themes, like dark mode, in a straightforward way.
The approach described in this post is what we are currently applying in Tuenti to easily maintain icons in a huge project with multiple brands.
One asset to rule them all
In a nutshell, what you’ll end up with after reading this post are these automagically styled icons!
This post assumes your minSdk is 21 or higher and you’re already using vectorDrawables in your app. If that’s not the case then consider migrating your pngs to vectorDrawables, unless for some diabolical reason you enjoy the hell of dealing with pngs in a multi-density screen world. Seriously, just migrate.
Show me the code
If you want to jump straight to the code take a look at the sample app. It showcases how to use theme attributes to handle different app themes and dynamically change the appearance of your icons.
Android sample project to play around with vector drawables and theme attributes in different ways The app is very…
What's a Theme?
First of all, it’s important to be very clear about the difference between a theme and a style in Android. I highly recommend checking out this awesome series of posts on Android styling by Nick Butcher to learn more. But, in a nutshell, a theme is a collection of named resources that can be referenced later by styles, layouts, views, etc.
Theme attributes to the rescue
The named resources mentioned earlier are the properties that define a theme in Android and they’re called theme attributes. Many of the properties are already in the Android platform or Appcompat libraries. You can see the most common ones in this great article. But, if you can’t find a theme attribute that suits your design, you can always define custom ones in your project res/values/attrs.xml file. For example:
And then in your res/values/styles.xml file you can reference this new created theme attribute and assign a color value:
Now, how can theme attributes be used with vectorDrawables so your icons automatically change appearance when the app theme is changed or a different app build variant is compiled?
This is frequently how you’ll find most of the vector XML files in an app.
Notice how the color for each vector <path> is hardcoded with a specific color like, android:fillColor=”#3282b8". This kind of code should be removed from the vector assets and a semantic color should be used instead. Semantic colors are just theme attributes that represent reusable colors with specific meaning.
The end goal is to define a set of semantic colors that can be referenced from the app’s vector assets, as shown here.
With the semantic colors defined, the vector assets will automatically change color whenever the app’s theme is changed to a different primaryColor or another build variant is compiled with a different colors.xml palette.
You might be thinking you can achieve the same results by defining colors for your drawables in your layouts or dynamically with code. You wouldn’t be wrong. But that approach has some limitations. Say your code looks like this:
val drawable = getResources().getDrawable(R.drawable.my_image)
val wrapDrawable = DrawableCompat.wrap(normalDrawable)
- First of all, coloring assets that way doesn’t’ change the color of the icon. All it does is paint a color on top. That means if you use a color with transparency for the asset you won’t get the results you expect. There are workarounds, of course. But, the point is to keep things as simple as possible.
- Secondly, when you color drawables that way you can only use one color. So, if your vectorDrawable has several paths with different colors you won’t be able to change the color for each vector path.
Define a set of semantic colors
The goal is to have a nice set of semantic colors that can be reused in all your app build variants/brands and themes. Something like this:
This is also great if you work with a design team, because you can define your semantic colors along with them. You can then make your design directly using the semantic palette. Consequently, when your drawable is imported you won’t have to worry if it’s going to be used in dark mode or any other brand variant of your app. Restyling your app becomes a piece of cake.
Let's get dynamic!
With the semantic colors in place, the vectorDrawables are defined using references to theme attributes. But, what happens when you suddenly need to change the color of an icon at runtime? Let’s say, when an item is clicked. It’s a piece of cake! Just define a theme for the state of your clicked icon and change it dynamically.
Now from your view, lets say MainActivity.kt:
And that's it. You can reuse your themes and easily apply them dynamically for specific views on your layout.
Multi-flavored app? No problem
Last but not least. Say you have an app with multiple build variants/brands and they all have different color palettes. When you use this approach you can still have a single instance of your visual assets and you won’t have to add a new one for every new brand.
Let’s say there are a couple brands defined as product flavors for the project in your app’s build.gradle.
Each brand has a different color palette in the colors.xml file.
When you have the color palettes defined, you just have to redefine the app themes to reference the new colors. So, in each newly created brand folder you just need to add a new file, that can be called something like brand_themes.xml. Then set the app themes to use it for coloring the icons in the different app variants.
With everything in place, you can now compile your different app variants and see the results.
Android Lint is your friend
If you’re convinced this approach is valuable and you’ve realized you don’t need to replicate all your icons for every app theme or brand variant. There are two options for enforcing this new approach through your entire Android project.
- Rely on yourself or your teammates to never use hardcoded color values in your assets, layouts, or views. And be hyper vigilant about hardcoded values during code reviews (not the best option).
- You can use android tooling to detect issues automatically with linting. Check out this great post about using custom linting rules to warn about hardcoded colors in your project. What’s more, you can also detect if you’re using colors like white, blue, red, etc, across your app instead of the semantic colors defined such as colorPrimary, colorDanger, colorWarning, etc.
Dark Mode: three Lint checks to help
Implementing Night Mode in Android is pretty straightforward: you have a theme with attributes and you can just define…
One more thing: VectorCompat!
One very important thing when using this approach is to use VectorCompat. For some reason, theme attributes in vectorDrawables don't always work in Android versions 21, 22 and 23. So use VectorCompat and everything will work fine in API 21 and later. Just add the following lines to your apps’s build.gradle file.
vectorDrawables.useSupportLibrary = true
And when setting your drawables directly from a layout.xml file use app:srcCompat=”@drawable/yourVector”
PRO TIP: Bulk drawable import in Android Studio. When a lot of assets are being added to an Android project, having to select the .svg files one by one and import them with the “vector asset” option in Android Studio can get pretty tedious. Recent versions of AS, however, have a new resource manager section with a great feature that went by almost unnoticed: bulk import for vector assets. Check it out!