Publishing your Android library - the local way

If you ever built an Android library, meant to be used in one or more projects, you have for sure faced the need to publish it in order to test its integration into your target app. You probably publish your libraries in some repository, such as JitPack, Nexus, JFrog, or other. But, what if you need to continue introducing changes or working on fixes in your library? Maybe publishing it to a remote repository each time is not the most time-saving option, especially if your library is a bit overweight. Or maybe your Internet access was cut off; how are you supposed to continue working like this? Well, there is actually a convenient, effective way to quickly get your library published and available to be integrated into your other projects: the Maven local repository.

Maven local

Maven local is actually a repository, but it is located in your own computer. As simple as that. So you can use this directory to store your project dependencies.

Depending on your computer's operating system, the default location for Maven local repository may vary.

  • Mac: /Users/[username]/.m2
  • Linux: /home/[username]/.m2
  • Windows: C:\Users\[username]\.m2

Publishing your library

First, we are going to navigate to our library's module, and there we are going to create a Gradle file, which will contain all the necessary code to publish the library locally. We are going to name it publishLocal.gradle. Its content will be the following:

apply plugin: 'maven-publish' 

task sourceJar(type: Jar) { 
    from android.sourceSets.main.java.srcDirs 
    classifier "sources" 
} 

project.afterEvaluate {
    publishToMavenLocal { 
        def groupId = LIBRARY_GROUP 
        def artifactId = LIBRARY_ARTIFACT_ID 
        def versionName = LIBRARY_VERSION_NAME + "-local" 
        def debugSuffix = "-debug" 
        def releaseSuffix = "-release" 
        publishing { 
            publications { 
                LibraryRelease(MavenPublication) { 
                    from components.release 
                    artifact(sourceJar) 
                    setGroupId groupId 
                    setArtifactId artifactId 
                    version versionName + releaseSuffix 
                } 
                LibraryDebug(MavenPublication) { 
                    from components.debug 
                    artifact(sourceJar) 
                    setGroupId groupId 
                    setArtifactId artifactId 
                    version versionName + debugSuffix 
                } 
            } 
            publications.all { 
                pom.withXml { 
                    asNode().dependencies.'*'
                    .findAll() { 
                        it.scope.text() == 'runtime' &&
                        project.configurations.implementation.allDependencies.find {
                            dep -> dep.name == it.artifactId.text() 
                        }
                    }.each { it.scope*.value = 'compile'} 
                } 
            } 
        } 

        doLast { 
            def prettyPrint = { 
                1.upto(100, { print "=" }) 
                println()
            } 
            println() 
            prettyPrint()
            println "PUBLICATION FINISHED" 
            println "Artifact RELEASE: " + groupId + ":" + artifactId +  ":" + versionName + releaseSuffix 
            println "Artifact DEBUG: " + groupId + ":" + artifactId +  ":" + versionName + debugSuffix
            prettyPrint() 
        } 
    }
}

Now we can include this script in our module-level build.gradle file. And that's it!

... 
apply from: 'publishLocal.gradle'

The code in depth

If you take a look at our first line of code, you can see that we are including the Maven Publish plugin, which lets us publish artifacts to an Apache Maven repository.

Below that line, we configure a task called sourceJar, which will generate the .jar file with our library's source files.

Now we are getting to the meaty part. We configure publishToMavenLocal task, indicating the publications it should perform. In this case, there are two: LibraryRelease and LibraryDebug; each of them uses a different build variant.

LIBRARY_GROUP, LIBRARY_ARTIFACT_ID, and LIBRARY_VERSION_NAME are constants defined in gradle.properties file.

Let's see each MavenPublication in detail:

LibraryRelease(MavenPublication) { 
    from components.release 
    artifact(sourceJar) 
    setGroupId groupId 
    setArtifactId artifactId 
    version versionName + releaseSuffix 
}

We are taking LibraryRelease as an example. First, we apply the component with the release build variant. After that, we indicate that besides the .aar file that is going to be generated, we also want to compile a .jar file with the source files (this step is optional). Finally, we set a group ID, an artifact ID, and a version name for our library (in this case we are using different suffixes for debug and release variants).

It is now time to generate a .pom file for each of the publications. The .pom file contains all the necessary configuration details that Maven uses to compile our project. Here we can find, among other things, the dependencies our library includes in detail.

publications.all { 
    pom.withXml { 
        asNode().dependencies.'*'
        .findAll() { 
            it.scope.text() == 'runtime' &&
            project.configurations.implementation.allDependencies.find {
                dep -> dep.name == it.artifactId.text() 
            }
        }.each { it.scope*.value = 'compile'} 
    }
}

In my particular case, I had to write a special rule to correct the Maven scope of some of the dependencies I use in my library. This may not be necessary for you. More information on Maven scopes here and here.

We end our script by printing a log with information about the published artifacts.

Publishing to Maven local

If you head to Gradle's task tree, inside the publishing section you will see we have these tasks available, which we can run depending on our needs:

  • publish[MODULE_NAME]DebugPublicationToMavenLocal compiles the project in debug mode.
  • publish[MODULE_NAME]ReleasePublicationToMavenLocal compiles the project in release mode.
  • publishToMavenLocal generates both debug and release versions.

In the following screenshot we can observe the output of a successful execution of publishToMavenLocal:

Gradle tasks can also be run from the command line, for example:
./gradlew publishToMavenLocal
./gradlew publishLibraryDebugPublicationToMavenLocal

Awesome! We already have our library, with its two variants and their artifacts, published in Maven local. But now, how can we integrate it into another project?

Integrating your library in a project

To import our library into another library or into an Android app, first we need to open our project-level build.gradle file, and instruct it to use mavenLocal() as repository:

buildscript { 
    repositories { 
        mavenLocal() 
        ... 
    } 
    ... 
} 

allprojects { 
    repositories { 
        mavenLocal() 
        ... 
    } 
}

Once this is done, we must include our library as a dependence in our app-level (or module-level) build.gradle file:

dependencies { 
    implementation "com.me:my-awesome-lib:1.0.0" 
    ... 
}

Our library's identifier, in this case com.me:my-awesome-lib:1.0.0, has the format groupId:artifactId:versionName, as we instructed to Gradle earlier. This information can also be seen in the .pom file generated with the publication.

So we arrived at the final step! Now it's time to compile our project and check that our library is imported correctly.

Conclusion

Today we tried an alternative to publishing an Android library, which hopefully will help you speed up your development process. We configured a Gradle file to publish debug and release versions of an Android library to Maven local repository, and then we learnt how to integrate it into our main project. I hope this article serves as one more tool for your daily work as an Android library developer.

24