6

I was (finally) able to publish my Android library to an AWS S3 maven repository using this guide. It's published as an AAR instead of JAR file, which means that even though the generated POM file lists all its internal dependencies, they are ignored when adding the library to another project. Seems like a pretty common problem. I understand from this that I just need to list the dependencies in a maven manifest, but how is this done?

I'm really a newbie at this, so the simpler the better... Thanks!

Note: I also found this question, so I added "{transitive=true}" at the end of the dependency implementation line and it worked.

So now to include my library successfully, the dependency must be coded as:

implementation (group: 'com.mygroup', name: 'my_library', version: '1.3', ext: 'aar', classifier: 'release') { transitive=true changing=true }

(I included "changing=true" to force it to re-download the library every time.)

If I don't include "transitive=true", I get the following errors:

    02-04 20:11:48.462 10225-10225/com.mydomain.app.testapplication4 E/dalvikvm: Could not find class 'android.graphics.drawable.RippleDrawable', referenced from method android.support.v7.widget.AppCompatImageHelper.hasOverlappingRendering
    02-04 20:14:07.080 10225-10225/com.mydomain.app.testapplication4 E/dalvikvm: Could not find class 'android.app.NotificationChannel', referenced from method com.mydomain.library.mydomain$1.onReceive
    02-04 20:14:07.290 10225-10225/com.mydomain.app.testapplication4 E/AndroidRuntime: FATAL EXCEPTION: main
        java.lang.NoClassDefFoundError: com.google.firebase.iid.FirebaseInstanceId
            at com.mydomain.library.MyActivity.onCreate(MyActivity.java:88)
            at android.app.Activity.performCreate(Activity.java:4701)
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1051)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1924)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1985)
            at android.app.ActivityThread.access$600(ActivityThread.java:127)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1151)
            at android.os.Handler.dispatchMessage(Handler.java:99)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:4477)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:788)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:555)
            at dalvik.system.NativeStart.main(Native Method)

Here is the latest script I used to publish the library (note that it only publishes the release build, but the dependency line still requires the "release" classifier):

    apply plugin: 'maven-publish'

    group = 'com.mydomain'
    version = '1.3'

    // Add sources as an artifact
    task sourceJar(type: Jar) {
        from android.sourceSets.main.java.srcDirs
        classifier "source"
    }

    // Loop over all variants
    android.libraryVariants.all { variant ->
        if (variant.buildType.name == "release") { // only release build
        variant.outputs.all { output ->
            // This creates a publication for each variant
            publishing.publications.create(variant.name, MavenPublication) {
                // The sources artifact from earlier
                artifact sourceJar

                // Variant dependent artifact, e.g. release, debug
                artifact source: output.outputFile, classifier: output.name

                // Go through all the dependencies for each variant and add them to the POM
                // file as dependencies
                pom.withXml {
                    final dependenciesNode = asNode().appendNode('dependencies')

                    ext.addDependency = { Dependency dep, String scope ->
                        if (dep.group == null || dep.version == null || dep.name == null || dep.name == "unspecified")
                            return // ignore invalid dependencies

                        final dependencyNode = dependenciesNode.appendNode('dependency')
                        dependencyNode.appendNode('groupId', dep.group)
                        dependencyNode.appendNode('artifactId', dep.name)
                        dependencyNode.appendNode('version', dep.version)
                        dependencyNode.appendNode('scope', scope)

                        if (!dep.transitive) {
                            // If this dependency is transitive, we should force exclude all its dependencies them from the POM
                            final exclusionNode = dependencyNode.appendNode('exclusions').appendNode('exclusion')
                            exclusionNode.appendNode('groupId', '*')
                            exclusionNode.appendNode('artifactId', '*')
                        } else if (!dep.properties.excludeRules.empty) {
                            // Otherwise add specified exclude rules
                            final exclusionNode = dependencyNode.appendNode('exclusions').appendNode('exclusion')
                            dep.properties.excludeRules.each { ExcludeRule rule ->
                                exclusionNode.appendNode('groupId', rule.group ?: '*')
                                exclusionNode.appendNode('artifactId', rule.module ?: '*')
                            }
                        }
                    }

                    // List all "compile" dependencies (for old Gradle)
                    configurations.compile.getDependencies().each { dep -> addDependency(dep, "compile") }
                    // List all "api" dependencies (for new Gradle) as "compile" dependencies
                    configurations.api.getDependencies().each { dep -> addDependency(dep, "compile") }
                    // List all "implementation" dependencies (for new Gradle) as "runtime" dependencies
                    configurations.implementation.getDependencies().each { dep -> addDependency(dep, "runtime") }
                }
            }
        }
        }
    }

    // Ensure that the publish task depends on assembly
    tasks.all { task ->
        if (task instanceof AbstractPublishToMaven) {
            task.dependsOn assemble
        }
    }

    // Configure the destination repository with
    // S3 URL and access credentials
    publishing {
        // Properties properties = new Properties()
        // properties.load(file('maven.properties').newDataInputStream())

        repositories {
            maven {
                url "s3://androidsdk.mydomain.com.s3.amazonaws.com"
                credentials(AwsCredentials) {
                    accessKey "myaccesskey"
                    secretKey "mysecretkey"
                }
            }
        }
    }

Here are the dependencies in the library's gradle.build file:

    dependencies {
        api fileTree(include: ['*.jar'], dir: 'libs')
        api 'com.android.support:appcompat-v7:27.1.1'
        api 'com.android.support.constraint:constraint-layout:1.1.3'
        api 'com.android.support:design:27.1.1'
        api 'io.reactivex.rxjava2:rxandroid:2.1.0'
        api 'io.reactivex.rxjava2:rxjava:2.2.4'
        api 'com.github.instacart.truetime-android:library-extension-rx:3.3'
        api 'com.google.dagger:dagger-android:2.15'
        annotationProcessor 'com.google.dagger:dagger-android-processor:2.15'
        annotationProcessor 'com.google.dagger:dagger-compiler:2.15'
        api 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
        api 'com.google.firebase:firebase-core:16.0.7'
        api 'com.google.firebase:firebase-messaging:17.3.4'
    }

I was thinking that the generated POM file below might not match the library's dependencies, but they do. The "scope" tags in the POM file should remove the need for "transitive=true" right?

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mydomain</groupId>
  <artifactId>my_library</artifactId>
  <version>1.3</version>
  <packaging>pom</packaging>
  <dependencies>
    <dependency>
      <groupId>com.android.support</groupId>
      <artifactId>appcompat-v7</artifactId>
      <version>27.1.1</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.android.support.constraint</groupId>
      <artifactId>constraint-layout</artifactId>
      <version>1.1.3</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.android.support</groupId>
      <artifactId>design</artifactId>
      <version>27.1.1</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.reactivex.rxjava2</groupId>
      <artifactId>rxandroid</artifactId>
      <version>2.1.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.reactivex.rxjava2</groupId>
      <artifactId>rxjava</artifactId>
      <version>2.2.4</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.github.instacart.truetime-android</groupId>
      <artifactId>library-extension-rx</artifactId>
      <version>3.3</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.google.dagger</groupId>
      <artifactId>dagger-android</artifactId>
      <version>2.15</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.nostra13.universalimageloader</groupId>
      <artifactId>universal-image-loader</artifactId>
      <version>1.9.5</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.google.firebase</groupId>
      <artifactId>firebase-core</artifactId>
      <version>16.0.6</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.google.firebase</groupId>
      <artifactId>firebase-messaging</artifactId>
      <version>17.3.4</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
</project>
ScottyB
  • 2,167
  • 1
  • 30
  • 46

2 Answers2

0

You should be able to resolve this by changing your POM XML generation so that the <packaging> section is setup as aar rather than pom:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.humanvideoboard</groupId>
  <artifactId>hvb_library</artifactId>
  <version>1.0</version>
  <packaging>aar</packaging>

However, now that I'm re-examining this, the cause for why it works with transitive dependencies is by the use of implementation vs api when building the library or when building the thing which is using the library. If your library is exposing types from its dependencies as part of its public API, then those build dependencies need to be handled correctly in your library build. See this page for more information.

Larry Schiefer
  • 15,687
  • 2
  • 27
  • 33
  • Thanks Larry. I tried to modify the script in the tutorial I followed (https://medium.com/@porten/publishing-android-library-artifacts-to-amazon-s3-buckets-8db4231e844f), specifically in the "pom.withXml" area, without any luck. What needs to change in that script to add the packaging tag? – ScottyB Jan 05 '19 at 06:09
  • I've updated my question with a lot more detail. Also, I realized I hadn't put the publish script in the library's build.gradle file, so now I can get it to work if I add {transitive = true} at the end of the dependency line. There's really a lot in my question...at the end I'm just trying to publish a release build of my library and make it as simple as possible for others to use it! Thanks again. – ScottyB Jan 05 '19 at 16:20
  • Just updated my answer with some additional info on the likely reason why transitive dependencies are working for you. – Larry Schiefer Jan 07 '19 at 12:34
  • I went ahead and gave you the bounty since time was about up and nobody else attempted an answer! I was only able to spend a little time looking at that link, and hoped it was a simple as changing all my "implementation" dependencies in my library's build.gradle to "api" dependencies. I made those changes and re-published the library, then tried to use the library from a new app (without "transitive=true"). It compiled fine, but then still crashed when it couldn't find the libraries. For example: Could not find class 'com.nostra13.universalimageloader.core.ImageLoaderConfiguration$Builder'. – ScottyB Jan 10 '19 at 05:22
  • Did I misunderstand what I need to do? Thanks again! – ScottyB Jan 10 '19 at 05:22
  • I think you are on the right path, but something is definitely not making it into your library or into the dependencies that your library needs in order for other projects to use it. By changing your deps to `api`, I would have expected the POM file `` section for each dep to have a `compile` added to it. – Larry Schiefer Jan 10 '19 at 13:02
  • I think my first mistake was using the guide I referenced in my original question since the script it included did not insert tags with dependencies. I found one that did here (https://stackoverflow.com/a/42160584/1161573) so I tried it, and the pom file (updated in my question above) now includes the tags. Unfortunately, I'm still getting the NoClassDefFoundError run-time errors with those libraries. I feel I am closer... Thanks again. – ScottyB Jan 10 '19 at 22:52
  • Well I was able to get back to this issue and updated information in the question, including the specific error I see. It seems to me that the POM file is still not quite right since the compile tags aren't doing the trick. Also, according to https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html, "compile" is the default scope. It may also be just a Firebase issue (https://stackoverflow.com/questions/51525615/using-firebase-in-a-library) but since it works with "transitive=true" I'm still hoping there's a solution. Thanks! – ScottyB Feb 05 '19 at 03:13
  • One more thing: To help isolate the problem, I republished a library version that doesn't use Firebase, and I got another NoClassDefFoundError (java.lang.NoClassDefFoundError: io.reactivex.subjects.BehaviorSubject), so it's not a Firebase-specific issue... – ScottyB Feb 05 '19 at 03:32
0

I've faced similar issue but my problem was that in the publication block i used the 'library' function instead of 'maven' function

In my library build.gradle i have such block:

afterEvaluation{
   publishing {
       publications {
            maven(MavenPublication) { // here I had library(MvenPublication) which caused the packaing to be pom instead of aar
           artifacts = ["build/outputs/aar/library-release.aar"]
       }
    }
  }
Moti Bartov
  • 3,454
  • 33
  • 42