Working in big teams with huge projects requires, among other things, an order and structure to keep a healthy codebase.

We all have worked in projects where there were classes and files with different styles, indenting, unused imports and so on.

Thanks to static analysis, we can avoid it with low effort, and we can even automate the process, in your current machines and in remote executions.

What is static analysis?

Static analysis is the process of inspecting the code without executing the application.

These are some of its usages:

  • Keep a codebase with the same code style in large teams.
  • Ensure a set of rules are obeyed, for example, removing several newlines between methods.
  • Improve scalability and automation as it can be executed in CI pipelines.

There are other measures to keep a stable and scalable codebase are dynamic analysis tools which are run during runtime, for example, unit testing. Let’s stick to static analysis for this post.

Android Studio default static analysis tools

Before learning about how we can implement static analysis tools to an Android project, let’s see first what does Android Studio, and other IntelliJ IDEs, provide by default.

If you are not new with these IDEs, and you use the tools they provide to use Git, you must have noticed the settings icon in the lower part of the Commit tab.

This menu allows the developer to perform some tasks before actually doing the commit.

You can format your code according to the style settings applied in Android Studio, remove unused imports, run the cleanup code command and so on.

I recommend you check this out before getting your hands dirty with Ktlint and Git Hooks because this could be enough for what you need.

What is Ktlint?

Ktlint is a static analysis tool that’s already optimized for Kotlin. You can set your own rules or modify existing ones, but I like to keep it as it comes by default, as it’s more than enough for me.

There’s an official guide on how to apply custom rules, but as this is something I have not done myself, I’ll just reference it for you.

The only thing you need to do is configure your Gradle project to use it and then run the tasks. Later in the post we will see how to do this.

One of the great things about this is that you can also use it in your KMM projects, the same you would do with an Android one.

But now, how can we automate it so that Ktlint runs every time a commit is performed, to ensure that our static analysis tool runs even when the developer forgets about it? That’s where Git Hooks come into place.

What are Git Hooks?

Imagine you are like me, and you have the memory of a three-year-old, well, that’s what Git Hooks are good for, because they are going to run scripts automatically based on a trigger.

There are two types of Git Hooks, client and server side. A client Git Hook would be a commit or a merge, whereas a server side could be pushing to the remote repository.

These scripts are stored in the .git/hooks hidden folder, and there are by default some examples loaded every time a Git repository is created.

You can also store your scripts in other folder, but you have to modify the config file inside .git and add a new entry in the [core] section like so hooksPath = hooks with this command: git config — local core.hooksPath <folderPath>.

There are a lot of hooks that you can check out in the official documentation, but for our case, we are going to use the one that runs before a commit, called, as you may have imagined, pre-commit.

We are going to run our static analysis tool before performing a commit, and if it returns errors, the commit is going to be aborted.

In theory, you can use different languages to code the script, but for this tutorial I’m going to use bash, as it’s the one that I know and I think is the most used.

Another good idea could be running your tests before pushing to the remote repository, using a server-side hook. That’s out of reach for this article, but I just wanted to leave it as a note.

Combine them in your Android project

Now, let’s get our hands dirty and check how we can implement this feature in an Android project.

I’m going to use a template project that I made some time ago, you can find the code there or in the snippets that are added below (the template is linked at the end of the article).

First things first, let’s add our Ktlint dependency. In my case, I’m using version catalogs, so we would add it like so:

ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "10.2.0" }
TOML

If you want to learn about what version catalogs are, you can check this article where I talk about them.

After that, we need to go to the build.gradle.kts file of whatever module we want to run Ktlint over, so that it looks like the following snippet.

import org.jlleitschuh.gradle.ktlint.KtlintExtension

plugins {
    ...
    alias(libs.plugins.ktlint)
}

android {
    
    ... 

    sourceSets {
        getByName("main") {
            java.srcDirs("src/main/kotlin")
        }
    }
}

configure<KtlintExtension> {
    android.set(true)
}
Kotlin

Once this configuration has synced, several Gradle tasks are going to generate. The two more relevant for our use case are ktlintCheck and ktlintFormat.

The first one is passive, meaning it will run the static analysis tool and then give you a report, meanwhile the second one is going to format the code accordingly with the rules set in Ktlint.

There are things it can’t fix, and you would have to change them manually, but overall, it works pretty well.

Now, to run those Gradle tasks every time a commit is performed, we need to set up our pre-commit hook.

First, we need to create a folder to store hooks, although you can use the default one mentioned before.

You can create it in the root of your project with the name .githooks, create there a file called pre-commit and give it execution permissions with this command chmod +x pre-commit.

After that, we need to tell Git where the hooks are now stored with this command, git config -local core.hooksPath .githooks.

The bash script is going to run the Gradle task and depending on the result is going to output an error or not, interrupting the commit process.

#!/bin/bash

echo "--------------------------------------"
echo "Running static analysis with Ktlint"
echo "--------------------------------------"

CMD="./gradlew ktlintFormat";
RESULT=$?

# return 1 exit code if Ktlint fails
$CMD
RESULT=$?
if [ $RESULT -ne 0 ]; then
    echo "--------------------------------------"
    echo "        --------------------------------------      "
    echo 1>&2 "Ktlint found violations it could not fix"
    exit 1
fi
echo "Ktlint ran succesfully"
exit 0
Bash

Now you can check your code before saving your changes to Git automatically! 🎉

If you need to see the full code, or you are interested in the Android project I mentioned before, you can check it out in the following post where I talk about it.

https://levelup.gitconnected.com/an-android-studio-template-project-for-your-android-apps-ca9ed9002f5

Featured image by Chris Liverani on Unsplash


If you want to read more content like this without ads and support me, don’t forget to check my profile, or give Medium a chance by becoming a member to access unlimited stories from me and other writers. It’s only $5 a month and if you use this link I get a small commission.

Categorized in: