Gradle and nested non-transitive dependencies - android

Here is a test project: click
I have a test Gradle Android project with three modules: app, library_a, library_b. app depends on library_a, then library_a depends on library_b:
build.gradle (app)
dependencies {
...
compile (project(":library_a")){
transitive = false;
}
}
build.gradle (library_a)
dependencies {
...
compile (project(":library_b")){
transitive = false;
}
}
Note that I set transitive = false because I don't want classes from library_b to be accessed from app
Every module has just one class, code is pretty simple:
app:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
//...
ClassA classA = new ClassA();
classA.doSomething();
}
}
library_a:
public class ClassA
{
public void doSomething(){
Log.i("Test", "Done A!");
ClassB classB = new ClassB();
classB.doSomething();
}
}
library_b:
public class ClassB
{
public void doSomething(){
Log.i("Test", "Done B!");
}
}
Well, here is the problem: I'm building my project with gradlew. Apk is compiling successfully, but when I run it I get NoClassDefFoundError.
I/Test﹕ Done A!
E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.NoClassDefFoundError: ru.pvolan.library_b.ClassB
at ru.pvolan.somelibrary.ClassA.doSomething(ClassA.java:12)
...
If I set transitive = true in both .gradle files, it runs ok, but, as I noted above, I don't want dependency to be transitive, as far as I don't want ClassB can be accessed from MainActivity - only ClassA.
What am I doing wrong?

This is a problem that Gradle has simplified in Gradle v3.4.
If you convert library A to use v3.4 there is a simple fix.
Gradle 3.4 changes the "compile" configuration to a set of configurations "api" and "implementation".
First you should upgrade gradle to 3.4 and use the java-library plugin in lieu of the java plugin.
You should use the "api" configuration on any jar that is explicitly used in the API method calls (return type, input parameters, etc).
For all other jars that you want to "hide" (like Library B) you should use the "implementation" configuration. As Library B is only used within the body of implementation methods there is no need to expose it to any other jars at compile time; however it still needs to be available at runtime so Library A can use it.
To implement this your Library A script should replace
apply plugin: 'java'
dependencies {
...
compile (project(":library_b")){
transitive = false;
}
}
with
apply plugin: 'java-library'
dependencies {
implementation project(":library_b")
}
This change will tell Gradle to include Library B as a runtime dependency of app, so that app cannot compile against it, but Library B still will be available at runtime for Library A to use. If for some reason app ends up needing Library B in the future, it would be forced to explicitly include Library B in it's dependency list to ensure it gets the desired version.
See this description from Gradle itself for more details and examples:
https://blog.gradle.org/incremental-compiler-avoidance

The problem is that library_b is a required dependency. You can't simply exclude it, since you need it to be on the classpath at runtime. You are effectively misrepresenting your actual dependencies in order to enforce a code convention and therefore losing any advantage of leveraging a dependency management system like Gradle. If you want to enforce class or package blacklist I'd suggest using a source analysis tool like PMD. Here's an an example of a rule to blacklist specific classes.
If that is not possible for some reason you can get your above example to "work" by simply adding library_b to the runtime classpath of app.
dependencies {
runtime project(':library_b')
}

Do you use multidex?
When I had a problem like this I used multidex and called class from different module. I could fix it only by turning off multidex and running proguard.
UPD
android {
compileSdkVersion 21
buildToolsVersion "21.1.0"
defaultConfig {
...
minSdkVersion 14
targetSdkVersion 21
...
// Enabling multidex support.
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.0'
}
more about multi dex https://developer.android.com/tools/building/multidex.html
and about proguard http://developer.android.com/tools/help/proguard.html

Related

Expected #HiltAndroidApp to have a value. Did you forget to apply the Gradle Plugin?

I have Google this problem, but the results are not work for me.
The detail as following.
public final class App extends com.zhixin.wedeep.common.BaseApplication implements androidx.lifecycle.LifecycleOwner {
^
// Expected #HiltAndroidApp to have a value. Did you forget to apply the Gradle Plugin?
The App code.
#HiltAndroidApp
class App : BaseApplication(), LifecycleOwner {
#Inject
lateinit var service: EventService
private val mLifecycleRegistry = LifecycleRegistry(this)
}
This module gradle file.
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-allopen'
apply plugin: 'androidx.navigation.safeargs.kotlin'
apply plugin: 'dagger.hilt.android.plugin'
dependencies {
implementation rootProject.ext.dependencies["hilt-android"]
implementation rootProject.ext.dependencies["hilt-lifecycle-viewmodel"]
kapt rootProject.ext.kapt["hilt-compiler"]
kapt rootProject.ext.kapt["hilt-android-compiler"]
}
Who has ideas? Thanks!
I just hit this problem this morning. Do you have anything in your build.gradle that adds arguments to the annotationProcessOptions? For example:
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
}
If so, try changing from "arguments =" to "arguments +=", as just using equals overwrites anything set previously.
EDIT: Looks like kotlin gradle plugin 1.5.21 solves the problem without using the bellow javacOptions.
UPDATE Kotlin and try again!
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21"
If you are not using Room and still get the error put this in the android block of build.gradle:
kapt {
javacOptions {
// These options are normally set automatically via the Hilt Gradle plugin, but we
// set them manually to workaround a bug in the Kotlin 1.5.20
option("-Adagger.fastInit=ENABLED")
option("-Adagger.hilt.android.internal.disableAndroidSuperclassValidation=true")
}
}
It's a kapt bug on kotlin 1.5.20: https://github.com/google/dagger/issues/2684
SOLUTION 1 : Downgrade kotlin
If you are using kotlin-gradle-plugin:1.5.20 (in your project level build.gradle), downgrading it to 1.5.10 should fix the issue.
The issue will probably be fixed in the next versions, then you will upgrade to the new version.
SOLUTION 2 : Disable Gradle worker API
Add this line to your gradle.properties file:
kapt.use.worker.api=false
It will disable the gradle worker API.
It works for me, but as said in the documentation:
Using the worker API lets Gradle run independent annotation processing tasks from a single project in parallel, which in some cases significantly decreases the execution time.
So by disabling it, your build may be slowed down.
Just don't forget to add Hilt classpath dependency to your project level gradle file:
classpath "com.google.dagger:hilt-android-gradle-plugin:$versions.daggerHiltCoreVersion"
Define the specific version number instead of $versions.daggerHiltCoreVersion above.
And add plugin to your app level gradle:
apply plugin : 'dagger.hilt.android.plugin'
For later Gradle versions, add the plugin as follows
plugins {
id 'dagger.hilt.android.plugin'
}
Adding to sitatech's answer, I've also encountered this issue using kotlin-grade-plugin-1.5.20. The new 1.5.21 patch solved it for me.
Kotlin Grade Plugin v1.5.21 release notes: https://github.com/JetBrains/kotlin/releases/tag/v1.5.21
Issue in Jetbrains issue tracker: https://youtrack.jetbrains.com/issue/KT-47416
To backup #SteveC answer, when using Kotlin Gradle DSL is a bit different
We can't use either += or arguments = mapOf(). As stated in the official Dagger-Hilt documentation here & the github issue here regarding the docs as well
See below image for explanations:
arguments = mapOf() will call setArguments with this.arguments.clear(), thus will overwrite previous argument (in this case Hilt)
Workaround approach:
javaCompileOptions {
annotationProcessorOptions {
arguments(
mapOf(
"dagger.gradle.incremental" to "true",
"room.incremental" to "true"
)
)
}
}
Wrapping the arguments() as a functions instead of calling setter, it'll retain the previous arguments as well.
in my case, multi module in presentaion layer, I just removed :
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
in defaultConfig block

Can't import AAR library with #IntDef annotations

I have the following code in my library:
#Documented
#IntDef({OpacityAnimationType.NONE,
OpacityAnimationType.BLINKING,
OpacityAnimationType.SHINY,
OpacityAnimationType.AURA,
})
public #interface OpacityAnimationType {
int NONE = 0;
int BLINKING = 1;
int SHINY = 2;
int AURA = 3;
}
In gradle for library I have
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
minSdkVersion 16
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["library" : "true"]
}
}
}
}
and
configurations {
javadocDeps
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile "com.android.support:appcompat-v7:$supportLibraryVersion"
compile "com.android.support:support-annotations:$supportLibraryVersion"
javadocDeps "com.android.support:support-annotations:$supportLibraryVersion"
}
Which I deploy to JFrog BinTray, and then try to use it in my app. I have to exclude appcompat-v7 and support-annotations from library dependency, but I build still fails with:
Error:Failed to resolve: annotationProcessor
Now I'm stuck, tried many things but nothing helps. I can't build main project with this library.
Do I need to implement any custom AnnotationProcessor to be able to use #IntDef's?
So finally I've been able to overcome this issue!
It looks like in case of custom annotations custom annotation processor is also required. For now I've decided to skip creating custom annotation processor and not use custom annotations for enumerations with #IntDef.
But anyways, if your library uses existing annotations and you publish it on mavenCentral or jCenter or other repository and use it in other projects, that you'll need to add some magic to javadoc task.
It starts here:
https://github.com/vulko/AnimatedArcProgressView/blob/master/library/build.gradle with
configurations {
javadocDeps
}
dependencies {
// ...
compile("com.android.support:support-annotations:$supportLibraryVersion") {
transitive false;
}
javadocDeps "com.android.support:support-annotations:$supportLibraryVersion"
}
and then continues in publishing gradle script here: https://github.com/vulko/AnimatedArcProgressView/blob/master/gradle/publish-library.gradle with:
task androidJavadocs(type: Javadoc) {
source = android.sourceSets.main.java.source
// this is the magic
classpath += configurations.javadocDeps
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}
Anyways, all the code can be found here:
https://github.com/vulko/AnimatedArcProgressView/
Been googling this issue and looks like if there are any annotations in library, library needs an annotations project which will contain those. Not sure about the processor though, hopefully it wouldn't be needed, otherwise I'll have to switch from custom annotations to #IntDef with some static final int's instead.
There's also some gradle magic that is required to do this, and so far no good tutorials, but a bunch of code on github with processors and annotations. I'd like to avoid using custom processor, cause I simply don't need it.
Any ways, I'll try to keep this thread updated when I finally manage to solve this issue.
Now the story continues.
I had to create a annotations submodule in library project for annotations, and I had to move #IntDef's out of it, otherwise it wouldn't be able to import any of android annotations to submodule. So I've moved #IntDef's to library, and they use custom annotations from submodule now.
Even though I'm able to build and deploy it, the library artifact can't be imported, cause:
Error:Could not find
com.kvolkov.animatedprogressviews:annotations:unspecified. Searched in
the following locations: Required by:
project : > com.kvolkov.animatedprogressviews:library:1.0-RC5
I assume this happens because I need to deploy annotations submodule as a separate artifact now, which I can't do, since I wasn't able to match code from some tutorial for .gradle of annotations module with the plugins for bintray to deploy it there from studio...
Well, this is still not the end.
If any1 is interested or willing to help, you might take a look at current code of library here:
https://github.com/vulko/AnimatedArcProgressView
I'd be glad to recieve some help or advice, cause all info regarding this uses apt instead of annotation processor.

Create library without package dependency using Firebase Android SDK

Since we keep switching our backend depedencies (Previously parse, then backendless now firebase), I am trying to create a backend library for all my projects with adapters for specific backends and common interface to be used in the projects (Adapter Design pattern).
For example,
The backend library interface will have method,
save(User user)
and the adapters will have implementation to save the User in firebase or backendless, and I can easily switch
But the way the firebase sdk works, we need to have 'google-services.json' in our path with predefined 'package name', otherwise we would get the following error in gradle build,
Execution failed for task ':backend:processReleaseGoogleServices'.
No matching client found for package name 'com.lib.backend'
The error is obvious, as the google-services.json would have the packagename of the specific project.
So now is there a way or a procedure I could follow, where my projects will not use the 'Firebase' classes directly and it works only through the 'backend' library.
A simple combination of Bridge pattern and Dependency Injection should be enough. Create an interface in your app/library, create another library module which implement that interface using Firebase (with the plugin), and use DI in order to load your implementation. Whenever you want to change implementation, just replace this library module to some other implementation.
Edit: an example
We'll create a project with 3 modules:
Application module
Interface module (for reference from two other modules)
Firebase implementation of interface
You project should look like this:
Firebase module package name same as the app package name.
Next, we'll create the Firebase impl gradle file:
apply plugin: 'com.android.library'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.google.gms:google-services:3.0.0'
}
}
android {
compileSdkVersion 25
buildToolsVersion "25.0.0"
defaultConfig {
minSdkVersion 25
targetSdkVersion 25
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile 'com.google.firebase:firebase-core:10.0.1'
compile 'com.google.firebase:firebase-database:10.0.1'
provided project(':proxylib')
}
apply plugin: 'com.google.gms.google-services'
In our app module gradle build file, we'll need to include both modules:
...
dependencies {
...
compile project(':firebaseproxy')
compile project(':proxylib')
}
And we'll put our google-services.json file in the firebase module dir:
Code is simple. The interface looks something like this:
public interface BackendProxy {
void save(String user);
}
And our Firebase implementation looks like:
public class BackendProxyImpl implements BackendProxy {
#Override
public void save(String message) {
FirebaseDatabase database = FirebaseDatabase.getInstance();
...
//save data to db
}
}
Note: I haven't tried actually implementing the database part, but it seems like it is running the google play gradle task. While it seems ok to me, I would have expect the google-play task to use the applicationId property, which is not available in the library module. If something is build wrong because of this, you'll have to run the google-play gradle task from the app module.

apt dependency scope in Android gradle - what is it used for?

What is the apt dependency scope in android gradle files i see sometimes ?
An example looks like this?
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
android {
compileSdkVersion 20
buildToolsVersion '20.0.0'
defaultConfig {
applicationId "org.ligboy.test.card.module1"
minSdkVersion 14
targetSdkVersion 20
versionCode 1
versionName "1.0"
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
final DAGGER_VERSION = '2.0.2'
dependencies {
compile "com.google.dagger:dagger:${DAGGER_VERSION}"
apt "com.google.dagger:dagger-compiler:${DAGGER_VERSION}"//what is this scope
provided 'org.glassfish:javax.annotation:10.0-b28'
}
and in the top level build.gradle file it has this global dependency:
buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
}
}
Notice in the dependencies section there is a apt scope ? i only know of compile, package and provided scope. compile
includes the dependency at compile time and in your package, provided says only include the library at compile time and discard it at
package time so its not included in final build. and Package is the reverse, it includes the dependency in the package and not at compile time.
But what is apt dependency scope which we obviously need the com.neenbedankt.android-apt for it to work so i know its android based.
update:
why cant i use provided dependency scope instead of apt scope ? How do they differ ?
i created a tutorial on dagger dependency scopes for those who need more info.
From the android-apt project page:
The android-apt plugin assists in working with annotation processors in combination with Android Studio. It has two purposes:
Allow to configure a compile time only annotation processor as a dependency, not including the artifact in the final APK or library
Set up the source paths so that code that is generated from the annotation processor is correctly picked up by Android Studio.
You are using Dagger, which uses annotation processing to generate code. The annotation processing code shouldn't be included in the final APK, and you want the generated code to be visible to Android Studio. android-apt enables this behavior.
This sounds very similar to the provided scope, but apt differs from provided in a few key ways. The first difference is that code generated by an apt dependency is available to the IDE, whereas code generated by a provided dependency is not.
Another important difference is that the code in a library using the provided scope is on the IDE classpath (i.e. you can import the classes and attempt to use them), whereas code in an apt dependency is not. With provided, your code will crash at runtime if you don't actually provide the referenced dependencies with a compile scoped counterpart.
You can find a discussion about apt vs provided on this android-apt issue.
In the case of Dagger, there should be no reason to include the annotation processor and code generator in any of your code (which the provided scope would allow). Thus the apt scope is more appropriate.
Update for October 2016:
You probably don't need apt and the android-apt plugin anymore. Version 2.2 of the Android Gradle plugin has an annotationProcessor configuration that you should be using instead.
See more at What's next for android-apt?
Just to add how to change this in Studio 2.2 +
dependencies {
compile 'com.google.dagger:dagger:2.4'
annotationProcessor "com.google.dagger:dagger-compiler:2.4"
}
Add this in apps gradle module. No need to change any other thing.
Happy coding :)

Gradle - add dependency to tests of another module

I have a multi-module gradle project that looks like this:
Parent
|--server
|--application (android module)
+--common
The server tests have a dependency on the common module tests. For this, I added
testCompile files(project(':common').sourceSets.test.output.classesDi
compileTestJava.dependsOn tasks.getByPath(':common:testClasses')
and it worked great. Unfortunately, when I tried to do the same thing for the application module that also has a dependency on the common module tests, it wouldn't work. It fails with:
Build file 'application\build.gradle' line: 103
A problem occurred evaluating project ':application'.
Could not find property 'sourceSets' on project ':common'
After googling a bit I also tried
project.evaluationDependsOn(':common')
testCompile files(project(':common').sourceSets.test.output.classesDir)
But fails with another exception:
Project application: Only Jar-type local dependencies are supported. Cannot handle: common\build\classes\test
Any ideas on how to fix this?
There's a couple of approaches solving the problem of importing test classes in this article. https://softnoise.wordpress.com/2014/09/07/gradle-sub-project-test-dependencies-in-multi-project-builds/ The one I used is:
code in shared module:
task jarTest (type: Jar) {
from sourceSets.test.output
classifier = 'test'
}
configurations {
testOutput
}
artifacts {
testOutput jarTest
}
code in module depending on the shared module:
dependencies{
testCompile project(path: ':common', configuration: 'testOutput')
}
And there seems to be a plugin for it as well! https://plugins.gradle.org/plugin/com.github.hauner.jarTest/1.0
Following the approach from sakis, this should be the configuration you need to get the tests available from another project in the Android platform (done for debug variant).
Shared module:
task jarTests(type: Jar, dependsOn: "assembleDebugUnitTest") {
classifier = 'tests'
from "$buildDir/intermediates/classes/test/debug"
}
configurations {
unitTestArtifact
}
artifacts {
unitTestArtifact jarTests
}
Your module:
dependencies {
testCompile project(path: ":libName", configuration: "unitTestArtifact")
}
The solution mentioned by droidpl for Android + Kotlin looks like this:
task jarTests(type: Jar, dependsOn: "assembleDebugUnitTest") {
getArchiveClassifier().set('tests')
from "$buildDir/tmp/kotlin-classes/debugUnitTest"
}
configurations {
unitTestArtifact
}
artifacts {
unitTestArtifact jarTests
}
Gradle for project that is going to use dependencies:
testImplementation project(path: ':shared', configuration: 'unitTestArtifact')
I know it's kinda an old question but the solution mentioned in the following blog solves the problem very nicely and is not a sort of hack or a temporary workaround:
Shared test sources in Gradle multi-module project
It works something like this:
// in your module's build.gradle file that needs tests from another module
dependencies {
testCompile project(path: ':path.to.project', configuration: 'test')
}
Also you should note that in the very last paragraph he mentioned that you need to enable Create separate module per source set in IntelliJ settings. But it works fine without using that option too. Probably due to changes in the recent IntelliJ versions.
EDIT: IntelliJ recognizes this fine as of 2020.x versions.
I think you could use gradles java test fixtures. This will automatically create a testFixtures source set, in which you can write your test that you want to reuse.
Test fixtures are configured so that:
they can see the main source set classes
test sources can see the test fixtures classes
For example, if you have some class in common module:
public class CommonDto {
private final Long id;
private final String name;
// getters/setters and other methods ...
}
Then in the common module, you could write into src/testFixtures/java following utils:
public class Utils {
private static final CommonDto A = new CommonDto(1, "A");
private static final CommonDto B = new CommonDto(2, "B");
public static CommonDto a() { return A; }
public static CommonDto b() { return B; }
}
Then in you other modules you could add this to reuse Utils class
dependencies {
// other dependencies ...
testImplementation(testFixtures(project(":common")))
}
All of this is better explained in the documentation that I provided initially. There are some nuances that you need to take into account until you create this not to leak test classes into production.

Categories

Resources