Resolving Android theme colours programmatically

The Android styling and theming system can be an enigmatic beast at the best of times. In some of the SDKs at Zendesk, we need to resolve theme attribute colours at runtime, and today I learned that there’s a big difference depending on whether you specify a colour as a reference or as a literal value.

The textColorPrimary attribute is often specified in your Application’s theme as a way of overriding the default text colour. The behaviour that I’ll describe is not limited to this one attribute, it applies to all attributes that can describe a colour.

The attribute can be specified as a colour reference (text_color_primary):

<style name="AppTheme" parent="ZendeskSdkTheme.Light.DarkActionBar">
    <item name="android:textColorPrimary">@color/text_color_primary</item>
    ...

Or as a literal colour value (#434343):

<style name="AppTheme" parent="ZendeskSdkTheme.Light.DarkActionBar">
    <item name="android:textColorPrimary">#434343</item>
    ...

Our initial approach for resolving the colour programmatically was something like this:

(calling code)

int textColorPrimaryColor =
        UiUtils.themeAttributeToColor(
            R.attr.textColorPrimary,
            context,
            R.color.fallback_text_color);

(method to resolve the colour)

public static int themeAttributeToColor(int themeAttributeId,
                                        Context context,
                                        int fallbackColorId) {
    TypedValue outValue = new TypedValue();
    Resources.Theme theme = context.getTheme();
    boolean wasResolved =
            theme.resolveAttribute(
                    themeAttributeId, outValue, true);
    if (wasResolved) {
        return ContextCompat.getColor(
                    context, outValue.resourceId);
    } else {
        // fallback colour handling
        return ...
    }
}

The code above checks that the colour referenced by the R.attr.textColorPrimary attribute can be resolved in the app’s theme. If the colour was resolved, we can then use ContextCompat.getColor to get the actual colour value.

This all worked very well, apart from the case where a literal colour was specified. In this case outValue.resourceId is zero, and a relatively cryptic Resources.NotFoundException is thrown. Examining outValue shows that outValue.resource is zero, but outValue.data is populated with the colour’s value. This means that we can adapt the method above to something like this:

public static int themeAttributeToColor(int themeAttributeId,
                                        Context context,
                                        int fallbackColorId) {
    TypedValue outValue = new TypedValue();
    Resources.Theme theme = context.getTheme();
    boolean wasResolved =
            theme.resolveAttribute(
                    themeAttributeId, outValue, true);
    if (wasResolved) {
        return outValue.resourceId == 0
        ? outValue.data
        : ContextCompat.getColor(
                    context, outValue.resourceId);
    } else {
        // fallback colour handling
        return ...
    }
}

There are further safety checks that can be added to the code, such as checking the type of the TypedValue. The type in this example was TYPE_INT_COLOR_RGB8, which is something that we should check. I’d also recommend returning a fallback colour in case ContextCompat.getColor fails.

I hope that this will help! If you have any suggestions about how to resolve theme colour attributes, then please let me know in the comments.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s