Exploring the Spilt in the support-v4 Library

Over a year ago the support library development team decided to split the monolithic support-v4 library up into several sub-libraries. This update, in version 24.2.0, meant that an app no longer needed to depend on the entire support-v4 library and could instead depend on only those sub libraries that it needed.

Until now I have yet to implement this update on any of my work or personal projects. This week as part of a general dependency audit, we decided to split up our support-v4 dependencies.

In determining which “new” support libraries our project relies on, I could have inspected every import android.support.v4.app.xyz statement in our app. This would be a tedious process. Instead, I decided to remove the support-v4 dependency entirely, build our app, and let Gradle complain about missing dependencies. I would then add in each new support library as needed.

However when I removed the support-v4 dependency and built our project it compiled fine. I had a momentary brain fart. “We’re using fragments. We are using the support Fragment right?” Sure enough, our project had plenty of dependencies on support.v4 package but compiled fine.

I then realized that the support-v4 dependency must be supplied as a transitive dependency. Sure enough, running ./gradlew app:dependencies revealed that quite a few of our dependencies were themselves depending on support-v4

+--- com.android.support:transition:25.3.1
|    ...
|    \--- com.android.support:support-v4:25.3.1 (*)

+--- com.android.databinding:library:1.3.1
|    +--- com.android.support:support-v4:21.0.3 -> 25.3.1

+--- com.google.firebase:firebase-core:10.2.1
|    \--- com.google.firebase:firebase-analytics:10.2.1
|         +--- com.google.android.gms:play-services-basement:10.2.1
|         |    \--- com.android.support:support-v4:24.0.0 -> 25.3.1 (*)

+--- com.android.support:appcompat-v7:25.3.1
|    ...
|    +--- com.android.support:support-v4:25.3.1 (*)

+--- com.facebook.android:facebook-android-sdk:4.20.0
|    +--- com.android.support:support-v4:25.0.0 -> 25.3.1 (*)

+--- com.zendesk:sdk:
|    ...
|    +--- com.android.support:support-v4:25.1.0 -> 25.3.1 (*)
|    ...
|    +--- com.android.support:design:25.1.0
|    |    +--- com.android.support:support-v4:25.1.0 -> 25.3.1 (*)

At this point it occurred to me that unless our dependencies fixed their support-v4 dependencies, there wasn’t much use in fixing things ourselves. The problem with this thinking, though, is that it can lead to a Mexican standoff: each library pointing at some other library waiting on them to upgrade. This was already happening with our Zendesk dependency**.

The solution though would be to prepare our app and politely pressure our dependencies to upgrade their own dependencies. After all, the more people “waiting” for a particular library to update, the more pressure on that library author to cleanup their dependencies.

The easiest solution would be to depend on all of the new libraries (whether they were top level dependencies or not) to ensure every library is using the same version number. We could then remove individual libraries as our dependencies defined only those that they needed.

implementation com.android.support:support-compat:...
implementation com.android.support:support-core-utils:...
implementation com.android.support:support-core-ui:...
implementation com.android.support:support-media-compat:...
implementation com.android.support:support-fragment:...

It’s at this point that I read the entire documentation (RTFM, stupid) and saw that support-fragments depends on the remaining: support-compat, support-core-utils, support-core-ui and support-media-compat. Well crap. The main reason we use support-v4 is for support Fragments. So if support-fragment depends on all of the other new support libs, what’s the point of being explicit about our dependencies?

Then the question came to me: If we are relying on support-fragment to provide other things like support-compat, what’s wrong with relying on appcompat-v7 to provide us support-fragments in the first place?

There is some good discussion about relying on transitive dependencies on StackExchange. In the case of appcompat-v7 and support-fragment though, I think it’s not only acceptable to rely on transitive dependency, it should be encouraged for two reasons:

  1. They are maintained by the same authors.
  2. The authors “provide” these transitive dependencies explicitly.

I think the first point is pretty self explanatory. The appcompat-v7 lib stays in lock step version number with support-fragment. You don’t have to worry about them getting out of sync and needing to force support-fragment library to latest version by including it as a top-level dependency.

The second point is a bit more subtle. With the inclusion of the new Java Library plugin configurations the support library authors are forced to decide whether or not to expose their dependencies to others. They have chosen to use the api configuration. If they wanted to limit exposure of those dependencies, they could have chosen the implementation configuration. I believe this is neither an accident, nor is it “dependency leaking”. Instead I choose to believe that they are purposefully providing us with access to support-fragments (among others).

So if the solution for us is to remove the support-v4 dependency and keep the appcompat-v7 dependency, what was all of this exploring for? Hopefully I can distill all of this thinking into a quick guide.

But wait, what about Proguard?

Yes, using Proguard can solve the support-v4 “monolith” problem. But I don’t think Proguard should remove your obligation to write clean code and remove useless code. I think there is already too much bad library design that hides behind Proguard.

Decisions, decisions

If you are early in app development:

  • If you need code included in appcompat-v7, then use it. You don’t need to add support-v4 or any of the new libraries.
  • If you don’t need anything from appcompat-v7 except one of its dependencies, use that dependency directly, even if your minSdk is 7+.
  • If your minSdk is 4+ use support-fragment. You are using Fragments after all, right?
  • If you are building an app with minSdk 4+ and you are somehow not using Fragments, then use only those new libraries you need: support-compat, support-core-utils, support-core-ui or support-media-compat

If you are building a library:

  • Declare all code that you use as top-level dependencies. For example, if you use code from appcompat-v7 AND support Fragments, then you should declare dependencies on both appcompat-v7 and support-fragment. Unlike app developers, you are in danger of having your dependency versions changed by your users. Even though you declare a dependency on appcompat-v7 version X, there is nothing stopping your users from “swapping” to version Y by declaring a top level dependency on version Y themselves. If a later version of appcompat-v7 removed the support-fragment dependency (however unlikely), your library would no longer work for that user.

If you have an existing app:

  • If you are already running Proguard, then it’s not worth your time to change anything. Otherwise…
  • If you already depend on appcompat-v7, then use it. You can remove any dependency on support-v4, or the new libraries if you’ve already switched.
  • If you don’t need anything from appcompat-v7 except one of its dependencies, remove the appcompat-v7 dependency and add dependencies for one or more of the new libraries, even if your minSdk is 7+.
  • If your minSdk is 4+ and you use support fragments, you can switch from support-v4 to support-fragment, but you won’t see any improvement.
  • If your minSdk is 4+ and you are somehow not using Fragments, then use only those new libraries you need. You can remove support-v4 and add a subset of support-compat, support-core-utils, support-core-ui or support-media-compat.

This is where things get tricky. For any transitive dependencies that don’t match the version number you are using, you’ll need to add those dependencies as top-level dependencies as well. Otherwise you risk the “All com.android.support libraries must use the exact same version specification” error. For example if you depend directly on support-compat:26.0.0 and library X, which in-turn depends on support-v4:25.0.0, you will need to add a top-level dependency for support-fragment:26.0.0.

However, if library X depends on support-v4:24.1.1 or older you will have to maintain your dependency on support-v4 with support-v4:26.0.0. You won’t be able to depend on support-fragment:26.0.0 because versions of support-v4 24.1.1 and older is NOT just a collection of the new support libraries. Overriding with “support-fragment:26.0.0will not overridesupport-v4:24.1.1` version.

I hope this overly-complex dive into support library dependencies keeps you from being stressed about the importance of using the new sub-divided support libraries.

**To be fair to Zendesk, they reached out to me right away with an update.