Kotlin Code Organization

January 27, 2020

What’s the motivation behind organizing the code? Two points come to mind.

Sounds empathic. Where do we start?

SDL

Maven introduced a concept of the Standard Directory Layout. Gradle tends to follow it bringing so-called source sets along the way. The following file system tree is SDL-compliant.

.
├── main
│   ├── java
│   │   └── Code.java
│   ├── kotlin
│   │   └── Kode.kt
│   └── resources
│       └── production.xml
└── test
    ├── java
    │   └── CodeTests.java
    ├── kotlin
    │   └── KodeTests.kt
    └── resources
        └── test.xml

This two-level structure allows us to organize the code based on a functional target (production, tests) and on implementation details (language, tools). Let’s leverage this.

Tips

src/{sourceSet}/kotlin

Storing Kotlin files in the Kotlin-specific directory sounds obvious but a lot of projects are 100% Kotlin and store the source code as Java. Take a look at LeakCanary, Muzei, OkHttp, Scarlet, Timber, ViewPump, Workflow and more. I see a number of reasons behind this.

To be honest, there is nothing outright wrong with mixing Java and Kotlin code. It’s more accurate and expectable to store them separately. Also, it might help with Java → Kotlin migration efforts — it’s easier to observe that the Java directory is shrinking and the Kotlin one is growing than running cloc all the time.

src/{sourceSet}/kotlinX

There is a common issue of organizing Kotlin extensions. I’ve seen a lot of projects with the Extensions.kt garbage fire. When everything is in a single file — it’s easier to overlook an extension and write a new one placed at… extensions/Extensions.kt. Guess what happens next.

I suggest storing extensions using the target class package and file names. Plus — move them to the kotlinX/ directory as a separation of the project code from additions to the external one. This approach leads to a better separation of concerns.

For example, the following io.reactivex.functions.Consumer extension should be placed at src/main/kotlinX/io/reactivex/functions/Consumer.kt.

package io.reactivex.functions

fun Consumer<Unit>.asAction() = Action { accept(Unit) }

Bonus — imports start to make sense.

- import hello.there.asAction
+ import io.reactivex.functions.asAction

src/testFixtures/kotlin

A growing test / specification suite might be not pleasant to look at. Using fakes is great but there is a possibility of having a huge file tree with mixed tests and fakes.

.
└── src
    └── test
        └── kotlin
            ├── ApplicationSpec.kt
            ├── FakeApplication.kt
            ├── FakePermissions.kt
            └── PermissionsSpec.kt

Since fakes and tests are different things — I suggest to split them in the digital world as well.

.
└── src
    ├── test
    │   └── kotlin
    │       ├── ApplicationSpec.kt
    │       └── PermissionsSpec.kt
    └── testFixtures
        └── kotlin
            ├── FakeApplication.kt
            └── FakePermissions.kt

In fact, Gradle supports this approach for the Java code and with benefits — it’s possible to share testFixtures across modules. However, it doesn’t work with Gradle Kotlin and Android plugins.

Gradle API

The code below will use the Gradle Kotlin DSL but it can be adapted to the Groovy DSL as well. The code was run against Gradle 6.1.1, Gradle Kotlin plugin 1.3.61 and Gradle Android plugin 3.5.3.

JVM

Gradle uses a couple of classes as an API to configure the source code location:

The Gradle Kotlin for JVM plugin adds another one.

The DSL works with those classes.

Android

Gradle Android plugin ignores native Gradle source set infrastructure and introduces its own. To be fair, the Android API tries to mimic the Gradle one, so I suspect the reinvention was done for a reason.

The Gradle Kotlin for Android plugin doesn’t provide a KotlinAndroidSourceSet (like KotlinSourceSet for JVM). Fortunately enough we can use the Java AndroidSourceSet instead (thanks to mixing).

The DSL is similar to the JVM one.

Gradle Implementation

Nice, we can use the Gradle API to apply our tips! Snippets below are DSL declarations that can be used in both single and multiple module configurations described above.

JVM

named("main") {
    withConvention(KotlinSourceSet::class) {
        // Gradle Kotlin for JVM plugin configures "src/main/kotlin" on its own
        kotlin.srcDirs("src/main/kotlinX")
    }
}

named("test") {
    withConvention(KotlinSourceSet::class) {
        // Gradle Kotlin for JVM plugin configures "src/test/kotlin" on its own
        kotlin.srcDirs("src/test/kotlinX", "src/testFixtures/kotlin")
    }
}

Android

named("main") {
    java.srcDirs("src/main/kotlin", "src/main/kotlinX")
}

named("test") {
    java.srcDirs("src/test/kotlin", "src/test/kotlinX", "src/testFixtures/kotlin")
}

Next?

Don’t afraid to configure source sets — think about what can be done better and adapt. The Gradle API might be not intuitive at first glance — especially when Kotlin and Android are brought in the mix — but almost everything can be achieved.