Integrating detekt in the Android Studio

Nagendran P
9 min readOct 14, 2021

--

detekt, a static code analysis tool for Kotlin, helps you do all of this. It comes with a wide range of rule sets. It provides you with options to configure them, as well as the ability to add your own custom rules. So, adding this as a part of your CI steps can prove to be beneficial. CI stands for Continuous Integration, a process of ensuring features and bug fixes are added to an application regularly.

During the process you’ll learn about:

  • detekt and its features.
  • Adding detekt to your project.
  • Rule sets available in detekt.
  • Writing custom rules and processors.

Understanding detekt

detekt is a static analysis tool for the Kotlin programming language. It tries to improve your codebase by enforcing a set of rules. You can integrate it with your CI to help avoid code smells on your codebase. This is helpful — especially when working with a team of developers.

detekt has several features that make it a worthwhile addition to your project:

  • It offers code-smell analysis for Kotlin projects.
  • It’s highly configurable — you can customize it according to your own needs.
  • You can suppress findings if you don’t want warnings for everything.
  • IDE, SonarQube and GitHub Actions integration.
  • You can specify code-smells threshold to break your builds or print warnings.
  • You can add code-smell baseline and suppression for legacy projects.

Looking at detekt Rule Sets

The DetektSampleApp already includes a list of the rule sets and their descriptions. Rule sets contain a group of rules that check compliance with your code to improve the code quality. The rules don’t affect the functionality of your app. Here are the common rule sets that exist:

  1. Comments: It provides rules that address issues in comments and documentation of the code. It checks header files, comments on private methods and undocumented classes, properties or methods.
  2. Complexity: This set contains rules that report complex code. It checks for complex conditions, methods, expressions and classes, as well as reports long methods and long parameter lists.
  3. Coroutines: The coroutines rule set analyzes code for potential coroutines problems.
  4. Empty-Blocks: It contains rules that report empty blocks of code. Examples include empty catch blocks, empty class blocks, empty function and conditional function blocks.
  5. Exceptions: It reports issues related to how code throws and handles exceptions. For example, it has rules if you’re catching generic exceptions among other issues related to handling exceptions.
  6. Formatting: This checks if your codebase follows a specific formatting rule set. It allows checking indentation, spacing, semicolons or even import ordering, among other things.
  7. Naming: It contains rules which assert the naming of different parts of the codebase. It checks how you name your classes, packages, functions and variables. It reports the errors in case you’re not following the set conventions.
  8. Performance: It analyzes code for potential performance problems. Some of the issues it reports include the use of ArrayPrimitives or misuse of forEach loop, for instance.
  9. Potential-Bugs: The potential-bugs rule set provides rules that detect potential bugs.
  10. Style: The style rule set provides rules that assert the style of the code. This will help keep code in line with the given code style guidelines.

Each rule set contains a vast number of rules. This tutorial covers the most common rules, but don’t hesitate to check the official documentation to learn more about the rules.

Adding detekt To Your Project

You can easily add detekt to new or existing Kotlin projects. For existing projects, you’ll add some more customization to prevent many errors.

Adding to New Projects

detekt is available as a Gradle plugin. For new projects, add the plugin via the Gradle files.

Navigate to the project-level build.gradle file. Add the corresponding classpath in the dependencies block:

classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.18.1"

Here, you define the path that Gradle will use to fetch the plugin.

Next, you need to add the plugin to your project.

Below the dependencies block add this:

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

With this, you apply the detekt plugin to your project. The final step of adding the plugin is to apply the plugin on the app level Gradle file.

Navigate to the app-level build.gradle file. Add this apply line:

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

This enables you to use the plugin in the app module too. With this added, do a Gradle sync. After the sync is complete, your plugin is set and ready to use!

Note: detekt requires Gradle 6.1 or higher. You can check the supported version from the compatibility documentation.

Running detekt Terminal Command

Now that we’re setup, Run the following command to see an initial report on your codebase:

./gradlew detekt

Setting Up A Config

Detekt is highly configurable to meet your team’s requirements. To start tinkering with configuration, generate a config file by running:

$ ./gradlew detektGenerateConfig

Open the newly generated file in <project-root>/config/detekt/detekt.yml to start playing with various configuration options. Here are a few our team has taken advantage of:

  • Setting maxIssues to 0, because why tolerate a codebase that isn’t squeaky clean?
  • Enabling UndocumentedPublicClass and UndocumentedPublicFunction to enforce codebase documentation.
  • Enabling MissingWhenCase to ensure our when statements are exhaustive.
  • Enabling ignoreOverridden on the TooManyFunctions check, so that we don’t have to suppress on Activity and Fragment classes that often require many overrides.

Open detekt.yml. It contains some rule sets and rules properties — for example, the comments section:

comments:
active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
CommentOverPrivateFunction:
active: false
CommentOverPrivateProperty:
active: false
EndOfSentenceFormat:
active: false
endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!:]$)
UndocumentedPublicClass:
active: false
searchInNestedClass: true
searchInInnerClass: true
searchInInnerObject: true
searchInInnerInterface: true
UndocumentedPublicFunction:
active: false

As you can see, in order to enable or disable a given rule, you just have to set the boolean value active: true/false.

If You’re Adding To An Existing Project

The correct time to setup static code checks is always “now”, but what if your project is already in production and you have a couple hundred pre existing detekt warnings? Detekt allows you to specify a baseline file of warnings that will be ignored, so only new warnings cause failures. Here’s how to set it up:

First, run the following command to generate a baseline file:

$ ./gradlew detektBaseline

Then, add the following detekt config in app/build.gradle:

detekt {
// other detekt configuration goes here
baseline = file(“$rootDir/detekt-baseline.xml”)
}

Now, when running ./gradlew detekt, warnings listed in the baseline file will be ignored! You can update the baseline file at any time by re-running ./gradlew detektBaseline.

Suppressing Issues

To prevent detekt from displaying an issue on a specific method, you can use the @Suppress annotation.

To see this in action, add this new method below potentialBugs():@Suppress("EmptyFunctionBlock")
private fun sampleEmptyFuntion() {
}

Writing Custom detekt Rules

As mentioned earlier, detekt allows you to extend its functionality by adding your own rules. Recently, the Kotlin Android Extensions plugin was deprecated. This means using Kotlin Synthetic is no longer recommended. Before the introduction of ViewBinding, this was the go-to way of accessing views in Android apps. This deprecation affects so many projects. Using detekt, you can write a specific rule to check it and fail your build if there is a synthetic import.

Note: You shouldn’t be using the Kotlin Android Extensions in your projects anymore. It’s used here to demonstrate how you can create custom detekt rules.

First, move to the customRules module and add these dependencies to build.gradle:

// 1
compileOnly "io.gitlab.arturbosch.detekt:detekt-api:1.17.1"
// 2
testImplementation "io.gitlab.arturbosch.detekt:detekt-api:1.17.1"
testImplementation "io.gitlab.arturbosch.detekt:detekt-test:1.17.1"
testImplementation "org.assertj:assertj-core:3.19.0"
testImplementation 'junit:junit:4.13.2'

This code:

  1. Adds the detekt API dependency. You need this for writing the custom rules.
  2. Adds test dependencies. To test your rules you need detekt-test. It also requires assertj-core as a dependency.

Do a Gradle sync to add this dependencies. Next, inside the com.raywenderlich.android.customrules.rules package in the customRules module, add a new file. Name it NoSyntheticImportRule.kt and add the following:

package com.raywenderlich.android.customrules.rulesimport io.gitlab.arturbosch.detekt.api.*
import org.jetbrains.kotlin.psi.KtImportDirective
//1
class NoSyntheticImportRule : Rule() {
//2
override val issue = Issue("NoSyntheticImport",
Severity.Maintainability, "Don’t import Kotlin Synthetics "
+ "as it is already deprecated.", Debt.TWENTY_MINS)
//3
override fun visitImportDirective(
importDirective: KtImportDirective
) {
val import = importDirective.importPath?.pathStr
if (import?.contains("kotlinx.android.synthetic") == true) {
report(CodeSmell(issue, Entity.from(importDirective),
"Importing '$import' which is a Kotlin Synthetics import."))
}
}
}

Here’s what’s happening:

  1. NoSyntheticImportRule extends Rule from the detekt API.
  2. This is a method from Rule that defines your issue. In this case, you create an issue named NoSyntheticImport. You specify the debt for the issue, the severity of the issue and the message for detekt to show. The debt represents the time you need to fix the issue.
  3. This method checks imports in your files and classes. Inside here you have a check to see if any of the import contains Kotlin synthetics. If it does, you report the code smell with a message.

Adding Your Own Rule to detekt

With your rule class complete, your need to create a RuleSetProvider. This class lets detekt know about your rule. You create one by implementing RuleSetProvider interface.

On the same package as the file above, create another new file. Name it CustomRuleSetProvider.kt and add the following code:

package com.raywenderlich.android.customrules.rulesimport io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
class CustomRuleSetProvider : RuleSetProvider {
override val ruleSetId: String = "synthetic-import-rule"
override fun instance(config: Config): RuleSet = RuleSet(ruleSetId, listOf(NoSyntheticImportRule()))
}

Implementing this interface provides detekt with a rule set containing your new rule. You also provide an id for your rule set. You can group as many rules as you'd like in this rule set if you have other related rules.

Next, you need to let detekt know about your CustomRuleSetProvider. Navigate to the customRules module. Open src/main/resources/META-INF/services, and you’ll find io.gitlab.arturbosch.detekt.api.RuleSetProvider. Inside this file, add:

com.raywenderlich.android.customrules.rules.CustomRuleSetProvider

Here, you add the full qualified name for your CustomRuleSetProvider. detekt can now find the class, instantiate it and retrieve your rule set. This file notifies detekt about your CustomRuleSetProvider.

Woohoo! You’ve created your first custom rule. It’s time to test if your rule is working.

Testing Your Custom Rule

Inside the test directory in the customRules module, add a new Kotlin file to the package and name it NoSyntheticImportTest.kt. Add the following code:

package com.raywenderlich.android.customrules.rulesimport io.gitlab.arturbosch.detekt.test.lint
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class NoSyntheticImportTest {
@Test
fun noSyntheticImports() {
// 1
val findings = NoSyntheticImportRule().lint("""
import a.b.c
import kotlinx.android.synthetic.main.activity_synthetic_rule.*
""".trimIndent())
// 2
assertThat(findings).hasSize(1)
assertThat(findings[0].message).isEqualTo("Importing " +
"'kotlinx.android.synthetic.main.activity_synthetic_rule.*' which is a Kotlin Synthetics import.")
}
}

In the code above:

  1. You use lint() extension function, which executes the checks and returns the results. Inside the functions you've added two imports. One is compliant and the other one is non-compliant.
  2. Using the results from above, you do an assertion to check if the findings has a size of one. This is because you have one non-compliant import. You also do an assertion to check for message in your findings.

Click the Run icon on the left side of noSyntheticImports(). You'll see your test passes as shown below:

You can see your test for the custom rule passes. This means you can use your rule in a real project. You’ll learn how to do that next.

Note: Don’t worry about the WARNING: An illegal reflective access operation has occurred message. It is relative to this test running on itself. This won't happen when using this rule for the app module.

Using the Custom Rule in Your Project

To use this new rule in your project, you’ll need to apply the customRules module in your app module. Navigate to the app-level build.gradle file and add the following to the dependencies section:

detekt "io.gitlab.arturbosch.detekt:detekt-cli:1.17.1"
detekt project(":customRules")

The first line is relative to detekt-cli dependency. detekt requires it to run your custom rule. The second line tells detekt to use your module as a dependency in order to be able to use your custom rule.

Have a look at the SyntheticRuleActivity class. As you can see, it has Kotlin Synthetic Import. You’ll use this class to test if the rule works.

import kotlinx.android.synthetic.main.activity_synthetic_rule.*

Last, you need to activate your rule set and include your rule in the configuration file. To do this, add the following code to detekt.yml just above the comments section:

synthetic-import-rule:
active: true
NoSyntheticImportRule:
active: true

Run ./gradlew detekt on your terminal. You'll see these results:

detekt now reports an issue under synthetic-import-rule. It shows a debt of 20 minutes and points to the class that has the import.

Congratulations! You’ve made your first custom rule.

Sample Project Link = https://github.com/nagusmart/detekt_sample_app

Thank You

--

--