Integrating detekt in the Android Studio
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:
- 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.
- 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.
- Coroutines: The coroutines rule set analyzes code for potential coroutines problems.
- 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. - 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.
- 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.
- 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.
- Performance: It analyzes code for potential performance problems. Some of the issues it reports include the use of
ArrayPrimitives
or misuse offorEach
loop, for instance. - Potential-Bugs: The potential-bugs rule set provides rules that detect potential bugs.
- 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
to0
, because why tolerate a codebase that isn’t squeaky clean? - Enabling
UndocumentedPublicClass
andUndocumentedPublicFunction
to enforce codebase documentation. - Enabling
MissingWhenCase
to ensure ourwhen
statements are exhaustive. - Enabling
ignoreOverridden
on theTooManyFunctions
check, so that we don’t have to suppress onActivity
andFragment
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:
- Adds the detekt API dependency. You need this for writing the custom rules.
- Adds test dependencies. To test your rules you need
detekt-test
. It also requiresassertj-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:
NoSyntheticImportRule
extendsRule
from the detekt API.- 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. - 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.RuleSetProviderclass 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.Testclass 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:
- 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. - 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 yourfindings
.
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