Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gradle for Android, AARs, and Conditional Dependencies

Short Form: What are some ways of organizing the code/POM for an AAR, such that apps using that AAR only have dependencies they actually need?

Long Form:

Suppose we have an app that depends upon an Android library project packaged as an AAR (L).

L contains a mix of classes, and any given app (like A) will only use a subset of those classes. For example:

  • L may contain Fragment implementations for native API Level 11 fragments, the backported fragments, and ActionBarSherlock-flavored fragments

  • L may contain Activity implementations for regular activities, FragmentActivity, ActionBarActivity, and ActionBarSherlock-flavored activities

  • L may raise events via LocalBroadcastManager, Square's Otto, and greenrobot's EventBus

  • And so on

These cases have two main commonalities, as I see it:

  1. Apps will usually only care about some of the classes. For example, an app that uses Otto will not care about code that references greenrobot's EventBus, or an app that uses ActionBarActivity will not care about ActionBarSherlock.

  2. If the AAR is an artifact in a repo, apps will not care about all possible upstream dependencies that is needed to build the AAR. For example, an app that is using native API Level 11 fragments will not need support-v4 or actionbarsherlock, even though the AAR itself needs them to build the AAR

If we were to go with JARs instead of AARs and dump dependency management, this is fairly straightforward. Building the JAR would have compile-time dependencies (e.g., support-v4). However, apps using that JAR could skip those dependencies, and so long as those apps do not use classes that truly need those dependencies, life is good.

However, I am having difficulty in seeing how to accomplish the same thing with AARs and Maven artifacts specified in a build.gradle file. If L has a dependencies block referencing the upstream dependencies, apps will in turn download those dependencies transitively when the apps depend upon L.

One solution that I am fairly sure will work is to split L into several libraries. For example, using the fragment scenario, we could have:

  • L1, which contains the implementation for the native API Level 11 version of fragments, plus any common code needed for other scenarios. This library would have no upstream dependencies.

  • L2, which contains the implementation that uses the Android Support package's backport of fragments. L2 would have dependencies on L1 and on support-v4.

  • L3, which contains the implementation that uses Sherlock-flavored fragments. L3 would have dependencies on L1 and actionbarsherlock.

Then, an app would choose whether to depend upon L1, L2, or L3, and therefore would only get the necessary upstream dependencies (if any).

My question is: is that the best solution? Or is there something else in the world of Gradle for Android, AARs, and Maven-style artifacts that would allow apps to depend upon a single L? I am concerned about possible combinatoric explosions of libraries to handle a varied mix of upstream dependencies. I am also concerned about oddball apps that actually do need multiple implementations and whether or not we can reliably specify the dependencies for those (e.g., an app depending on both L1 and L2, because that's what that app's author thinks that app needs).

I know that there are ways for an app to block exclude dependencies (see Joseph Earl's answer for the syntax), so an app could depend upon L but then block the actionbarsherlock upstream dependency if it is not needed. While that could work, for cases where I'm the author of L, I'd rather go the L1/L2/L3 approach, as that seems cleaner.

Any other suggestions?

like image 352
CommonsWare Avatar asked Jan 05 '14 19:01

CommonsWare


People also ask

Does Gradle handle transitive dependencies?

Transitive dependencyBy default, Gradle resolves transitive dependencies automatically. The version selection for transitive dependencies can be influenced by declaring dependency constraints.

How do you avoid transitive dependencies in Gradle?

When you specify a dependency in your build script, you can provide an exclude rule at the same time telling Gradle not to pull in the specified transitive dependency. For example, say we have a Gradle project that depends on Google's Guava library, or more specifically com.

What does Gradle use for dependency management?

At runtime, Gradle will locate the declared dependencies if needed for operating a specific task. The dependencies might need to be downloaded from a remote repository, retrieved from a local directory or requires another project to be built in a multi-project setting. This process is called dependency resolution.

What are Gradle dependencies?

Gradle build script defines a process to build projects; each project contains some dependencies and some publications. Dependencies refer to the things that supports in building your project, such as required JAR file from other projects and external JARs like JDBC JAR or Eh-cache JAR in the class path.


1 Answers

I'm not aware of any dependency management feature that would help with this.

Having a single libraries with many dependencies and the ability to remove unwanted dependencies could work but there are some issues that will come with this:

  • You have to rely on users of L to remove the right dependencies.
  • You'll have classes in the library that won't be easy to strip out with Proguard. Proguard will not remove anything that extends Fragment/Activity/etc, and L should provide a proguard rule files to not remove classes that extend the support Fragment/Activity/etc... This will make it difficult to remove unwanted classes.
  • Some implementation may have additional resources and right now we can't strip out unneeded resources.

I think splitting the library is the right thing to do, however this is going to be complicated to do until we have flavors in library projects. Once we have this I think it'll be much easier to handle. You wouldn't have a base L1 and L2/L3 extending it. Instead you'd have a single library that generate different variants, each with their own dependencies.

With flavor dimensions, you could handle support libs vs event libs combinations though you'd definitively get some explosions if you add more dimensions.

The advantages of a multi-variant library is that it's much easier to share code across a subset of variants without introducing even more sub libraries. You can also have bi-directional references between the main code and the flavors, which you couldn't have with L2/L3 depending on L1.

As for supporting Ant/Eclipse, this is definitively going to be complicated. I would ignore Ant definitively. If someone only cares about command line build they should already move to Gradle. As for Eclipse, well we'll have Gradle support at some point, but I understand if you can't wait.

like image 166
Xavier Ducrohet Avatar answered Sep 21 '22 01:09

Xavier Ducrohet