Search code examples
androidandroid-resourcesandroid-theme

What's the use of `theme` in `context.getColor` ?


Background

Starting from API 23, the small method of getting a color getColor (by just giving it the resource id) of the resources is deprecated:

enter image description here

Instead, we are told to use a method that includes a Theme parameter too:

This method was deprecated in API level 23. Use getColor(int, Theme) instead.

https://developer.android.com/reference/android/content/res/Resources.html#getColor(int, android.content.res.Resources.Theme)

The problem

The docs don't say much about the theme:

enter image description here

https://developer.android.com/reference/android/content/res/Resources.html#getColor(int, android.content.res.Resources.Theme)

What I've found

Searching over the Internet, all I could find is that we can use the support library to get the color :

ContextCompat.getColor(context, R.color.color_name);

This is a bit weird, because under the hood, it doesn't seem like it's doing anything with themes. Here's its code:

@ColorInt
    public static final int getColor(@NonNull Context context, @ColorRes int id) {
        if (Build.VERSION.SDK_INT >= 23) {
            return context.getColor(id);
        } else {
            return context.getResources().getColor(id);
        }
    }

Looking at Context.getColor, I can see this:

enter image description here

So it seems to me it's using the theme of the activity?

The questions

  1. What's the purpose of the 'theme' parameter?

  2. How is it used? Is there any sample/tutorial/article about it?

  3. Why was the function deprecated anyway? It still seems safe to use for me...

  4. What's the use of the support library function? How different is it from using the deprecated function?


Solution

  • tl;dr

    • If your min SDK is 23 you don't need the compat API for obtaining colors.
    • If you don't use theme attribute references in colors and don't plan change, use Context.getResources().getColor(int) or getColorStateList(int) as you're used to. It's marked deprecated but functionally it's OK.
    • If you want to use theme attribute references in colors on API 23+ only, use ContextCompat.getColorStateList(Context, int).
    • If you use AppCompat and want to be able to use theme attribute references on all versions of Android, use AppCompatResources.getColorStateList(Context, int).

    Starting API 23 ColorStateList can contain theme attribute references, the same thing that's allowed in Drawable since API 21.

    Theme attributes are mostly used in color state lists obtained by getColorStateList method as opposed to getColor so I'll use that from now on. Most of what's mentioned applies to both variants.

    Example:

    res/color/my_csl.xml

    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_enabled="false" android:color="?attr/colorControlHighlight" android:alpha="?android:disabledAlpha"/>
        <item android:color="?attr/colorControlHighlight"/>
    </selector>
    

    What's the purpose of the 'theme' parameter?

    Theme is required to resolve theme attributes. Resources itself is not aware of any theme. Theme is provided by a context.

    context.getColorStateList(R.color.my_csl);
    

    is essentially a shortcut to

    context.getResources().getColorStateList(R.color.my_csl, context.getTheme());
    

    Both of these methods are part of public API so both of them were backported:

    ContextCompat.getColorStateList(context, R.color.my_csl);
    ResourcesCompat.getColorStateList(context.getResources(), R.color.my_csl, context.getTheme());
    

    How is it used?

    Typically it's as simple as this:

    final int myColor = ContextCompat.getColorStateList(context, R.color.my_csl);
    

    If you use AppCompat you might be better off with another option:

    final int myColor = AppCompatResources.getColorStateList(context, R.color.my_csl);
    

    Here's a comparison between these two options:

    • ContextCompat.getColorStateList

      • Requires only support-compat support library
      • Backports only API - it will crash when trying to resolve theme attributes below API 23
    • AppCompatResources.getColorStateList

      • Requires full appcompat-v7 support library
      • Backports functionality - it will respect context theme even below API 23

    ResourcesCompat.getColorStateList(int, Theme) is API (not functional) backport of Resources.getColorStateList(int, Theme) which is used internally by Context.getColorStateList(int) on API 23+. In everyday use you won't be needing this method.

    Why was the function deprecated anyway? It still seems safe to use for me...

    The method was deprecated to migrate developers from the version that's not aware of themes to the theme-aware version of the method.

    The theme-unaware method is still perfectly safe to use as long as you use it to obtain colors without theme attribute references, for example:

    res/values/colors.xml

    <resources>
        <color name="my_cint">#f00</color>
    </resources>
    

    can be safely obtained by

    context.getResources().getColor(R.color.my_cint);
    

    What's the use of the support library function?

    ContextCompat.getColorStateList(context, R.color.my_csl);
    

    is literally this

    public static final ColorStateList getColorStateList(Context context, @ColorRes int id) {
        if (Build.VERSION.SDK_INT >= 23) {
            return context.getColorStateList(id);
        } else {
            return context.getResources().getColorStateList(id);
        }
    }
    

    The method exists so you don't have to write if-else everywhere. That's it.

    On API 23+ it can use the context theme. Below API 23 it falls back to the old version that cannot resolve theme attributes.

    ResourcesCompat.getColorStateList(int, Theme) looks and works similarly, it ignores theme attributes (crashes when you use them) below API 23.

    AppCompatResources.getColorStateList(Context, int) will not crash and correctly resolve theme attributes on all versions of Android.

    What about ResourcesCompat#getColorStateList?

    You won't typically need this method.

    It's a layer of abstraction. It's telling you that to resolve a color you don't need any god-object, any Context. Resources is just enough to resolve a color. BUT for Resources to be able to resolve theme attributes you need to provide a Theme. The fact that a Theme can be obtained from a Context is of no concern to Resources. Resources doesn't disclose or care that it itself came from some Context.getResources().

    Can you [...] update about getColor too?

    getColor will resolve

    • <color> resource as color integer
    • <selector> color state list resource as color integer of its default color

    getColorStateList will resolve

    • <color> resource as ColorStateList with one color
    • <selector> CSL resource as ColorStateList with specified colors

    Use the one that's required by consumer API.

    There is no AppCompatResources.getColor(Context, int) because there's little point in a color resource being defined as a theme attribute reference. If you ever need this AppCompatResources.getColorStateList(Context, int).getDefaultColor() is the functional equivalent.

    ContextCompat.getColor(Context, int) and ResourcesCompat.getColor(Resources, int, Theme) exist because they mirror the framework API. Their practical usefulness, as described above, is debatable.

    I recommend reading the discussion below to grasp some of the elusive subtle differences.