How do you add KtLint to a multimodular Android project?

Shubham Kumar Gupta
6 min readFeb 14, 2024

If you’re here, that means you’re also stuck over this. Let’s add it then!

When collaborating and building something, keeping code clean and tidy is crucial. In this guide, we’ll explore how to use ktlint, a helpful tool that checks your Kotlin code for style and consistency, along with pre-commit hooks to maintain high code quality automatically on commit. We’ll explain why ktlint is useful, explain how a linter works, explore Git hooks, and provide step-by-step instructions with easy-to-understand code examples. This helps locally improve code and push a proper linted code.

Why ktlint?

Code consistency is crucial for collaboration, readability, and maintainability. ktlint, a Kotlin linter, automates the process of enforcing coding standards, helping developers adhere to a unified style. It ensures that the codebase remains clean and organized and it follows a common style pattern for everyone. Let’s dive into how to integrate ktlint into your Android project.

How does a Linter work?

A linter, short for code linter or static code analyzer, is a tool that scans your source code for potential issues, style violations, and other patterns that might lead to errors. ktlint, specifically designed for Kotlin, performs static analysis on your code, highlighting deviations from the defined coding standards.

Check this article https://ogutdgnn.medium.com/what-is-linting-how-does-a-linter-work-49381f28fc60

Understanding Git Hooks:

Git hooks are scripts that Git executes before or after events such as commits, pushes, and merges. Git hooks allow developers to automate tasks or specific actions taken at different stages of the development workflow. In our case, we’ll focus on pre-commit hooks, which run before a commit is finalized. There is a directory that stores default pre-commit, pre-push, etc. It is located in .git > hooks > pre-commitso we need to add something over here to enable this git hook.

check this out : https://codeburst.io/understanding-git-hooks-in-the-easiest-way-bad9afcbb1b3

Step-by-Step Ktlint Implementation:

1. Project-level Gradle Setup:

In your project-level build.gradle file, apply the ktlint plugin, and set up Git hooks. Here, we have two tasks registered “copyGitHooks” and “installGitHooks”. Here “copyGitHooks” copies the pre-commit script from a specified location to the .git/hooks/ directory within the project. Here “installGitHooks” installs the pre-commit git hooks by setting executable permissions.

// Groovy
// Project-level build.gradle
plugins {
id("org.jlleitschuh.gradle.ktlint") version "12.1.0" apply false
}

allprojects { // to run the ktlint Format on all modules
task copyGitHooks(type: Copy) {
description = "Copies the git hooks from /git-hooks to the .git folder."
group = "git hooks"
from "$rootDir/scripts/pre-commit" // make sure you pre-commit reside here
into "$rootDir/.git/hooks/"
}

task installGitHooks(type: Exec) {
description = "Installs the pre-commit git hooks from /git-hooks."
group = "git hooks"
workingDir rootDir
commandLine "chmod", "-R", "+x", ".git/hooks/"
dependsOn copyGitHooks
doLast {
logger.info("Git hook installed successfully.")
}
}
afterEvaluate { project -> // to run the ktlint Format on all modules
if (project.tasks.findByName("preBuild") != null) {
project.tasks.named("preBuild") { dependsOn "installGitHooks" }
}
}
}
// Kotlin DSL
// Project-level build.gradle.kts
plugins {
id("org.jlleitschuh.gradle.ktlint") version "12.1.0" apply false
}

allprojects { // to run the ktlint Format on all modules
tasks.register("copyGitHooks", Copy::class) {
description = "Copies the git hooks from /git-hooks to the .git folder."
group = "git hooks"
from("$rootDir/scripts/pre-commit") // make sure you pre-commit reside here
into("$rootDir/.git/hooks/")
}

tasks.register("installGitHooks", Exec::class) {
description = "Installs the pre-commit git hooks from /git-hooks."
group = "git hooks"
workingDir = rootDir
commandLine("chmod", "-R", "+x", ".git/hooks/")
dependsOn("copyGitHooks")
doLast {
logger.info("Git hook installed successfully.")
}
}
afterEvaluate {
tasks.findByName("preBuild")?.let {
it.dependsOn("installGitHooks")
}
}
}

2. Module-level Gradle Setup:

Open your module-level build.gradle file and apply the ktlint plugin. Then, customize the configuration according to your project's requirements. Make sure you make android = true if you are working on Android. Marking outputToConsolewill make sure that it will show output in the terminal on the build. Here we see baseline. A baseline file contains the existing code style, allowing you to gradually enforce ktlint rules without fixing existing code. In scripts we can store our pre-commit file

// Groovy
// Module-level[:app , :common ] build.gradle

apply plugin: 'org.jlleitschuh.gradle.ktlint'

dependencies {
// Add your dependencies here
}

ktlint {
version = "0.48.2"
debug = true
verbose = true
android = true
outputToConsole = true
outputColorName = "RED"
ignoreFailures = true
enableExperimentalRules = true
additionalEditorconfig = [ // not supported until ktlint 0.49
"max_line_length": "20"
]
disabledRules = ["final-newline"] // not supported with ktlint 0.48+
baseline = file("${projectDir}/config/ktlint/baseline.xml")
reporters {
reporter "plain"
reporter "checkstyle"
reporter "sarif"
}
kotlinScriptAdditionalPaths {
include fileTree("scripts/")
}
filter {
exclude("**/generated/**")
include("**/kotlin/**")
}
}
// Kotlin DSL
// Module-level[:app , :common ] build.gradle.kts
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType

plugins {
id("org.jlleitschuh.gradle.ktlint")
}

ktlint {
version.set("0.48.2")
debug.set(true)
verbose.set(true)
android.set(true)
outputToConsole.set(true)
outputColorName.set("RED")
ignoreFailures.set(true)
enableExperimentalRules.set(true)
additionalEditorconfig.set( // not supported until ktlint 0.49
mapOf(
"max_line_length" to "20"
)
)
disabledRules.set(setOf("final-newline")) // not supported with ktlint 0.48+
baseline.set(file("${projectDir}/config/ktlint/baseline.xml"))
reporters {
reporter(ReporterType.PLAIN)
reporter(ReporterType.CHECKSTYLE)
}
kotlinScriptAdditionalPaths {
include(fileTree("scripts/"))
}
filter {
exclude("**/generated/**")
include("**/kotlin/**")
}
}

3. Pre-Commit Hook Script:

Create a ‘pre-commit’ (no extension) file inside the ‘scripts’ folder at the project’s top level. This script will run ktlint before each commit:

#!/bin/bash
echo "............Starting ktlint check......................................"
echo "Started Formatting...."
./gradlew ktlintFormat
echo "Completed.............................................................."

4. Git Configuration:

Enable Git hooks by running the following command in the terminal once to enable git hook.

git config core.hooksPath .git/hooks

5. Generate ktlint Baseline:

For each module, generate a ktlint baseline to capture the existing code style via the terminal

./gradlew :yourModuleName:ktlintGenerateBaseline

via GUI; we can hover over the sections of the task and select “module > help > ktlintGenerateBaseline”

6. Bonus

There is a plugin that can help one to view lint errors in ktlint file and can be easy to setup for a small project. Here’s the link for the plugin which can be installed from the plugins section then selecting install from disk.

Ktlint Plugin: https://plugins.jetbrains.com/files/15057/287040/ktlint-0.12.0.zip?updateId=287040&pluginId=15057&family=INTELLIJ

Time to Test:

Opening “MainActivity” file and replace it like this

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}

// Added Extra line


}

Now try to commit and see if this gets auto-fixed or not.

Conclusion:

By integrating ktlint and pre-commit hooks into your Android project, we laid the foundation for consistent, high-quality code. ktlint acts as a guardian, ensuring adherence to coding standards, while pre-commit hooks automate the verification process. This enhances collaboration, readability, and productive development experience. Let's make your Android development journey fun. Let ktlint and pre-commit hooks be your allies in code quality. Happy coding!

About the Author

I’m just a passionate Android developer who loves finding creative elegant solutions to complex problems. Feel free to reach out on LinkedIn to chat about Android development and more.

--

--