Android SDK Deployment to Bitbucket as Maven Repository

Ramesh Pokhrel
5 min readFeb 22, 2023

--

Overview

There are lots of Maven repositories that gave us to upload and retrieve our private artifacts. Some of them are free, and some with premium plans. one of the best I found while researching is the Jfrog Artifactory. It provides lots of features and supports multiple package types including gradles, but its pricing is too high. Some other maven repositories are like packageCloud, MyprivateMavenrepo, Repsy.io, and These all are good in some sense but some of them don’t provide proper user authentication and do not provide multi-user support.

Bitbucket manages source code and is a low-cost option to maintain a private repository. it’s not a fully functional maven repository and some of the uploading tasks have to be done manually in Gradle script so it better is to search for other repositories such as Nexus, artifactory, and so on.

so why is Maven repo like Bitbucket?

  1. The backup mechanism ensures you never ever lose releases.
  2. Access remotely outside of your network.

Set Up Bitbucket

First, create a bitbucket repository where we manage our Gradle artifacts. and create a releases branch that stores all artifacts releases.

Set Up Android to upload artifacts

Wagon-git

Wagon-git is a maven plugin to upload maven artifacts to the git repository. It’s a third-party library available on Github here.

add a wagon repository in your project-level build.gradle file.

allprojects {
apply plugin: 'maven-publish'
repositories {
maven { url "https://raw.github.com/synergian/wagon-git/releases" }
}
}

create a Gradle publish-bitbucket.gradle file that contains some tasks to upload our artifacts in the bitbucket maven repo. file location should be the same as the project label Gradle file.

apply plugin: 'maven'
android {
compileSdkVersion 31
}

repositories {
maven { url "https://raw.github.com/synergian/wagon-git/releases" }
}

configurations {
deployLibrary
}

dependencies {
deployLibrary "ar.com.synergian:wagon-git:0.2.5"
}

task androidJavadocs(type: Javadoc) {
failOnError false
source = android.sourceSets.main.java.sourceFiles
}

task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.sourceFiles
}

task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
}

artifacts {
archives androidSourcesJar
archives androidJavadocsJar
}




def urlExists(String repositoryUrl) {

try {
def connection = (HttpURLConnection) new URL(repositoryUrl).openConnection()

connection.setRequestProperty("Authorization", "Basic " + getBase64EncodedCredentials())
connection.setConnectTimeout(10000)
connection.setReadTimeout(10000)
connection.setRequestMethod("HEAD")
def responseCode = connection.getResponseCode()
println("responseCode: " + responseCode)

if (responseCode == 401) {
throw new RuntimeException("Unauthorized BitBucket user - please provide valid username and password in gradle.properties file")
}

return (200 == responseCode)
} catch (IOException ignored) {
return false
}
}

def getBase64EncodedCredentials() {
def s = USERNAME + ":" + PASSWORD;
return s.bytes.encodeBase64().toString()
}


ext {
urlExists = this.&urlExists
getBase64EncodedCredentials = this.&getBase64EncodedCredentials
}

Put your Bitbucket repository information. for e.g., if your Bitbucket repo URL is like

https://bitbucket.org/companyname/yourRepo/

In gradle.properties file, provide the following information

ARTIFACT_PACKAGING=aar
COMPANY=companyname
REPOSITORY_NAME=yourRepo

USERNAME=your-bitbucket-username
PASSWORD=your-bitbucket_password

you can create a Bitbucket password from here

Setting icon moved to the left of the profile picture in latest bitbucket design

If your SDK project is multi-module, so go to the modules label build.gradle file and write scripts to upload your archives to bitbucket artifactory.

Note: uploadArchives Deprecated. Move to publishing below.

The uploadArchives task has been deprecated starting from Gradle 6.8 and is no longer available in Gradle versions 7.x and newer. It is recommended to use the maven-publish plugin for artifact publication in recent versions of Gradle. If you still want to use uploadArchives please use gradle before 7.x.

plugins {
id 'com.android.application'
id 'kotlin-android'
id 'maven-publish'
id 'maven'
}
apply from: '../publish-bitbucket.gradle'

artifacts {
archives file("aar location")// eg. "$buildDir/outputs/aar/${project.getName()}-release.aar"
}
uploadArchives {
repositories.mavenDeployer {
configuration = configurations.deployLibrary
repository(url: 'git:releases://git@bitbucket.org:' + COMPANY + '/' + REPOSITORY_NAME + '.git')
pom.groupId = "your.package.name" //your library package name e.g. com.remote.control
pom.version = "1.0.0" //library version 1.0.0
pom.artifactId = "artifact id" //your library artifact id. eg. dispatcher
//you can now access as com.remote.control:dispatcher:1.0.0
pom.withXml {
def dependenciesNode = asNode().appendNode('dependencies')
configurations.implementation.allDependencies.each { dependency ->
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', dependency.group)
dependencyNode.appendNode('artifactId', dependency.name)
dependencyNode.appendNode('version', dependency.version)
}
}
}
}

COMPANY, REPOSITORY_NAME, and other stuff are from gradle.properties file, so don’t forget to put information there. for building and publishing in the old way

./gradlew :modulename:build
gradlew uploadArchives

publishing (Gradle 7.x.x)

I have to remove the `uploadArchives` script from the above. Please update it with the publishing configuration task. The artifact will be published to the local repository located at ‘D:\\learning\\remotedispatch’.

Note: remove all :unspecified: from .pom file. Publishing task accepts gradle dependency with groupId:artifactId:version.


artifacts {
archives file("aar location")// eg. "$buildDir/outputs/aar/${project.getName()}-release.aar"
}

publishing {
publications {
mavenAar(MavenPublication) {
pom.groupId = "your.package.name" //your library package name e.g. com.remote.control
pom.version = "1.0.0" //library version 1.0.0
pom.artifactId = "artifact id" //your library artifact id. eg. dispatcher
artifact(file("$buildDir/outputs/aar/${project.getName()}-release.aar")) // Replace "aar location" with the actual location of your AAR file
pom.withXml {
def dependenciesNode = asNode().appendNode('dependencies')
configurations.implementation.allDependencies.each { dependency ->
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', dependency.group)
dependencyNode.appendNode('artifactId', dependency.name)
dependencyNode.appendNode('version', dependency.version)
}
}
}
}
repositories {
maven {
name = 'myrepo'
url = "D:\\learning\\remotedispatch" //location where build generated
}
}
}

Publishing (Gradle 8.7.x, Openjdk 17.0.11 ) in Kotlin Gradle

Kotlin Gradle

Project Level build.gradle.kts

plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
alias(libs.plugins.android.library) apply false
`maven-publish`

}

Module level build.gradle.kts


plugins {

id ("maven-publish")

}
.
.
publishing {
repositories {
maven {
name = "myrepo"
url = uri("file://${project.projectDir.parent}/analytics-library/") // Adjusted to point to the desired directory
}
}

publications {
create<MavenPublication>("mavenJava") {
pom {
groupId = "com.analytics.analytics_android" //your library package name e.g. com.remote.control
version = "1.0.0" //library version 1.0.0
artifactId = "analytics" //your library artifact id. eg. dispatcher
artifact(file("$buildDir/outputs/aar/${project.getName()}-release.aar")) // Replace "aar location" with the actual location of your AAR file
name = "Android Analytics"
description = "Analytics for Android"
url = "Bitbucket.org"
licenses {
license {
name = "The Apache License, Version 2.0"
url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
}
}
developers {
developer {
id = "kanxoramesh"
name = "Ramesh Pokhrel"
email = "pokhrelramesh1996@gmail.com"
}
}
withXml {
val dependenciesNode = asNode().appendNode("dependencies")
val configurationNames = arrayOf("implementation", "api")

configurationNames.forEach { configurationName ->
configurations[configurationName].allDependencies.forEach {
if (it.group != null) {
val dependencyNode = dependenciesNode.appendNode("dependency")
dependencyNode.appendNode("groupId", it.group)
dependencyNode.appendNode("artifactId", it.name)
dependencyNode.appendNode("version", it.version)
}
}
}
}

}
}
}
}

Now build aar for each module and upload them with the following scripts

./gradlew :modulename:build //.\gradlew :app:remote:build --warning-mode all
./gradlew publish

This will publish in the local directory. Initialize generated local directory with the bitbucket repo and upload it on the remote branch e.g releases.

Setup Android to access Artifacts

Go to your bitbucket project setting page and under user and group access, add a member with read access.

now in the android project gradle file add the following repo.

1. On Groovy Gradle

allprojects {
...
repositories {
....
//access
maven {
credentials {
username "username"
password "password"
}
authentication {
basic(BasicAuthentication)
}
url "https://api.bitbucket.org/2.0/repositories/your-company-name/your-project-name/src/releases"
}
...
}
...
}

2. On Kotlin Gradle (Using token)

Set Token on local.properties

bitbucket.token=*******token here*****

On Project level setting.gradle.kts


dependencyResolutionManagement {

repositories {

maven {
url = uri("url")

authentication {
create<HttpHeaderAuthentication>("header")
}
credentials(HttpHeaderCredentials::class) {
name = "Authorization"
value = "Bearer ${localProperties.getProperty("bitbucket.token")}"
}
}
}
}
implementation 'com.your.package:modulename:1.0.0'//

Problems that I face during the development

Each module should be with plugin com.android.library, but uploading archives will not work so we should change com.android.application while building aar and so uploadArchives works as expected.

--

--