AppCompat 1.2 Lint Checks - AppCompatResources or ContextCompat or ResourcesCompat
AppCompat 1.2 release came with a couple of new lint rules which suggest using either AppCompatResources
or ContextCompat
or ResourcesCompat
depending on the API you were originally consuming. But what is the difference between the common methods in these three classes?
What are Compat Classes?
The AndroidX/Jetpack libraries provide a bunch of compat
classes to help developers work with deprecated API’s and avoid having to write if (Build.VERSION.SDK_INT >= X)
everywhere. A few compat
classes even backport newer platform capabilities to older platforms (more on that later).
A general rule of thumb that has served me extremely well over the last few years is to look for a Compat
class whenever I encounter a deprecated API method or try to use a method that is only available in the recent versions of the platform framework API.
The name of the compat
class is usually the same as the class in which the original API method exists. For the above example, setTextAppearance
is a part of the TextView
class. Therefore, the equivalent compat
class which might (because not all such methods have a compat
equivalent) have this method is the TextViewCompat class. Note that it is important to look at the class which contains the API method and not mistake it for the class on which we are operating. For example, the setBackgroundDrawable
method is deprecated and we could be using it to set a background on a Button
, but the method actually belongs to the View class and hence it’s equivalent would be found in the ViewCompat` class.
Now that we have a primer on how to find compat
equivalents, let’s take a look at the methods inside ContextCompat
and ResourcesCompat
. For this post, we’ll only look at the methods which have the same name in both classes.
From the above table, we can see that there is no real difference between the ContextCompat
methods vs the ones in ResourceCompat
. You would use ContextCompat
methods if you had access to Context
but if you only had access to the Resource
object, then you would use the ones inside ResourceCompat
. At the end of the day, the result from both of them will be the same.
At this juncture, it is important to remember that platform framework support for Vector Drawables and tinting was added in API level 21. As the ContextCompat
and ResourcesCompat
methods simply delegate to platform framework, these methods will not be able to load Vector Drawables on older platforms as the older platforms don’t have support for them.
Enter AppCompat
Now the examples that were detailed above mostly save developers from having to write if (Build.VERSION.SDK_INT >= X)
but appcompat
is different. AppCompat
actually works on backporting the new UI toolkit functionality that was introduced in the later platform versions, such as Vector Drawables, tint, theme attributes in ColorStateLists, etc. So if we want to load VectorDrawables on older platforms, we would need to use the AppCompat library. The hook into the AppCompat library (through Java/Kotlin code) is through AppCompatResources. Now’s let’s compare the methods inside AppCompatResources
with those inside ContextCompat
and ResourcesCompat
static Drawable getDrawable(...)
:
ContextCompat | ResourcesCompat | AppCompatResources | |
Vector Drawable (API Level 21 & above) | Yes | Yes | Yes |
Vector Drawable (API Level 20 & below) | Crash | Crash | Yes |
static ColorStateList getColorStateList(...)
:
ContextCompat | ResourcesCompat | AppCompatResources | |
Theme attributes in CSL (API Level 21 & above) | Yes | Yes | Yes |
Theme attributes in CSL (API Level 20 & below) | Crash | Crash | Yes |
FAQ’s
- Why does the lint check recommend using
ContextCompat
and notAppCompatResources
ifContextCompat
can crash on Pre API Level 21 devices?
As of AppCompat 1.2, the lint check recommends using ContextCompat
. This is a known bug and will be resolved soon. Future versions of AppCompat will instead recommend using AppCompatResources
to load drawables and Color State Lists.
- Should we use
AppCompatResource
if we only support API Level 21 and above?
Yes, we should. VectorDrawableCompat
is an unbundled implementation of Vector Drawables outside of the framework and can be updated without needing platform framework updates. This allows developers to work around any bugs that are there in the framework implementations of Vector Drawables (which it did have).
ViewCompat
has asetBackgroundTintList
method which applies tints to views even on pre API Level 21 devices. How does that work?
The ability to tint backgrounds and images was added in API Level 21. As explained earlier in the post, the ViewCompat class does not backport this functionality to older API platforms. That is still the job of AppCompat
. ViewCompat
uses a nifty trick where on the older platform (pre-API level 21), it checks if the view implements TintableBackgroundView
. If it does, then the ViewCompat
delegates to it to perform the tinting. Most of the widgets in AppCompat
implements the TintableBackgroundView
interface and offer tinting functionality, effectively backporting it to older platforms.
Since we use the AppCompat theme (or MDC theme) in our apps, we end up using the AppCompat widgets and hence get the tinting capability through ViewCompat
.
Note: The same mechanism applies for
ImageCompat
- What about
app:srcCompat
,app:drawableLeftCompat
,app:tint
, etc.?
Drawables passed through the above XML attributes are inflated by AppCompatResource
. Tint passed through the above XML attributes are also applied by the app compat widgets.
Note: Just like the framework had bugs with Vector Drawables, the framework, unfortunately, had bugs with tinting as well. Hence it is always recommended to use
app:srcCompat
,app:tint
, etc. even when we are only targeting API Level 21 and above.
- What is the future with Jetpack Compose?
Jetpack Compose is a full-fledged replacement for the UI toolkit. Once it’s stable, I believe we would no longer need the AppCompat Widgets, AppCompatResources, ContextCompat and ResourcesCompat. Till then (and even in the future when maintaining existing apps), it would serve us well to know when to use which method and how things work under the hood.