Creating Android Archive (AAR), publishing to Maven Central and using it in android project Part 3

in #utopian-io6 years ago (edited)

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
    1. Creating GPG Key Pair
    2. Get keyId
    3. Exporting public key
    4. Exporting private key
  • Sign all artifacts
  • Publishing artifacts
    1. Creating sonatype account and JIRA ticket
    2. Configure publishing task
    3. Uploading public key to OpenPGP keyserver
    4. Publish
  • Importing library in project
    1. 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

1.png

Select minimum SDK version and click Next

2.png

Select Add No Activity and click Next

3.png

Select C++ Standard as Toolchain Default and Click Finish to create project

4.png

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

5.png

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

6.png

After successful process key pair will be generated

7.png

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

8.png

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

9.png

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

10.png

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 = [email protected]: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

11.png

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

13.png

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

12.png

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

14.png

Click on Release button to release library to maven central enter your groupId in advance search to search for your library

15.png

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

16.png

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

17.png

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

Sort:  

You have a minor typo in the following sentence:

On 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.
It should be one of the instead of on of the.

Thank you for your contribution.
I really enjoyed reading your tutorial, good work.

  • It could improve explanation of what Maven Central is, you are very brief when you talk about this point.
  • Put some comments on your code, plus the explanation you've already made.

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,

  • "... Maven Central is, you are very brief when you talk about this point".
  • In this file build.gradle i don´t see comments.

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

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @kabooom! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

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!