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:
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.
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?
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.
In your application project can exclude transitive dependencies in Gradle (e.g. exclude ActionBarSherlock being included if you know it won't be used), e.g.
dependencies {
compile('org.hibernate:hibernate:3.1') {
// Excluding a particular transitive dependency:
exclude module: 'cglib' //by artifact name
exclude group: 'org.jmock' //by group
exclude group: 'org.unwanted', module: 'iAmBuggy' //by both name and group
}
}
You can also ProGuard (and DexGuard) to compact and strip out unused code.
Gradle doesn't yet support marking dependencies as optional as Maven does (which could be used in the library project).
Well, the first thing I'd like to mention is Gradle configurations that are actually kinda reflection of Ivy configurations: a module you deploy to some repository can have different configurations and each configuration may have its own set of dependencies.
http://www.gradle.org/docs/current/userguide/dependency_management.html#ssub:multi_artifact_dependencies
Yet, Maven does not fit this case, since there are no configurations in Maven world. And artifact classifiers will not allow you to have different dependencies.
What we can do with Maven is remove compile time dependency declarations from POM or mark them with provided scope.
AFAIK ability to specify compile-time only dependencies will be available in future releases of Android Gradle plugin.
https://code.google.com/p/android/issues/detail?id=58626
Related
I would like to be able to have common test code in a library module of my android projects, so that the different apps in the project can use them.
The problem is, apps cannot import classes from <library>/src/androidTest/java, and if I move that at code into src\main, it would have to move dependencies from androidTestCompile to compile (more dependencies to the release build).
The only solution right now is to create a separate library to hold the shared test classes, however this has the downside of adding a new library to the project structure, which is not that big a deal, but I'd like to know nonetheless if there are better solutions.
I'd rather implement a Gradle hack at this point if any Gradle (Android plugin) wizards out there can help me find one.
Since I got no answers, I might as well answer my own question.
I ended up using the solution I already mentioned in my question:
Create a library with shared test classes (not actual test cases, but common code to be used in the final ones) under src/main and import it with androidTestCompile or androidTestImplementation for recent gradle versions.
Got a solution that doesn't involve adding a module? I won't accept my own answer since it doesn't exactly answer the question.
If you have code (test or otherwise) that can be reused across multiple modules, the appropriate thing to do is exactly what you've done: put it in it's own module and import it into the other modules.
The small overhead of creating a new module is giving you a lot of power. It allows you to manage its build without having to change every dependent module's build.
Any other option I've tried (creating a single module that contains all modules' tests, e.g.) ends up being a much bigger headache and a dependency nightmare.
I'm in the process of writing an instant app for others to learn how to write an instant app and hoping to get some ideas on how to best structure the app for dependencies.
Now reading the Android developer docs on project structure and referencing the diagram below:
I'm wondering with the new gradle 3.0 dependency configurations, what libraries should live in which modules?
Base Feature
I was thinking pretty much anything in the base feature module should be using the api gradle configuration since the base feature module essentially compiles down to an AAR library file. One question I might have for this module, if one was to use ROOM would this be the module to place it in?
Feature
Now in the feature modules, it is my understanding that everything should be utilizing the implementation gradle configuration since these modules should not leak there dependencies out to any other modules in order to truly make them independent from one another.
Just looking for some confirmation of my understanding and also any ideas to help with the project. Here is the github repo if you want to check out the code I got so far. It is really simple at the moment, but I was thinking about messing around with the Star Wars API using Retrofit.
Thanks for any help and gladly accept any contributions if you want to to try and make a pull request yourself for any other concepts in making an instant app that others should know.
Shared details in your question are correct. Consider some of the below suggestions which add to the points mentioned by TWL:
Adding certain libraries to specific feature module which should
be included in the feature module only, instead of being added in the
base APK.
For example, let's say you have an application that depends on
libraries X, Y, and Z. Initially, you may pack all the libraries in
the base module by placing all the dependencies in the base
gradle.build file. But if only the code in the feature module requires
library Z, it makes sense to move that dependency from the base module
to the feature module.This works as long as no other feature modules
depend on the same library. If multiple feature modules use the same
library it definitely makes sense to keep it in the base module.
Taking care of Transitive dependencies.
Transitive dependencies occur when the library your project relies
upon depends on another library, which in turn may depend on yet
another library. Sometimes those transitive dependencies may contain
unexpected surprises such as libraries you do not need at all (i.e. a
JSON processing library you never use in your code.)
I hope this adds some information to your query,
I'm wondering with the new gradle 3.0 dependency configurations, what libraries should live in which modules?
Some of these links can also be referred for additional data:
Android Instant Apps(best-practices)
AIA structure
As mentioned by keyboardsurfer, your dependency assumption is in the right direction.
Base is at the root and acts like a library shared by all the
non-base feature modules, so its shared dependencies should be set with
api so that modules that depend on it can also access them. (though, base doesn't have to act only like a library, it can
also be a feature APK itself)
Features, as an instant app, each one extends out to the end as its own APK, so there's no reason it should be leaking its dependencies to any other modules and therefore dependencies should be set with implementation here.
Within the Google Samples, the cookie-api and install-api are some samples that more clearly demonstrate the dependency configuration usage as how I explained above.
I am working on a project and so far in order to provide minimal integration effort for my consumers, I was forced to use all core Android framework APIs. That being said, I did not use any additional dependencies although there are alot of android library available out there to make my life easier. But recently I've been thinking about using Android annotation library. and in order to use it the docs ask me to include them into the project.
dependencies { compile 'com.android.support:support-annotations:24.2.0' }
From my knowledge, this will make my consumers forced to downland the libraries(they might not need) when they use my library. Can anyone please let me know if there is any better practice to achieve that not to include the library for my consumer but only my own project scope?
UPDATED:
I think I just found those two lines from the official docs.
If you use annotations in your own library module, the annotations are included as part of the Android Archive (AAR) artifact in XML format in the annotations.zip file. Adding the support-annotations dependency does not introduce a dependency for any downstream users of your library.
But I am still curious about general dependency issue and how to handle the dependencies if we are just a library.
In general, it is OK to make the library you depend on available to the user of your library.
If several libraries require a popular dependency, it's better to have the code included just once, if you want to reduce your apk size.
Also, if the user of your library doesn't want to include the transitive dependency, an exclude option can be added to the dependency declaration.
Other case is when you hapen to provide several libraries, where you can also ensure that the transitive dependency has the same version among your libraries
In other words, I would let the users the power to decide if they want the transitive dependency or not.
I was wondering what is the recommended way of including library projects in an Android Studios gradle like this:
First way:
compile 'de.greenrobot:greendao:2.1.0'
Second way:
compile files('libs/greendao-2.0.0.jar')
and what are the pros and cons.
In the first case gradle is completely handling the process of dependency management, i.e. downloading the jar and including it in your project.
In the second case you have to manually download the jar and include it in the libs folder.
The simpler and preferred way is the first.
First way: compile 'de.greenrobot:greendao:2.1.0'
PRO:
Easy, quick to update
CON:
Internet connection required when updating your gradle file
Second way: compile files('libs/greendao-2.0.0.jar')
PRO:
You can make changes to the library and those won't be overwritten.
Like Qian Sijianhao said, it's quicker to build.
CON:
More work to set up, update
In most cases I think you want to go with the first way.
Trust me, the second way will save lots of your building time.
By the way , time is money.
In the first way, i.e:
compile 'de.greenrobot:greendao:2.1.0'
compile 'com.library.sample:library:x.x.x'
Gradle will finding these dependencies, and making them available in your build. If your dependencies have a dependencies, gradle will also finding them and include it for the project. So you don't need to manually add all the dependencies.
Quoting from Gradle documentation:
7.1. What is dependency management?
Very roughly, dependency management is made up of two pieces. Firstly,
Gradle needs to know about the things that your project needs to build
or run, in order to find them. We call these incoming files the
dependencies of the project. Secondly, Gradle needs to build and
upload the things that your project produces. We call these outgoing
files the publications of the project. Let's look at these two pieces
in more detail:
Most projects are not completely self-contained. They need files built
by other projects in order to be compiled or tested and so on. For
example, in order to use Hibernate in my project, I need to include
some Hibernate jars in the classpath when I compile my source. To run
my tests, I might also need to include some additional jars in the
test classpath, such as a particular JDBC driver or the Ehcache jars.
These incoming files form the dependencies of the project. Gradle
allows you to tell it what the dependencies of your project are, so
that it can take care of finding these dependencies, and making them
available in your build. The dependencies might need to be downloaded
from a remote Maven or Ivy repository, or located in a local
directory, or may need to be built by another project in the same
multi-project build. We call this process dependency resolution.
Note that this feature provides a major advantage over Ant. With Ant,
you only have the ability to specify absolute or relative paths to
specific jars to load. With Gradle, you simply declare the “names” of
your dependencies, and other layers determine where to get those
dependencies from. You can get similar behavior from Ant by adding
Apache Ivy, but Gradle does it better.
Often, the dependencies of a project will themselves have
dependencies. For example, Hibernate core requires several other
libraries to be present on the classpath with it runs. So, when Gradle
runs the tests for your project, it also needs to find these
dependencies and make them available. We call these transitive
dependencies.
Gradle will store the downloaded library to your USER_HOME/.gradle. In Linux, it will store it in /home/user/.gradle/caches/modules-2/. in Mac it will store it in ~/.gradle/caches/modules-2/.
By the way, if you have used the library, you can set Android Studio to use a local cache of the library.
In the second way, you need to manually add the library for your project. And you also need to include all the remaining dependencies of the library. This is so error prone.
We have developed an android library (aar) using Java and it depends on android support library v4. In fact, we have extended ViewPager (android.support.v4.view.ViewPager) class and consumed it in our library’s GUI.
We have applied proguard on the library and it works fine with our test apps but we have a customer that has developed its android app in native C++ and they are going to integrate our component into their app and there is an issue on build.
They receive com.android.dex.DexIndexOverflowException on build which is a sign of having more than almost 64K methods that are allowed in a single dex.
They asked us to use fewer or smaller dependencies as they have to include our component dependencies into their build setting and one of their suggestions is that we should extract ViewPager out of android support source and put it in our component source code.
Now the questions are
If we extract ViewPager out of android source and add it to our
library source code then will it reduce the amount of methods to
prevent mentioned exception? Is it a good practice to do that?
What is the best way to resolve this issue?
Thanks
If we extract ViewPager out of android source and add it to our library source code then will it reduce the amount of methods to prevent mentioned exception?
Probably not, at least for release builds. Your customer should have configured ProGuard, which will identify and remove unnecessary code pulled in via libraries.
Is it a good practice to do that?
No. Your customer should know better than that.
What is the best way to resolve this issue?
Mostly, it is not your problem. It is your customer's problem. Your customer is blaming you, but there is no evidence in your question that your library is a significant source of the customer's DEX method references.
Your customer should be configuring ProGuard, and your customer should use the APK Analyzer in Android Studio 2.2+ to see where their method references come from.
You, in your library, could:
Try to use more focused dependencies than support-v4. ViewPager itself is in the support-core-ui artifact. However, if you are using FragmentPagerAdapter or FragmentStatePagerAdapter, in addition you will need either support-fragment (if you are using the v4 edition of those classes) or support-v13 (if you are using the v13 edition of those classes).
Use the APK Analyzer on some demo project that you create that uses your library, so you can see how many DEX method references that your library uses, to see if you are really a significant source of such references.
Suggest ProGuard settings for your customer that will keep required classes of yours, to help them use ProGuard effectively with their app.
You can set jumboMode in your build.gradlefile like this:
dexOptions {
jumboMode true
}
This option will allow you to have 32it wide string references in your .dex file. Thus you can have 2^32 referenced strings in your project.
Hope it helps.
Available options are :
Yes, you can extract ViewPager from support library. ViewPager import some files from support library that are also required to be extracted. This will reduce method count with significant difference.
You can also use exclude parameter in dependencies in build.gradle file.
compile ('com.android.support:recyclerview-v7:+') {
exclude module: 'support-v4'
}