Creating Android Archive (AAR), publishing to Maven Central and using it in android project Part 3
Repository
https://github.com/aosp-mirror
Overview
In Part 1 and Part 2 we used indirect approach to link to maven central, first we published android archive library to jcenter and then linked it to maven central. In this tutorial we will use direct approach we will create android archive (AAR) library project, publish directly to maven central and then import in android project.
Maven Central repository is the largest collection of Java and other open source components. We can easily distribute our libraries to millions of developers and it provides easiest way to access those libraries. Some of the advantages of maven central are, it is already configured in maven and gradle based projects no need to add repository url, libraries published to maven central comes with complete artifacts and complete POM file(info about library and dependencies etc) and libraries on maven central are all signed.
Requirements
- Android Studio 3.0 or higher
- Android NDK
- GnuPG
Difficulty
- Intermediate
Tutorial covers
- Android Archive (AAR) library project
- Generating artifacts and POM file
- Generating signing keys
- Creating GPG Key Pair
- Get keyId
- Exporting public key
- Exporting private key
- Sign all artifacts
- Publishing artifacts
- Creating sonatype account and JIRA ticket
- Configure publishing task
- Uploading public key to OpenPGP keyserver
- Publish
- Importing library in project
- Output
Guide
Android Archive (AAR) library project
Android Archive (AAR) library is a zip archive it can have
- Java code
- C/C++ code
- Resources in res/ folder
- Assets in assets/ folder
- Other jars in libs/ folder
- Android manifest
Create new android project and change Application name, Company domain and Package name and select Include C++ support and click Next
Select minimum SDK version and click Next
Select Add No Activity and click Next
Select C++ Standard as Toolchain Default and Click Finish to create project
Open app build.gradle file and remove application plugin
apply plugin: 'com.android.application'
and replace with library plugin
apply plugin: 'com.android.library'
remove applicationId and add archivesBaseName in defaultConfig and Sync project to convert application project to library project
android {
defaultConfig {
archivesBaseName = 'maven-aar'
........
........
}
}
Empty library project has been created next step is to add some code and resources in library. Our library .aar file will include Java code, C++ code, youtube drawable and String resource.
Java code
Java code contains one native method which will return library version and one utility method that will generate unique id
public class MyLib {
//loading native library
static {
System.loadLibrary("native-lib");
}
//calls native method and returns version
public static native String getLibVersion();
//returns unique id
public static String getUniqueId() {
return UUID.randomUUID().toString();
}
}
C++ code
Simple JNI method to return library version add this method to cpp/native-lib.cpp
#include <jni.h>
//returns library version as string
extern "C"
JNIEXPORT jstring JNICALL
Java_com_faob_mavenaar_MyLib_getLibVersion(JNIEnv *env, jobject instance) {
return env->NewStringUTF("Lib Version: 1.0.0");
}
Drawable
There is youtube logo in drawable folder which you can download from here
String resource (lib_description)
<resources>
<string name="app_name">MavenAAR</string>
<string name="lib_description">My awesome MavenAAR library</string>
</resources>
Generating artifacts and POM file
If we build our project we will get only .aar library we need additional artifacts which are required by maven central. Our package should contain jar file for sources and optional jar file for docs. To create additional jars open build.gradle file and add these lines to create sources and docs jars
task sourcesJar(type: Jar) {
classifier "sources"
from android.sourceSets.main.java.srcDirs
}
task javadoc(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
archives sourcesJar
}
Next step is to create POM file, maven central requires some additional information to be added in POM file which includes name, description, url, license, scm, developer and dependencies. For more information please read POM example at the end in maven central requirements.
We will use gradle plugin to create basic POM file which also adds our project dependencies to POM file. Add this plugin at the top of build.gradle file
plugins {
id "com.github.dcendents.android-maven" version "2.1"
}
Next step is to add additional information in POM file, install task gives us access to POM file
def groupIdX = 'com.faob'
def artifactIdX = 'maven-aar'
def versionX = '1.0.0'
group groupIdX
version versionX
install {
repositories.mavenInstaller {
pom.project {
name 'MavenAAR'
description 'Android aar library'
url 'https://github.com/faob-dev/Tutorials'
licenses {
license {
name 'MIT License'
url 'http://www.opensource.org/licenses/mit-license.php'
}
}
scm {
url 'https://github.com/faob-dev/Tutorials'
}
developers {
developer {
id 'faob'
name 'FaoB'
}
}
}
}
}
Open Gradle tool window in android studio and double click on
app > Tasks > other > install to create 4 artifacts which are in orange folders
app/build/libs/maven-aar-1.0.0-javadoc.jar
app/build/libs/maven-aar-1.0.0-sources.jar
app/build/outputs/aar/maven-aar-release.aar
app/build/poms/pom-default.xml
Generating signing keys
Another requirement of maven central is to sign all artifacts that we want to publish. In this section we will sign all artifacts that we generated in previous section. Download and install GnuPG if you using Debian based OS such as Ubuntu this tool is already installed.
1. Creating GPG Key Pair
In this section we will create GPG key pair. Open terminal and type this command to create new key pair
gpg --gen-key
Enter Name, Email and add some Passphrase and click on OK to create key pair
After successful process key pair will be generated
2. Get keyId
To export public and private key, keyId is required type this command
gpg --list-keys
Last eight characters of fingerprint is keyId see highlighted characters in screenshot my keyId is 98066C03
3. Exporting public key
To export public key is ASCII format type this command value after --export is keyId and last parameter is file name where you want to save your key
gpg --armor --export 98066C03 > publicKey.gpg
4. Exporting private key
To export private key type this command value after --export-secret-keys is keyId and last parameter is file name where you want to save your key
gpg --export-secret-keys 98066C03 > secretKey.gpg
Sign all artifacts
To sign all artifacts first we apply signing plugin add this on top in build.gradle file
apply plugin: 'signing'
Signing plugin needs three values that we generated in previous section which are keyId, Passphrase and secretKey file. Put all these in local.properties file since this file is not added to git it is safe to put your credentials here make sure you don't share this file
signing.keyId=98066C03
signing.password=testing
signing.secretKeyRingFile=D\:\\secretKey.gpg
Next step is to load and read these properties from local.properties file and assign them to signing properties. Note we added these signing properties to ext so that signing plugin can access these values. Next we created custom task signArtifacts which depends on install task means when we run signArtifacts first it will run install task to create all artifacts and then sign them
Properties props = new Properties()
props.load(project.rootProject.file('local.properties').newDataInputStream())
ext."signing.keyId" = props.getProperty("signing.keyId")
ext."signing.password" = props.getProperty("signing.password")
ext."signing.secretKeyRingFile" = props.getProperty("signing.secretKeyRingFile")
task signArtifacts(dependsOn: install) {
signing.sign configurations.archives
doLast {
signing.sign file("${buildDir}/poms/pom-default.xml")
}
}
Open Gradle tool window in android studio and double click on
app > Tasks > other > signArtifacts to create artifacts with signed files(.asc) in orange folders
app/build/libs/maven-aar-1.0.0-javadoc.jar
app/build/libs/maven-aar-1.0.0-javadoc.jar.asc
app/build/libs/maven-aar-1.0.0-sources.jar
app/build/libs/maven-aar-1.0.0-sources.jar.asc
app/build/outputs/aar/maven-aar-release.aar
app/build/outputs/aar/maven-aar-release.aar.asc
app/build/poms/pom-default.xml
app/build/poms/pom-default.xml.asc
Publishing artifacts
1. Creating sonatype account and JIRA ticket
In this section we will create sonatype account and create JIRA ticket to request groupId. groupId is usually your organization domain in reverse form. One of the disadvantages of maven central is that we can't use custom groupId you must own the domain but in jcenter we can use any groupId. In my case i don't own any domain so i will use my project hosting domain which is github which works with maven central.
Create account https://issues.sonatype.org, login and then open dashboard. Click on create button to create issue then enter Summary, Description, Group Id, Project URL, SCM URL and leave others as defaults then click on Create button to create issue
Summary = Request for groupId
Description = I want to publish my android library to maven central i need groupId
Group Id = io.github or com.github or your domain
Project URL = https://github.com/faob-dev/Tutorials
SCM URL = git@github.com:faob-dev/Tutorials.git
After few hours you will get reply something like that
io.github.faob-dev has been prepared, now user(s) faob can:
Deploy snapshot artifacts into repository https://oss.sonatype.org/content/repositories/snapshots
Deploy release artifacts into the staging repository
https://oss.sonatype.org/service/local/staging/deploy/maven2
Promote staged artifacts into repository 'Releases'
Download snapshot and release artifacts from group
https://oss.sonatype.org/content/groups/public
Download snapshot, release and staged artifacts from staging group
https://oss.sonatype.org/content/groups/staging
please comment on this ticket when you promoted your first release, thanks
They assigned me this groupId io.github.faob-dev my github username has been appended with github domain to create unique groupId. We will use this groupId and staging repository later when we publish our library. Change groupId in build.gradle file
def groupIdX = 'io.github.faob-dev'
2. Configure publishing task
Add sonatype account credentials in local.properties file
sonatype.username=foo
sonatype.password=bar
Apply maven-publish plugin which allows us to upload our library to maven central. Next we create publishing task in this task we add two closures first one is publications here we will add all artifacts with their signed files and 2nd one is repositories here we will add staging repository url and sonatype credentials
apply plugin: 'maven-publish'
publishing {
publications {
mavenAAR(MavenPublication) {
groupId groupIdX
artifactId artifactIdX
version versionX
artifact(sourcesJar)
artifact(file("$buildDir/libs/$artifactIdX-$versionX-sources.jar.asc")) {
classifier = 'sources'
extension = 'jar.asc'
}
artifact(javadocJar)
artifact(file("$buildDir/libs/$artifactIdX-$versionX-javadoc.jar.asc")) {
classifier = 'javadoc'
extension = 'jar.asc'
}
artifact("$buildDir/outputs/aar/$artifactIdX-release.aar")
artifact(file("$buildDir/outputs/aar/$artifactIdX-release.aar.asc")) {
classifier = null
extension = 'aar.asc'
}
artifact(file("${buildDir}/poms/pom-default.xml.asc")) {
classifier = null
extension = 'pom.asc'
}
}
}
repositories {
maven {
url "https://oss.sonatype.org/service/local/staging/deploy/maven2"
credentials {
username props.getProperty('sonatype.username')
password props.getProperty('sonatype.password')
}
}
}
}
maven-publish plugin creates its own POM file and publish it since we already created POM file, to stop maven-publish to creates its own POM file we will return false from the task and configure it to use already generated POM file by setting its destination property
model {
tasks.generatePomFileForMavenAARPublication {
destination = file("${project.buildDir}/poms/pom-default.xml")
onlyIf {
false
}
}
}
This gradle task is dynamically generated based on the name of publication since we used mavenAAR as publication name plugin will add this name to task name. Task should be added under model closure you can get the name of task from gradle tool window under publishing
3. Uploading public key to OpenPGP keyserver
Public key is used by nexus repository manager to validate the signatures of signed files in staging phase It must be upload to any OpenPGP keyserver. Open publicKey.gpg in your favorite text editor that we generated in Exporting public key section go to http://keyserver.ubuntu.com:11371/ copy and paste public key and click on Submit to upload key.
To verify it is successfully uploaded type this command in terminal to search key using keyId
gpg --keyserver http://keyserver.ubuntu.com:11371/ --search-keys 98066C03
4. Publish
Open Gradle tool window in android studio and double click on
app > Tasks > build > clean
app > Tasks > other > signArtifacts
app > Tasks > publishing > publish
After successful publish goto https://oss.sonatype.org(use same sonatype credentials) and click on Staging Repositories scroll to the end and you will see you library its name starts with your groupId. Here you can see all the files that are uploaded
Select your library and click on Close button to start validation process. Click on Refresh button and in Activity tab you can see detail of every step if any validation fails it will show errors. After successful validation Release button becomes enable and it should look like this
Click on Release button to release library to maven central enter your groupId in advance search to search for your library
Importing library in project
importing library from maven central is simple just add this one line to dependencies in app build.gradle file. Note dependency ends with @aar since it is .aar library dependency format is
groupId:artifactId:version@aar
dependencies {
implementation 'io.github.faob-dev:maven-aar:1.0.0@aar'
...
...
}
Drag and drop ImageView on layout, dialog will appear youtube logo will be in Project section select logo and click Ok
To call library code first we will call C++ method to get library version, Java method to get unique Id and then access string resource from .aar library
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//calling C++ method
System.out.println(MyLib.getLibVersion());
//calling java method
System.out.println("UniqueId: "+ MyLib.getUniqueId());
//getting string resource
String lib_description = getResources().getString(R.string.lib_description);
System.out.println("String resource: "+ lib_description);
}
}
1. Output
Output is in Logcat tool window
Code
Complete build.gradle file
plugins {
id "com.github.dcendents.android-maven" version "2.1"
}
apply plugin: 'com.android.library'
apply plugin: 'signing'
apply plugin: 'maven-publish'
def groupIdX = 'io.github.faob-dev'
def artifactIdX = 'maven-aar'
def versionX = '1.0.0'
group groupIdX
version versionX
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 15
targetSdkVersion 28
versionCode 1
archivesBaseName = artifactIdX
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0-alpha3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
task sourcesJar(type: Jar) {
classifier "sources"
from android.sourceSets.main.java.srcDirs
}
task javadoc(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
archives sourcesJar
}
install {
repositories.mavenInstaller {
pom.project {
name 'MavenAAR'
description 'Android aar library'
url 'https://github.com/faob-dev/Tutorials'
licenses {
license {
name 'MIT License'
url 'http://www.opensource.org/licenses/mit-license.php'
}
}
scm {
url 'https://github.com/faob-dev/Tutorials'
}
developers {
developer {
id 'faob'
name 'FaoB'
}
}
}
}
}
Properties props = new Properties()
props.load(project.rootProject.file('local.properties').newDataInputStream())
ext."signing.keyId" = props.getProperty("signing.keyId")
ext."signing.password" = props.getProperty("signing.password")
ext."signing.secretKeyRingFile" = props.getProperty("signing.secretKeyRingFile")
task signArtifacts(dependsOn: install) {
signing.sign configurations.archives
doLast {
signing.sign file("${buildDir}/poms/pom-default.xml")
}
}
publishing {
publications {
mavenAAR(MavenPublication) {
groupId groupIdX
artifactId artifactIdX
version versionX
artifact(sourcesJar)
artifact(file("$buildDir/libs/$artifactIdX-$versionX-sources.jar.asc")) {
classifier = 'sources'
extension = 'jar.asc'
}
artifact(javadocJar)
artifact(file("$buildDir/libs/$artifactIdX-$versionX-javadoc.jar.asc")) {
classifier = 'javadoc'
extension = 'jar.asc'
}
artifact("$buildDir/outputs/aar/$artifactIdX-release.aar")
artifact(file("$buildDir/outputs/aar/$artifactIdX-release.aar.asc")) {
classifier = null
extension = 'aar.asc'
}
artifact(file("${buildDir}/poms/pom-default.xml.asc")) {
classifier = null
extension = 'pom.asc'
}
}
}
repositories {
maven {
url "https://oss.sonatype.org/service/local/staging/deploy/maven2"
credentials {
username props.getProperty('sonatype.username')
password props.getProperty('sonatype.password')
}
}
}
}
model {
tasks.generatePomFileForMavenAARPublication {
destination = file("${project.buildDir}/poms/pom-default.xml")
onlyIf {
false
}
}
}
Github
Complete project available on github. Clone repo and open project name MavenAAR
You have a minor typo in the following sentence:
It should be one of the instead of on of the.thanks
Thank you for your contribution.
I really enjoyed reading your tutorial, good work.
Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.
To view those questions and the relevant answers related to your post, click here.
Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]
Hi i added some explanation of maven central and can you tell me where to put comments? i guess i added comments everywhere in Java/C++ codes and also explains them and i explain everything in pieces about build.gradle file
Hi @kabooom,
Thank you.
Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]
yes i updated info about Maven Central and build.gradle is used for building project this file is not part of source code this whole project was about this file and i explained everything about this file that's why i didn't add comments. In future tutorials i will try to add comments in build files
Thanks
Thank you for your understanding and good work.
Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]
Hey @kabooom
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!
Want to chat? Join us on Discord https://discord.gg/h52nFrV.
Vote for Utopian Witness!
Congratulations @kabooom! You received a personal award!
Happy Birthday! - You are on the Steem blockchain for 1 year!
Click here to view your Board
Congratulations @kabooom! You received a personal award!
You can view your badges on your Steem Board and compare to others on the Steem Ranking
Vote for @Steemitboard as a witness to get one more award and increased upvotes!