Android project code style using Spotless and ktlint


Why use Spotless?

When you write code, it’s always good to follow some common code style. But doing this manually, even using modern IDEs like Android Studio is painful — sooner or later you’ll forget about this.

This is especially important when you work in a team. If I (or you) press ⌘+⌥+L (autoformatting) pretty often, you can’t be sure that all your teammates do the same.

Of course, even with big problems in the code style, the code will compile successfully and would work absolutely equal with code with good code style. But it’s always better when your code is formatted following the common code style, diffs would be more readable and your colleagues will scold you less 🙂

How to solve the problem?

Spotless will help us! It allows us to format (and check rules) code in multiple languages, but we’re interested in Kotlin. Spotless uses ktlint to work with it.

ktlint’s standard rules are listed in its’ README, but let me duplicate some of them here:

  • 4 spaces for indentation
  • No semicolons (unless used to separate multiple statements on the same line)
  • No unused imports
  • No consecutive blank lines
  • No blank lines before }
  • etc

As you can see, it’s pretty useful. Let’s set it up!

Spotless & ktlint integration

So, let’s assume that we have an Android project with Gradle, Kotlin, Java, and XML.

Spotless is provided as Gradle-plugin (not only Gradle, but we need just it).

First of all, add the following dependency to your project-level build.gradle:

classpath "com.diffplug.spotless:spotless-plugin-gradle:3.27.0"

It’ll look like this:

buildscript {

    repositories {
        google()

    }    dependencies {
        classpath "com.android.tools.build:gradle:4.0.0-alpha07"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "com.diffplug.spotless:spotless-plugin-gradle:3.27.0"
    }
}

allprojects {
    repositories {
        google()
    }
}

Then create spotless.gradle file, and add the following code to it:

apply plugin: "com.diffplug.gradle.spotless"

spotless {
    java {
        target '**/*.java'
        googleJavaFormat().aosp()
        removeUnusedImports()
        trimTrailingWhitespace()
        indentWithSpaces()
        endWithNewline()
    }    
    kotlin {
        target '**/*.kt'
        ktlint()
        trimTrailingWhitespace()
        indentWithSpaces()
        endWithNewline()
    }    
    format 'misc', {
        target '**/*.gradle', '**/*.md', '**/.gitignore'
        indentWithSpaces()
        trimTrailingWhitespace()
        endWithNewline()
    }

    format 'xml', {
        target '**/*.xml'
        indentWithSpaces()
        trimTrailingWhitespace()
        endWithNewline()
    }
}

The main configuration block is spotless, it contains rules for different file formats.

Let’s take a look at Java rules:

target specifies the mask of files for which the rules apply. For example, target '**/*.java' indicates that rules should be applied to all files with the .java extension in all directories.

Next, we list the rules for these files:

googleJavaFormat().aosp()
removeUnusedImports()
trimTrailingWhitespace()
indentWithSpaces()
endWithNewline()

Rules names are self-describing, so I don’t think there is a need to describe any of them.

Next, we set the rules for the other languages — Kotlin, XML and other files, like Markdown.gitignore, or .gradle.

Now we need to include spotless.gradle in our build — for example, in app/build.gradle:

apply from: "$project.rootDir/spotless.gradle"

Now app/build.gradle looks something like this:

apply plugin: "com.android.application"
apply plugin: "kotlin-android"
apply plugin: "kotlin-kapt"

apply from: "$project.rootDir/spotless.gradle"
// ....

Now you have just to synchronize your project with Gradle.

Troubleshooting

Gradle probably will raise this error during the synchronization:

Cannot add task ‘clean’ as a task with that name already exists

If so, you have to remove the following block from your project-level build.gradle:

task clean(type: Delete) {
 delete rootProject.buildDir
}

Setting up Android Studio

We have to change some settings in Android Studio to make it format code the right way.

First of all, install ktlint. On macOS the easiest way to do it is to use Homebrew:

$ brew install ktlint

Next, navigate to your project’s directory in the Terminal and execute this command:

$ ktlint --android applyToIDEAProject

The last thing to do is to disable wildcard imports in Android Studio. Go to the Preferences -> Editor -> Code Style -> Java and check the Use single class import checkbox, then increase Class count to use import with ‘*’ and Names count to use static import with ‘*’ values to something bigger, like 99.

Java Settings

For Kotlin we need just to check the Use single name import radio buttons:

Kotlin Settings

And it’s done!

Using Spotless

Using Spotless is even easier than setting it up. We need just these two commands:

$ ./gradlew spotlessCheck
$ ./gradlew spotlessApply

The first one is checking the code style and fails with an error if there are some problems.

The second one auto-formats the code. You can fix formatting issues in the whole project calling it. But only formatting — it can’t fix issues like wildcard imports, you have to fix them manually.

Now just execute these two commands before every Git push 🙂

Bonus: Travis CI integration

Travis CI

Every serious project uses some CI system, and it’s important to add code style checks in it.

For Travis CI it’s enough just to add ./gradlew spotlessCheck to the script block, and it will check your formatting on every build (and fail if there are some issues). Now your script block should look like this:

script:
 — "./gradlew spotlessCheck"
 — "./gradlew :library:clean :library:build :library:connectedCheck -PdisablePreDex — stacktrace"
 — "./gradlew :app:clean :app:build :app:connectedCheck -PdisablePreDex — stacktrace"

You can find the full config here.

Source code

You can find the full integration in the real project in this repository.

Have a nice day!

Leave a Reply

Your email address will not be published. Required fields are marked *