Gradle Implementation VS API Configuration

Gradle Implementation vs API configuration

Gradle compile keyword was deprecated in favor of the api and implementation keywords to configure dependencies.

Using api is the equivalent of using the deprecated compile, so if you replace all compile with api everything will works as always.

To understand the implementation keyword consider the following example.

EXAMPLE

Suppose you have a library called MyLibrary that internally uses another library called InternalLibrary. Something like this:

// 'InternalLibrary' module
public class InternalLibrary {
public static String giveMeAString(){
return "hello";
}
}
// 'MyLibrary' module
public class MyLibrary {
public String myString(){
return InternalLibrary.giveMeAString();
}
}

Suppose the MyLibrary build.gradle uses api configuration in dependencies{} like this:

dependencies {
api project(':InternalLibrary')
}

You want to use MyLibrary in your code so in your app's build.gradle you add this dependency:

dependencies {
implementation project(':MyLibrary')
}

Using the api configuration (or deprecated compile) you can access InternalLibrary in your application code:

// Access 'MyLibrary' (granted)
MyLibrary myLib = new MyLibrary();
System.out.println(myLib.myString());

// Can ALSO access the internal library too (but you shouldn't)
System.out.println(InternalLibrary.giveMeAString());

In this way the module MyLibrary is potentially "leaking" the internal implementation of something. You shouldn't (be able to) use that because it's not directly imported by you.

The implementation configuration was introduced to prevent this.
So now if you use implementation instead of api in MyLibrary:

dependencies {
implementation project(':InternalLibrary')
}

you won't be able to call InternalLibrary.giveMeAString() in your app code anymore.

This sort of boxing strategy allows Android Gradle plugin to know that if you edit something in InternalLibrary, it must only trigger the recompilation of MyLibrary and not the recompilation of your entire app, because you don't have access to InternalLibrary.

When you have a lot of nested dependencies this mechanism can speed up the build a lot. (Watch the video linked at the end for a full understanding of this)

CONCLUSIONS

  • When you switch to the new Android Gradle plugin 3.X.X, you should replace all your compile with the implementation keyword *(1). Then try to compile and test your app. If everything it's ok leave the code as is, if you have problems you probably have something wrong with your dependencies or you used something that now is private and not more accessible. *Suggestion by Android Gradle plugin engineer Jerome Dochez (1))

  • If you are a library mantainer you should use api for every dependency which is needed for the public API of your library, while use implementation for test dependencies or dependencies which must not be used by the final users.

Useful article Showcasing the difference between implementation and api

REFERENCES
(This is the same video splitted up for time saving)

Google I/O 2017 - How speed up Gradle builds (FULL VIDEO)

Google I/O 2017 - How speed up Gradle builds (NEW GRADLE PLUGIN 3.0.0 PART ONLY)

Google I/O 2017 - How speed up Gradle builds (reference to 1*)

Android documentation

Gradle dependency configuration : implementation vs api vs runtimeonly vs compileonly

Please refer the link : Android Studio 3.0 New Gradle Configuration available at android developers official site.

Based on description mentioned in above link:

  • implementation: When your module configures an implementation dependency, it's letting Gradle know that the module does not want to
    leak the dependency to other modules at compile time. That is, the
    dependency is available to other modules only at runtime. Using this
    dependency configuration instead of api or compile can result in
    significant build time improvements because it reduces the amount of
    projects that the build system needs to recompile. For example, if an
    implementation dependency changes its API, Gradle recompiles only that
    dependency and the modules that directly depend on it. Most app and
    test modules should use this configuration.
  • api: When a module includes an api dependency, it's letting Gradle know that the module wants to transitively export that
    dependency to other modules, so that it's available to them at both
    runtime and compile time. This configuration behaves just like compile
    (which is now deprecated), and you should typically use this only in
    library modules. That's because, if an api dependency changes its
    external API, Gradle recompiles all modules that have access to that
    dependency at compile time. So, having a large number of api
    dependencies can significantly increase build times. Unless you want
    to expose a dependency's API to a separate test module, app modules
    should instead use implementation dependencies.
  • compileOnly: Gradle adds the dependency to the compilation classpath only (it is not added to the build output). This is useful
    when you're creating an Android library module and you need the
    dependency during compilation, but it's optional to have present at
    runtime. That is, if you use this configuration, then your library
    module must include a runtime condition to check whether the
    dependency is available, and then gracefully change its behavior so it
    can still function if it's not provided. This helps reduce the size of
    the final APK by not adding transient dependencies that aren't
    critical. This configuration behaves just like provided (which is now
    deprecated).
  • runtimeonly: Gradle adds the dependency to the build output only, for use during runtime. That is, it is not added to the compile
    classpath. This configuration behaves just like apk (which is now
    deprecated).

What's the difference between implementation, api and compile in Gradle?

tl;dr

Just replace:

  • compile with implementation (if you don't need transitivity) or api (if you need transitivity)
  • testCompile with testImplementation
  • debugCompile with debugImplementation
  • androidTestCompile with androidTestImplementation
  • compileOnly is still valid. It was added in 3.0 to replace provided and not compile. (provided introduced when Gradle didn't have a configuration name for that use-case and named it after Maven's provided scope.)

It is one of the breaking changes coming with Android Gradle plugin 3.0 that Google announced at IO17.

The compile configuration is now deprecated and should be replaced by implementation or api

From the Gradle documentation:

dependencies {
api 'commons-httpclient:commons-httpclient:3.1'
implementation 'org.apache.commons:commons-lang3:3.5'
}

Dependencies appearing in the api configurations will be
transitively exposed to consumers of the library, and as such will
appear on the compile classpath of consumers.

Dependencies found in the implementation configuration will, on the
other hand, not be exposed to consumers, and therefore not leak into
the consumers' compile classpath. This comes with several benefits:

  • dependencies do not leak into the compile classpath of consumers anymore, so you will never accidentally depend on a transitive
    dependency
  • faster compilation thanks to reduced classpath size
  • less recompilations when implementation dependencies change: consumers would not need to be recompiled
  • cleaner publishing: when used in conjunction with the new maven-publish plugin, Java libraries produce POM files that
    distinguish exactly between what is required to compile against the
    library and what is required to use the library at runtime (in other
    words, don't mix what is needed to compile the library itself and what
    is needed to compile against the library).

The compile configuration still exists, but should not be used as it will not offer the guarantees that the api and implementation configurations provide.


Note: if you are only using a library in your app module -the common case- you won't notice any difference.

you will only see the difference if you have a complex project with modules depending on each other, or you are creating a library.

Best practice for Gradle api vs implementation in multi-module project

Reposting from the Gradle forum thread.

What you describe is a fairly common discussion about layered architecture systems, also known as "strict" vs "loose" layering, or "open" vs "closed" layers. See this (hopefully free for you too) chapter from Software Architecture Patterns for some semiotics which is unlikely to help you much with your choice

From my point of view, if a module needs to break layering, I'd model the project structure to expose this in the most direct and visible way. In this case it means adding library as implementation dependency of feature1. Yes it makes the diagram uglier, yes it forces you to touch few more files on upgrade, and that is the point - your design has a flaw and it is now visible.

If few modules need to break the layer encapsulation in the same way, I may consider adding a separate base module exposing that functionality, with a name such as base-xyz. Adding a new module is a big thing, not because of the technical work, but because our brain can handle only so many "things" at a time (chunking). I believe the same would hold for Gradle "variants" when they become available, but I can't claim that yet as I haven't tried them hands on.

If all clients of the base module need to access library (i.e. because you use classes or exceptions from library in your public signatures) then you should expose library as API dependency of base. The downside of that is that library becomes part of the public API of base, and it is probably bigger than you would like, and not under your control. Public API is something you are responsible for, and you want to keep it small, documented, and backwards compatible.

At this point you may be thinking about jigsaw modules (good), osgi (err... don't), or wrapping the parts of lib that you need to expose in your own classes (maybe?)

Wrapping only for the sake of breaking dependencies is not always a great idea. For one it increases the amount of code you maintain and (hopefully) document. If you start doing small adaptations in the base layer, and the library is a well known library, you introduce (value added) inconsistencies - one needs to always be on guard whether their assumptions for lib still hold. Finally, often the thin wrappers end up leaking the library design, so even if they wrap the API - that still forces you to touch the client code when you replace/upgrade lib, at which point you may have been better off using lib directly.

So, as you can see, is about trade-offs and usability. The CPU doesn't care where your module boundaries lie, and all developers are different - some cope better with large amount of simple things, some cope better with small number of highly abstract concepts.

Don't obsess about the best (as in What Would Uncle Bob Do) design when any good design would work. The amount of extra complexity that is justified for the sake of introducing order is a fuzzy quantity, and is something that you are in charge of deciding. Make you best call and don't be afraid to change it tomorrow :-)

What does it really mean that api configuration exposes depedencies whereas implementation does not, in Gradle?

So does exposing dependencies in this case really just mean adding them to classpath (compile scope)?

Yes, it's pretty much just a matter of having them on the consumer's compile classpath or not.

One of the many answers about api vs implementation says it is merely about build optimization, it makes sense that the build time will be reduced if we not adding everything in the classpath maybe?

Well, good software design advocates not exposing internal implementation details. This is why you have public and private class members in the code. You could argue that this principal is solid when it comes to dependencies as well. I see the following benefits:

  • A consumer does not implicitly start relying on "internal" transitive dependencies. If they did, it would mean that you can't remove them from the library without breaking the consumers.
  • A reduced classpath may make compilation slightly faster. I don't think it matters a whole lot for normal projects though. Maybe it is more impactful if you rely on Java or Kotlin annotation processors or Groovy AST transformations that feels like scanning the entire classpath through each time.
  • Not having unnecessary modules on the compilation classpath means a library will not have to be recompiled if those modules changes.

The last one is the biggest benefit in my opinion. Let's say you have a big multi-project where a shared sub-project internally relies on Apache Commons Lang. If you have declared Lang as an api dependency and update it, then all other projects relying on this shared project need to be recompiled. If you declare it as an implementation dependency instead, this will not happen. All those projects will still need to be re-tested of cause as the runtime behaviour might have changed (this is handled correctly by default in Gradle).

And a bonus question, the Gradle doc says api configuration comes with java-library plugin, but apparently, I can use it without applying the plugin, how is this possible?

This is because the Kotlin plugin also declares an api configuration. It has the same semantics as configured by the java-library plugin.

If your project is a multi-project, you can still add the java-library plugin even if it is using the Kotlin plugin. An additional change that this will cause is that consumers will see the output directory for the compiled classes instead of the final jar file. This removes the need to construct the jar during normal development, which should reduce build time. On the other hand, there is apparently a potential performance problem on Windows if you have a lot of classes in a single project, so the usual your mileage may vary disclaimer applies here as well (I don't know how many "a lot" is though).



Related Topics



Leave a reply



Submit