• When “Compat” libraries won’t save you
  • Originally written by Danny Preussler
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Hoarfroster
  • Proofreader: Kimhooo, Greycodee

— and why you should avoid NewApi suppression warnings!

The Compat support library concept is probably one of the key aspects of Android’s dominance in the mobile space. Unlike iOS, Android users often can’t update their operating system after a new release because their phones don’t allow updates, and that’s the fragmentation of Android. But developers still want to compete with the latest features in their apps, and the solution is simple: Instead of adding new apis to the operating system, we can package them directly with your app by using the “backtracking” version that Google has provided us.

It all started with the ActionBar Sherlock project developed by Jake Wharton. The project was later adopted by Google and put into their support library. Later, this support library was mirrored as the AndroidX project under Jetpack.

Same, but different

On the face of it, not all Compat apis are built the same way. Some apis, such as Fragment’s, are built from complete code copies. You can either use the operating system in android. App. Fragments (actually has been abandoned), or use androidx. Fragments. App. Fragments. Neither of them shares any code with each other, nor do they have a common base class (which is why we have two versions of FragmentManager).

On the other hand, a class like AppCompatActivity simply extends the existing Activity class. AppCompatImageButton is still an ImageButton!

As you can see, sometimes these Compat classes just act as a bridge to add missing functionality, and sometimes they’re even the same.

Let’s look at another example!

One area that has changed a lot over time, though, is Android’s notification API. There was a time when every Google I/O conference announced a new API change.

Fortunately we have NotificationManagerCompat to save us!!!!!!

For example, when we need to get a NotificationChannelGroup:

val groups = notificationManagerCompat.notificationChannelGroups
Copy the code

We don’t need to worry about whether the channel group is supported by all operating system versions, because it will actually be handled in the Compat class:

public List<NotificationChannelGroup> getNotificationChannelGroups() {
    if (Build.VERSION.SDK_INT >= 26) {
        return mNotificationManager.getNotificationChannelGroups()
    }
    return Collections.emptyList()
}
Copy the code

If we were before API 26, we would just get an empty list, if not we would get the new channel group introduced in API 26.

You can find more complex in the NotificationManagerCompat code inspections.

But if you look carefully, NotificationManagerCompat will return to our actual API classes. The NotificationChannelGroup, listed in the sample code above, is not a duplicate version of Compat, but because it checks API availability, it is safe to use.

val groups = notificationManagerCompat.notificationChannelGroups
val channels = groups.flatMap {
    it.channels.filter { it.shouldShowLights() }
}
Copy the code

Here we only need those channel groups that are triggering lights, which is API 26 and above. Since we are using apI-level classes higher than the lowest SDK level, the compiler will warn us here:

The compiler doesn’t mind NotificationManagerCompat we use to achieve a goal.

We have several ways to solve this problem.

Adding the RequiresApi annotation to our method doesn’t make sense, because we’ll simply move the warning to the calling function. Surrounded with check seems to have been out of date, because the inspection has been completed by NotificationManagerCompat, as shown in the above.

The best option seems to be to suppress the warning.

@SuppressLint("NewApi")
private fun checkChannels(a) {
   val groups = notificationManagerCompat.notificationChannelGroups
   val channels = groups.flatMap {
        it.channels.filter { it.shouldShowLights() }
   }
   ...
}
Copy the code

New demands are coming

Suppose we get an additional requirement that we need to filter out the blocked groups. We can add a simple check to this:

@SuppressLint("NewApi")
private fun checkChannels(a) {
    val groups = notificationManager.notificationChannelGroups
    val channels = groups.filterNot { it.isBlocked }.flatMap {
        it.channels.filter { it.shouldShowLights()}
    }
    ...
}
Copy the code

Everything looks good, right?

You are done!

But we just introduced a crash!

The reason: isBlocked was introduced in API 28, and we didn’t check! Although we used the NotificationManagerCompat, but we still meet the problem on the API level!

And because we suppressed NewApi warnings, we didn’t get any warnings on this issue!

So when it comes to restraining warnings, we should be more careful!

The solution?

Since this problem can only be solved at the method level (not for a single statement), the best approach is to write a single-line method that meets our needs.

Thanks to extension functions, this can be done very easily:

@SuppressLint("NewApi") // SDK 26
fun NotificationChannelGroup.lightingChannels(a) = 
   channels.filterLightingOnes()

@SuppressLint("NewApi") // SDK 26
private fun List<NotificationChannel>.filterLightingOnes(a) = 
   filter { it.shouldShowLights() }
Copy the code

If we had used this approach in the above example, we would have been warned when we added isBlocked:

Sure, it’s a lot more work for us developers, but our users would love a crash-free app.

The Linter

The above example is not a Compat library Bug, but is suppressed and hidden. This can happen with many other apis as well.

  • Don’t fall into this trap!
  • Using Compat libraries can give us a false sense of security and trick us into believing we don’t have to think about these issues.

And again, try to avoid suppressing NewApi warnings!

Instead, we should use direct version checking such as:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
Copy the code

Unfortunately, Lint is not very smart here. It does not understand version checking for variations such as:

.filter { Build.VERSION.SDK_INT >= Build.VERSION_CODES.P }
Copy the code

For help?

Maybe some of you want to explore this a little bit more, with some custom Lint rules. Basically, we need something like this:

@CheckedUpTo(Build.VERSION_CODES.P)
Copy the code

This will do something similar internally to SuppressLint(“NewApi”), but will only be called for apis that do not require higher than version P.

For now, you can make existing Lint functions work for you. For example, add @requiresAPI (build.version_codes.p) to your own code to force you to deal with these issues.

Remember, these comments are also considered documentation for your code reader.

PS: The latest version of NotificationCompat Alpha will bring us compatible versions of NotificationChannel and NotificationChannelGroup. 🥳

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.