How to use ktlint and detekt for static analysis of Kotlin code and Android apps

by: | Aug 20, 2020

Imagine you’re working a typical day as a software developer. You open a pull request for a feature you are implementing. One of your coworkers comments that a blank line is missing and says a brace should be there. You also forgot to remove an import that is no longer needed. So, you’ll need to fix these small issues, commit again, and the code will need another review. The edits are really important to keep your code clean and performing well. But these small issues and the related communication are taking up a lot of your development cycles — costing you precious time and the opportunity to work on new features.

What if you could eliminate those issues? That’s where static analysis with ktlint and detekt come into play. Using these two tools with Kotlin for Android apps can help you automate the process of fixing issues like these. ktlint and detekt help identify problems even before opening any pull requests, saving time, and ensuring your code is clean.

Ktlint: Don’t waste your time with code styles anymore

Ktlint is a static analyzer for Kotlin that automatically scans your code for any style, indentation, or line breaks that don’t follow the official Kotlin style defined by Jetbrains. By using ktlint, you ensure quality code that adheres to a pattern — making it easily understandable for new developers and even your current ones. And if you think ktlint sounds like a good idea but you don’t have the time, great news: Ktlint is extremely easy to implement!

Follow these step-by-step instructions to add ktlint to your Kotlin app project:

Step 1: Add this dependency in your module-level gradle file:

dependencies {
  ktlint “com.pinterest:ktlint:0:37:2”
}

Step 2: Update your top-level project file, like this:

repositories {
  jcenter()
}

Step 3: Sync your project and then create a new gradle file, named ktlint.gradle. In this file, paste the following code:

configurations {
  ktlint
}

task ktlint(type: JavaExec, group: "verification") {
  description = "Check Kotlin code style."
  classpath = configurations.ktlint
  main = "com.pinterest.ktlint.Main"
  args "src/**/*.kt"

}

task ktlintFormat(type: JavaExec, group: "formatting") {
  description = "Fix Kotlin code style deviations."
  classpath = configurations.ktlint
  main = "com.pinterest.ktlint.Main"
  args "-F", "src/**/*.kt"
}

Step 4: Sync your project again. Then ktlint and ktlintFormat will be added to the gradle tab in Android Studio. To add it to a particular module, just add the following in your module gradle:

apply from: rootProject.file(‘ktlint.gradle’)

Simple, right? Now let’s explore each step more.

Ktlint.gradle: Check if your code is well-formatted

The ktlint.gradle task is a verifier: It checks all your Kotlin files to ensure you are following the right code style. To run it you double-click the task on the gradle tab. Or, just run the terminal command /gradlew ktlint. The execution is light and very fast (the execution time varies between 5 to 10 seconds). You will see SUCCESS if your code is well-formatted or FAILURE (with the console showing where and why you have errors) if you don’t.

KtlintFormat.gradle: Format your code with one click

Now you’re able to check if your code style is correct, but what about when it’s not? Should you fix any errors one by one? The answer is — thankfully — no! Ktlint provides us another gradle task called ktlintFormat, which searches your Kotlin files for code style issues and automatically fixes them. Although ktlintFormat is a very useful tool, there are some cases where it won’t be able to fix the issues. The task will fix what it can and display a FAILURE status on other errors, along with the reasons why they weren’t fixed.

How ktlint reduces the time of your code reviews

With ktlint, you have a tool to check and fix code style issues — but how does it impact the whole project? At ArcTouch, the implementation of ktlint not only helps us to maintain code quality but also speeds up our code reviews.

With it, we don’t need as many discussions about style, nor reviews related to blank or break lines. Instead, we can focus on the code itself and our architectural patterns. Bonus: When we add someone new to the project, the learning curve for our code style is eliminated. Consistent code style is a huge focus for us at ArcTouch — having everyone follow the same patterns makes our apps faster to build and easier to maintain. And it means our clients can have confidence that any ArcTouch staff member can join a project or fill in for someone who is sick or on vacation — without impacting quality or the schedule.

Consistent code style is achieved by adding the ktlint to the project — and also by running it in our continuous integration (CI) environment, which we explain later.

Detekt: Automated review of code quality

There are other things we use at ArcTouch to automatically ensure code quality and improve developer efficiency. Detekt is another tool that helps us with static analysis of Kotlin files. While ktlint helps with code style issues, detekt goes far beyond that — including analyzing code complexity, potential bugs, code smells, unhandled exceptions, and many other things. As a consequence, it is much more complex to configure.

Follow these step-by-step instructions to add detekt to your Kotlin project:

Step 1: In your top-level project, add:

buildscript {
  repositories {
    jcenter()
  }
}

plugins {
  id("io.gitlab.arturbosch.detekt").version("1.9.0")
}

repositories {
  jcenter()
}

Step 2: Sync your project and run the DetektGenerateConfig task in your gradle tab. This task will create a configuration file placed in /config/detekt/detekt.yml. The yaml file is the standard definition of rules that will guide your analysis.

Step 3: Create a new gradle file named detekt.gradle, referencing the newly created configuration file:

apply plugin: 'io.gitlab.arturbosch.detekt'

detekt {
  config = files("config/detekt/detekt.yml")
}

Step 4: Run the detekt task in your gradle tab. The task will display all the code smells and issues it finds in your code, pointing out the location of the error and suggesting the time needed to solve each issue. If you want to apply the task in a module level, you just need to add the following to the build.gradle of your module:

apply from: rootProject.file('detekt.gradle')

Choosing your detekt ruleset

Detekt has a lot of powerful features with varying complexity defined by the ruleset. You’ll need to take some time to determine which features are best for your Kotlin project.

  • Comments: Rules based on the principle that public things — functions and properties — should always have documentation. Meanwhile, private things should not.
  • Complexity: Rules that define a maximum size for a class/method, the maximum number of methods in classes, and the number of nested blocks. These are very project-dependent rules.
  • Coroutines: Rules to prevent global scope and to avoid redundant suspend modifiers.
  • Empty blocks: Rules to keep your code easy to read and identify any empty block “{}” in any operator — e.g. for, while, function, and when.
  • Exceptions: Rules for issues related to how you throw and handle exceptions.
  • Formatting: Rules for formatting issues. Since detekt uses the default Kotlin rules, this will give you the same report that ktlint gives you.
  • Naming: Rule related to names in your codebase, such as classes, functions, and packages.
  • Performance: Rule for unnecessary variables and forEach on ranges.
  • Potential bugs: Rules for deprecation, redundant elses, and unchecked casts.
  • Style: Rules for code style like collapse if statements, magic numbers, and optional braces.

Each rule can be found in the configuration file you created, and more details about them can be found on the official detekt page.

Adding static analysis to ongoing Android projects

Now you know how static analysis with ktlint and detekt can help your new Android projects. But what if you already have an Android project underway and want to implement these static analysis tools anyway?

Do it. The sooner the better. Your project isn’t going to get any smaller or easier to fix the longer you wait. Although it might seem daunting to implement these tools, the long-term benefits far outweigh the investment in your time now.

In our experience with ktlint — and specifically the ktlintFormat task — the changes it recommends are easy and fast. If you add ktlint mid-project, you might initially see a large number of issues. For example, on one of our projects, ktlint recommended 957 changed files. That’s a significant number, but those issues could be a lot more difficult to address if you wait until later in the project. After its implementation, we had much faster code reviews. There’s no doubt we spared ourselves a lot of time and headache later.

With detekt, you need to take the time to evaluate which rules actually make sense for your project — and focus on those. Like ktlint, the sooner you implement detekt the better. But that doesn’t mean you need to rush into it. In one of our projects, we took almost two weeks to choose the rulesets with the best fit.

With the default configuration, we saw more than 2,000 issues for some projects, with an estimated time of over 10 days to correct! However, much of the complexity in the default ruleset didn’t apply to our project. So after we reduced the applicable rules, the number of issues went down to 185 with an estimated time of 30 hours. Much different.

We were able to split the issues into small tasks, work on them over a few weeks, and ensure our project’s quality was state-of-the-art.

Implementing ktlint, detekt as CI gradle scripts

After implementing those tools you need to make sure every developer on your team runs them before every commit. Manually, this can be an exhausting process that is subject to human error. Luckily, with both ktlint and detekt gradle scripts we can easily automate them to run within a continuous integration (CI) process.

CI: If you use a CI server such as Jenkins or GitLab CI, add both gradle scripts to your pipeline in the step that makes most sense for you. We usually choose to run it before every pull request. They are lightweight tasks that rarely take more than a few seconds to run. And it’s a really small price to pay to ensure quality.

GitHook: If you use Git, you can simply use a git hook. Git allows you to create bash scripts and add them into some parts of the development cycle (such as pre-push, pre-commit, pre-rebase, and others). All you need to do is to create a bash script with pre-commit, for example, and add it into the folder .git/hooks/ of your project. You just need to do it every time you add the repository into a new folder. Here’s an example of a script for ktlint and detekt:

#!/bin/sh

  ./gradlew ktlint && ./gradlew detekt

Now, ktlint and detekt run first with every commit attempt and only proceed if the code passes.

Focus on solving problems and a delightful UX

The goal for this post is to share how to implement static analysis in Android projects to improve code quality in a systematic way. By using ktlint and detekt, you will ensure code quality and save valuable time by automatically finding and fixing minor issues throughout development. Less time doing code fixes means more time delivering new features that can help improve your app’s user experience.


Need help developing your Kotlin app?

Since the dawn of the App Store, ArcTouch has been helping some of the world’s most important businesses build lovable apps for Android. Contact us if you’d like to work with our award-winning Android development team.