azizbekian
azizbekian

Reputation: 62199

ResourcesCompat.getDrawable() vs AppCompatResources.getDrawable()

I'm a bit confused with these two APIs.

ResourcesCompat.getDrawable(Resources res, int id, Resources.Theme theme)

Return a drawable object associated with a particular resource ID and styled for the specified theme. Various types of objects will be returned depending on the underlying resource -- for example, a solid color, PNG image, scalable image, etc.

Prior to API level 21, the theme will not be applied and this method simply calls through to getDrawable(int).

AppCompatResources.getDrawable(Context context, int resId)

Return a drawable object associated with a particular resource ID.

This method supports inflation of vector and animated-vector resources on devices where platform support is not available.

Question

  1. What is the significant difference between these two classes (besides vector inflation)?
  2. Which one should I prefer to another and why?

Upvotes: 79

Views: 51410

Answers (5)

Tamtomo Abdi Negoro
Tamtomo Abdi Negoro

Reputation: 326

I wouldn't recommend using either two if you're planning to support older version of Android up to the most recent version. Have tried both while trying to support SDK 19 to 34, and had to revert to getResources().getDrawable approach as a solution. Bugs, inconsistencies and incompatibility issues were the ones I got

Upvotes: 0

Linh
Linh

Reputation: 60989

Here is my understanding after some testing:

ContextCompat.getDrawable(@NonNull Context context, @DrawableRes int resId)

ResourcesCompat.getDrawable(@NonNull Resources res, @DrawableRes int id, @Nullable Theme theme)

AppCompatResources.getDrawable(@NonNull Context context, @DrawableRes int resId)

VectorDrawableCompat.create(@NonNull Resources res, @DrawableRes int resId, @Nullable Theme theme

The first thing I see is VectorDrawableCompat and ResourcesCompat can specify a theme.

I) Without using

AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); in onCreated of the Application class

1) For vector images

  • API >= 21

  • ContextCompat works well

  • ResourcesCompat works well

  • AppCompatResources works well

  • VectorDrawableCompat works well

  • API < 21

  • ContextCompat crash

  • ResourcesCompat crash

  • AppCompatResources works well

  • VectorDrawableCompat works well

2) For normal image

  • In all API levels
  • ContextCompat works well
  • ResourcesCompat works well
  • AppCompatResources works well
  • VectorDrawableCompat crash

II) Using

AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); in onCreated of the Application class

1) For vector images

  • In all API levels
  • ContextCompat works well
  • ResourcesCompat works well
  • AppCompatResources works well
  • VectorDrawableCompat works well

2) For normal images

  • In all API levels
  • ContextCompat works well
  • ResourcesCompat works well
  • AppCompatResources works well
  • VectorDrawableCompat crash

Upvotes: 115

MingalevME
MingalevME

Reputation: 1985

API 19+ workaround

package com.example;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.view.View;

import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;

/**
 * https://stackoverflow.com/a/48237058/1046909
 */
public class AppDrawableCompat {

    public static Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {
        try {
            return AppCompatResources.getDrawable(context, resId);
        } catch (Resources.NotFoundException e1) {
            try {
                return ContextCompat.getDrawable(context, resId);
            } catch (Resources.NotFoundException e2) {
                return VectorDrawableCompat.create(context.getResources(), resId, context.getTheme());
            }
        }
    }

    @Nullable
    public static Drawable findDrawable (@NonNull Context context, @DrawableRes int resId) {
        try {
            return getDrawable(context, resId);
        } catch (Resources.NotFoundException e) {
            return null;
        }
    }

    public static void setViewBackgroundDrawable(@NonNull View view, @NonNull Context context, @DrawableRes int resId) {
        Drawable drawable = findDrawable(context, resId);
        if (drawable != null) {
            view.setBackground(drawable);
        }
    }
}

Using example (MainActivity#onCreate)

ImageView icon = findViewById(R.id.icon);
AppDrawableCompat.setViewBackgroundDrawable(icon, this, R.drawable.bg_icon);

Upvotes: 1

DeeV
DeeV

Reputation: 36045

Looking at the source code of the two methods, they seem very similar. If you don't have vectors, you could probably get away with using either one or the other.

ResourcesCompat.getDrawable() will call Resources#getDrawable(int, theme) on APIs 21 or greater. It also supports Android APIs 4+. It is no more than this:

public Drawable getDrawable(Resources res, int id, Theme theme)
        throws NotFoundException {
    final int version = Build.VERSION.SDK_INT;
    if (version >= 21) {
        return ResourcesCompatApi21.getDrawable(res, id, theme);
    } else {
        return res.getDrawable(id);
    }
}

Where-in ResourcesCompatApi21 merely calls res.getDrawable(id, theme). This means it will not allow vector drawables to be drawn if the device does not support vector drawables. It will, however, allow you to pass in a theme.

Meanwhile, the code change for AppCompatResources.getDrawable(Context context, int resId) eventually lands to this:

Drawable getDrawable(@NonNull Context context, @DrawableRes int resId, boolean failIfNotKnown) {
    checkVectorDrawableSetup(context);

    Drawable drawable = loadDrawableFromDelegates(context, resId);
    if (drawable == null) {
        drawable = createDrawableIfNeeded(context, resId);
    }
    if (drawable == null) {
        drawable = ContextCompat.getDrawable(context, resId);
    }

    if (drawable != null) {
        // Tint it if needed
        drawable = tintDrawable(context, resId, failIfNotKnown, drawable);
    }
    if (drawable != null) {
        // See if we need to 'fix' the drawable
        DrawableUtils.fixDrawable(drawable);
    }

    return drawable;
}

So this instance it will attempt to draw the resource if it can, otherwise it looks in the ContextCompat version to get the resource. Then it will even tint it if necessary. However, this method only supports API 7+.

So I guess to decide if you should use either,

  1. Do you have to support API 4, 5, or 6?

    • Yes: No choice but to use ResourcesCompat or ContextCompat.
    • No: Keep going to #2.
  2. Do you absolutely need to supply a custom Theme?

    • Yes: No choice but to use ResourcesCompat
    • No: Use AppCompatResources

Upvotes: 46

Eugen Pechanec
Eugen Pechanec

Reputation: 38243

ContextCompat

ResourcesCompat, ContextCompat and pretty much any class from support-v4 ending with Compat saves you from writing if (Build.VERSION.SDK_INT >= X) checks everywhere. That's it. For example instead of

final Drawable d;
if (Build.VERSION.SDK_INT < 21) {
    // Old method, drawables cannot contain theme references.
    d = context.getResources().getDrawable(R.drawable.some_image);
} else {
    // Drawables on API 21 can contain theme attribute references.
    // Context#getDrawable only exists since API 21.
    d = context.getDrawable(R.drawable.some_image);
}

you can write

final Drawable d = ContextCompat.getDrawable(context, R.drawable.some_image);

The limits described in comments apply, for example

// This line is effectively equivalent to the above.
ResourcesCompat.getDrawable(context.getResources(), R.drawable.some_image, context.getTheme());

does not actually apply the theme attributes before Lollipop (this is said in the documentation). But you don't have to write if checks and your code does not crash on old devices because you're not actually using new APIs there.

AppCompatResources

AppCompatResources on the other hand will actually help you bring new features to old platforms (support vectors, theme references in color state lists).

Which one should I prefer to another and why?

Use AppCompatResources to get consistent results with the rest of appcompat-v7 library. You'll get:

  • getColorStateList which can resolve colors with theme attribute references (such as android:alpha="?android:disabledAlpha"),
  • getDrawable which supports inflating vectors on all platforms and these vector drawables also understand theme attribute references (e.g. android:tint="?colorControlNormal"),
  • appcompat-v7 drawables and colors like checkmarks or radio buttons will have proper colors defined by supplied context theme,
  • if the above does not apply it falls back to ContextCompat anyway.

Upvotes: 28

Related Questions