ContentProvider in Android Libraries Considered Harmful

There is a trick that some Android libraries and SDKs use: embed a ContentProvider inside the library and perform the required initialization in its onCreate() method. Libraries that use this technique can auto-initialize the moment the host application starts, without requiring any manual initialization steps.

This approach works because the build system merges the manifests from the libraries with the app’s own manifest, and then Android instantiates all ContentProviders declared in the merged manifest before it invokes Application’s onCreate(). In other words, ContentProviders are created automatically before Application’s onCreate() is called, so libraries can leverage this property to auto-initialize.

However, even though this approach became relatively popular in the last couple of years, I think it’s an anti-pattern. In this post, I’ll present the arguments in support of this claim.

ContentProvider in Google’s Firebase SDK

Let’s start with a bit of history and understand how exactly this hack became so popular. I blame Firebase developers for that.

See, while this approach had always been available, it really took off after Firebase folks integrated ContentProvider into their SDK and then wrote this famous blog post about it. In the conclusion of this article they write:

Yes, it’s true, this particular application of ContentProvider seems really weird, since it’s not actually providing any content. And you have to provide implementations of all the other ContentProvider required methods by returning null. But, it turns out that this is the most reliable way to automatically initialize without requiring extra code. I think the convenience for developers using Firebase more than makes up for this strangeness of this use of a ContentProvider. Firebase is all about being easy to use, and there’s nothing easier than no code at all!

Well, they were absolutely right when they wrote that this way of abusing ContentProvider is “weird”. However, their claim that “convenience for developers makes up for this strangeness” turned out to be a big mistake.

Auto Initialization in Firebase SDK Backfired

Many Android developers are familiar with the aforementioned article from the Firebase blog. However, relatively few know that Firebase folks had to publish a follow-up post to address users’ criticism of this approach. In essence, many developers just told them either “we don’t want your SDK to auto-initialize” or “this hack doesn’t work for us”.

Among other things, this article also acknowledged the fact that “no code at all” assertion from the original post was… well, let’s call it a misleading statement. Since Firebase requires many configuration parameters to operate, to make the auto-init hack work, users had to add google-services.json file into their codebase and then rely on Gradle plugin to parse this file. That’s pretty much the same code you’d need to write to initialize Firebase manually. However, in terms of complexity and built time overhead, this auto-init scheme is much worse.

Complexity-wise, it’s enough to just search for google-services.json on StackOverflow to understand what a fiasco this approach is. Questions about this file earned hundreds of thousands (maybe even millions) of views. Now, many will jump and say “everything is explained in the docs, so that’s users’ problem”. There is a bit of truth there, but, as all library maintainers know, users’ problems are your problems in most cases. Therefore, if your library is all about “being easy to use”, but users constantly experience problems with its initialization, I’ve got bad news for you. In addition, I think that most developers find it simpler to copy-paste several strings into their Application’s onCreate() method, rather than dealing with standalone files and Gradle plugins.

The above complexity is inherent in this auto-init feature, but things become even more complex if you don’t actually need it. Then, you’ll have to search for ways to disable the auto-init feature and spend time verifying that it’s actually disabled.

In addition to excessive complexity, this hack also requires Gradle plugin to parse google-services.json. Even if this plugin works ideally (caches stuff, detects when everything is up-to-data, etc.), that’s still some additional processing that needs to be done every single time you build your app. Given how big of a problem build times can become, adding build tasks to avoid writing several lines of code is crazy in my opinion.

Legal Considerations

Ever since GDPR compliance became mandatory, it constantly bothers me. I don’t understand these rules well enough and I don’t want to spend much time on this stuff. What’s worse, it looks like some of my clients don’t understand GDPR either. So, extrapolating from my own experience, I’d estimate that many businesses don’t dedicate enough attention to GDPR compliance. And GDPR is just one regulation out of god knows how many out there.

Given I’m already worried about regulations, incorporating third-party code which starts before my own code doesn’t sound compelling. How can I be sure that these libraries don’t do something illegal before my app gets a chance to ask for user’s consent? Is this even a potential problem, or I’m just being silly? I honestly don’t know and I don’t have time to learn this topic properly. Therefore, I don’t want any auto-starting code in my projects. I’d rather write several lines of code to initialize all the required libraries manually after I get the overall user’s consent.

Question to readers who understand GDPR: in your estimation, can Android app breach GDPR by integrating Firebase and allowing it to auto-start? You can write your opinion in the comments below.

Development and Maintenance Challenges

In my estimation, the above reasons are more than sufficient to call “ContentProvider trick” an anti-pattern. However, there is more.

Let’s imagine that you wrote an Android library and decided to use this approach. Let’s also assume that the integration of ContentProvider was relatively straightforward because the initialization of your library is simple. You don’t have Gradle plugins, configuration files, etc. Instead, ContentProvider just initializes some default references and your users are good to go. Sounds great, isn’t it? Fast-forward several months. Now imagine that you need to either add a new customizable feature, or your users want to customize one of the existing ones. So, now you need to pass arguments into your SDK. What are your options?

The “simplest” solution is to ask your users to call a method at runtime and just pass that parameter. Alright, but what if your library has already been initialized with a default value? Now you need to make sure that this change is safe, at which point you might run into “one of the hard problems of computer science”: cache invalidation. Basically, you’ll need to make sure that this change at runtime doesn’t conflict with the already existing internal state. Obviously, everything about this flow must also be thread-safe.

However, if you absolutely need to account for this configuration parameter during library’s auto-initialization, things become more complex for your users. Now, you’ll either need to ask them to hack with their app’s manifest, or use even more problematic approach (e.g Firebase SDK).

If your library wouldn’t auto-initialize, you could simply ask for these parameters in library’s init method. Aside from the fact that this approach eliminates the state consistency problem (because there is no state yet), it also allows you to make this parameter mandatory. This property on its own can spare much support overhead in the future. In addition, this solution is way simpler to implement in terms of thread-safety (e.g. just use final fields).

Lastly, there is a high chance that you’ll need to allow your users to disable auto-init at some point in the future. If you’ll reach this point, then you’ll need to waste time implementing this feature and documenting it. Furthermore, you’ll also need to maintain both approaches going forward because you can’t just drop the auto-initilization without confusing your existing users. Therefore, even if you believe that auto-initialization is great, think for a moment whether sparing several lines of code justifies the risk of this outcome.

Performance Considerations

When you initialize a library in Application’s onCreate(), it’s just a method call. ContentProvider, on the other hand, is a full-blown Android component and its initialization happens on UI thread. Therefore, when a library uses ContentProvider auto-init hack, it forces additional performance overhead on all its users.

However, my main problem here is not the overhead of ContentProvider (which is relatively modest in most cases), but the fact that auto-initialization happens before host application’s code is executed. This means that it will be very challenging for users to profile their application’s startup time and measure the contribution of external libraries. On the other hand, when you initialize third-party libraries in Application’s onCreate(), it’s a piece of cake to profile this process (e.g. print timestamps into logcat). Furthermore, you can even set up monitoring for these metrics in production.

Therefore, in the context of performance, using ContentProvider to auto-init a library is a very bad approach.

Speaking about performance and ContentProvider, you might have heard that Google decided to promote ContentProvider hack into a standard pattern in the form of App Startup framework. The idea is to use single auto-initializing ContentProvider and allow for other initialization tasks to plug into its startup flow. This way, theoretically, we can reduce the number of ContentProviders that need to be started on apps’ startup.

While the idea behind App Startup framework might look legit on the first sight, it’s actually kind of crazy. Just think about this for a moment: instead of asking developers to write several lines of simple, explicit and clean code, Google now suggests that we should learn an entire framework and write quite a lot of complex code to achieve the same goal. So, we’ve made a full cycle with ContentProvider hack, starting with “no code” several years ago (which has always been a… misleading statement) and all the way to today’s needlessly complex framework.

Devil’s Advocate

Said all the above, let’s discuss some potentially valid reasons to use ContentProvider in your Android library.

First, if you want to use ContentProvider to share data with other applications on the device (i.e. use ContentProvider as it’s supposed to be used), then go ahead. I haven’t encountered such a use case for a library yet, but I guess in some cases that’s what you need.

Then, if I stretch my imagination, I can imagine that if you develop something like crash reporting SDK, you might want it to initialize before all other components in the host application, including other ContentProviders. In such a case, you might decide to employ this hack and also set the android:initOrder to some high value.

However, given the drawbacks described earlier in this post and the fact that ContentProvider is relatively uncommon component, I’ve got a better solution (potentially). Implement the standard init method to be called from Application’s onCreate(), and then add a note in the docs that applications that employ ContentProviders might want to implement their own initializing ContentProvider for your SDK. This adds some work on the side of interested users, but given the fact that absolute majority of projects don’t use ContentProviders, I don’t believe it’ll become an issue. In addition, this approach will give your users the control over when your SDK initializes, which is more important than sparing several lines of code.

Summary

So, these are my arguments against employing “ContentProvider hack” in Android libraries.

As a user, I’ve never wanted any library to auto-initialize and I spent a considerable amount of time struggling due to this property in some cases. In addition, I believe that long-term maintainability of the code improves when you initialize the libraries which require initialization explicitly, as opposed to them starting up implicitly just because you’ve got a line in your Gradle dependencies list. Lastly, when library authors think that writing several lines of code in one specific place is a problem for me, I find that patronizing.

Therefore, dear Android developers, I invite you to stay away of this approach. Let’s make this anti-pattern a history.

As usual, thank you for reading and if you’ve got anything to add, leave your comments below.

Check Out My Courses on Udemy

2 comments on "ContentProvider in Android Libraries Considered Harmful"

Leave a Comment