Post

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.

Min-API

Deprecated

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.

ContextCompat-vs-ResourcesCompat

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(...):

ContextCompatResourcesCompatAppCompatResources
Vector Drawable (API Level 21 & above)YesYesYes
Vector Drawable (API Level 20 & below)CrashCrashYes

static ColorStateList getColorStateList(...):

ContextCompatResourcesCompatAppCompatResources
Theme attributes in CSL (API Level 21 & above)YesYesYes
Theme attributes in CSL (API Level 20 & below)CrashCrashYes

 

FAQ’s

  • Why does the lint check recommend using ContextCompat and not AppCompatResources if ContextCompat 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 a setBackgroundTintList 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.

This post is licensed under CC BY 4.0 by the author.