Publish a Kotlin lib with gradle Kotlin DSL

I wanted to play more with Kotlin and I wanted to publish KGeoGson lib to a remote maven repo.

I was following gradle guide to build my kotlin project with Kotlin DSL.

Bootstrap Kotlin project

Create project directory with files of your library you want to build and publish. You may have a directory structure like the following:

project
├── build.gradle.kts
├── settings.gradle.kts
└── my-kotlin-library
├── build.gradle.kts
└── src
├── main
│   └── kotlin
│   └── org
│   └── example
│   └── MyLibrary.kt
└── test
└── kotlin
└── org
└── example
└── MyLibraryTest.kt

Setup

Root build.gradle.kts

plugins {
`build-scan`
}

buildScan {
termsOfServiceUrl = "https://gradle.com/terms-of-service"
termsOfServiceAgree = "yes"

publishAlways()
}

val clean by tasks.creating(Delete::class) {
delete(rootProject.buildDir)
}

In this file, « build-scan » plugin is activated and a clean task is added. That’s all for it

settings.gradle.kts

include("lib")

In this file, we define our modules

lib/build.gradle.kts

First we are adding some plugins

plugins {
// Add Kotlin plugin to build our Kotlin lib
kotlin("jvm") version "1.3.21"
// Get version from git tags
id("fr.coppernic.versioning") version "3.1.2"
// Documentation for our code
id("org.jetbrains.dokka") version "0.9.17"
// Publication to bintray
id("com.jfrog.bintray") version "1.8.4"
// Maven publication
`maven-publish`
}

Then we are defining dependencies and their repositories for our code

repositories {
jcenter()
mavenCentral()
}

dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7")
implementation("com.google.code.gson:gson:2.8.5")

testCompile("junit:junit:4.12")
}

We need some more tasks to add sources and javadoc to our lib. We are starting by configuring dokka task:

tasks {
dokka {
outputFormat = "html"
outputDirectory = "$buildDir/javadoc"
moduleName = rootProject.name
}
}

We can then bundle documentation into a jar

val dokkaJar by tasks.creating(Jar::class) {
group = JavaBasePlugin.DOCUMENTATION_GROUP
description
= "Assembles Kotlin docs with Dokka"
archiveClassifier.set("javadoc")
from(tasks.dokka)
dependsOn(tasks.dokka)
}

We are creating another jar containing sources

val sourcesJar by tasks.creating(Jar::class) {
archiveClassifier.set("sources")
from(sourceSets.getByName("main").allSource)
}

At this stage, you are able to compile your lib. The most important part of this article begins. Let’s see how publication is working. It is very important to configure pom.xml file of maven artifact in a right manner. Otherwise you will not be able to submit your library into JCenter repo.

Let’s start configuring base of maven publish plugin

val artifactName = "libname"
val artifactGroup = "org.example"

publishing {
publications {
create<MavenPublication>("lib") {
groupId = artifactGroup
artifactId = artifactName
// version is gotten from an external plugin
version = project.versioning.info.display
  // This is the main artifact
from(components["java"])
// We are adding documentation artifact
artifact(dokkaJar)
// And sources
artifact(sourcesJar)
  }
}
}

Now we need to add information about package in pom.xml file. You can edit pom.xml with pom.withXml {code


val pomUrl = "..."
val pomScmUrl = "..."
val pomIssueUrl = "..."
val pomDesc = "..."
val pomScmConnection = ""..."
val pomScmDevConnection = "..."

val githubRepo = "..."
val githubReadme = "..."

val pomLicenseName = "The Apache Software License, Version 2.0"
val pomLicenseUrl = "http://www.apache.org/licenses/LICENSE-2.0.txt"
val pomLicenseDist = "repo"

val pomDeveloperId = "..."
val pomDeveloperName = "..."


publishing {
publications {
create<MavenPublication>("lib") {
[...]

pom.withXml {
asNode().apply {
appendNode("description", pomDesc)
appendNode("name", rootProject.name)
appendNode("url", pomUrl)
appendNode("licenses").appendNode("license").apply {
appendNode("name", pomLicenseName)
appendNode("url", pomLicenseUrl)
appendNode("distribution", pomLicenseDist)
}
appendNode("developers").appendNode("developer").apply {
appendNode("id", pomDeveloperId)
appendNode("name", pomDeveloperName)
}
appendNode("scm").apply {
appendNode("url", pomScmUrl)
appendNode("connection", pomScmConnection)
}
}
}
}
}
}

Now that your maven publication is well configured, you can configure bintray plugin

bintray {
  // Getting bintray user and key from properties file or command line
user = if (project.hasProperty("bintray_user")) project.property("bintray_user") as String else ""
key = if (project.hasProperty("bintray_key")) project.property("bintray_key") as String else ""

// Automatic publication enabled
publish = true

// Set maven publication onto bintray plugin
setPublications("lib")

// Configure package
pkg.apply {
repo = "maven"
name = rootProject.name
setLicenses("Apache-2.0")
setLabels("Gson", "json", "GeoJson", "GPS", "Kotlin")
vcsUrl = pomScmUrl
websiteUrl = pomUrl
issueTrackerUrl = pomIssueUrl
githubRepo = githubRepo
githubReleaseNotesFile
= githubReadme

// Configure version
version.apply {
name = project.versioning.info.display
desc
= pomDesc
released = Date().toString()
vcsTag = project.versioning.info.tag
}
}
}

Here we is ! Happy publication !

Use Dagger Provider with Android Material Stepper

On an Android project, I am using android-material-stepper from StepStone. I am also using dependency injection with dagger.

I was facing the following problem : How to properly inject Fragments without the need to create a DaggerComponent for each of them ?

Providers are here to help. This is how I am doing

class StepFragmentBL @Inject constructor() :
Fragment(), Step, BlView {

@Inject
lateinit var blPresenter: BlPresenter

...
}

This is one of the Fragment I am creating. We can see the @Inject constructor with @Inject lateinit var that shows that this object is injectable by dagger. It is also implementing Step to be used by StepperLayout and implements another interface.

class BlPresenter @Inject constructor

BlPresenter is a simple injectable class

@Singleton
@Component(modules = [(DeliveryModule::class)])
interface DeliveryComponents {

fun inject(deliveryActivity: DeliveryActivity)
}

This is the component injecting my Activity

@Module
class DeliveryModule(private val activity: DeliveryActivity) {

@Named("Activity")
@Provides
internal fun providesContext(): Context {
return activity
}

@Provides
fun providesFragmentManager(): FragmentManager {
return activity.supportFragmentManager
}
}
class DeliveryActivity : AppCompatActivity() {

@Inject
lateinit var deliveryPresenter: DeliveryPresenter

private lateinit var deliveryComponents: DeliveryComponents

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)

di()
ui()
}

private fun di() {
deliveryComponents = DaggerDeliveryComponents.builder()
.deliveryModule(DeliveryModule(this))
.build()
deliveryComponents.inject(this)
}

private fun ui() {
Timber.v(Info.getMethodName())
stepperLayout.adapter = deliveryPresenter.stepperAdapter
    
}
}

We are creating the component into the Activity here. Then, presenter for this activity is also injected

class DeliveryPresenter @Inject constructor() {

@Inject
lateinit var stepperAdapter: PickingStepperAdapter

}

We are arriving to a StepperAdapter instance, also injected

private const val NB_FRAG = 2

class PickingStepperAdapter @Inject constructor(
fm: FragmentManager,
@Named("Activity") context: Context) :
AbstractFragmentStepAdapter(fm, context) {

/**
* Provider for Fragment
*/
@Inject
lateinit var blFragmentProvider: Provider<StepFragmentBL>

/**
* Provider for Fragment
*/
@Inject
lateinit var pdtFragmentProvider: Provider<StepFragmentPdt>

override fun getCount(): Int {
return NB_FRAG
}

override fun createStep(position: Int): Step {
Timber.v("${Info.getMethodName()} $position")

return when (position) {
0 -> blFragmentProvider.get()
else -> pdtFragmentProvider.get()
}
}
}

Here, we are just using

@Inject
lateinit var blFragmentProvider: Provider<StepFragmentBL>

to tell dagger to create the code that will provide an instance of fragment each time blFragmentProvider.get() will be called. Indeed, this is called here

return when (position) {
0 -> blFragmentProvider.get()
else -> pdtFragmentProvider.get()
}

Like this, dagger is injecting fragment for us, and we don’t have to care about objects creation