Issue #257

checkstyle (Java)

Checkstyle is a development tool to help programmers write Java code that adheres to a coding standard. It automates the process of checking Java code to spare humans of this boring (but important) task. This makes it ideal for projects that want to enforce a coding standard.

app/build.gradle

apply plugin: 'checkstyle'

task checkstyle(type: Checkstyle) {
    description 'Check code standard'
    group 'verification'

    configFile file('$project.rootDir/tools/checkstyle.xml')
    source 'src'
    include '**/*.kt'
    exclude '**/gen/**'

    classpath = files()
    ignoreFailures = false
}

tools/chekcstyle

<?xml version="1.0"?><!DOCTYPE module PUBLIC
        "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
        "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
<module name="Checker">
    <module name="FileTabCharacter"/>
    <module name="TreeWalker">

        <!-- Checks for Naming Conventions                            -->
        <!-- See http://checkstyle.sourceforge.net/config_naming.html -->
        <module name="MethodName"/>
        <module name="ConstantName"/>

        <!-- Checks for Imports                                 -->
        <!-- See http://checkstyle.sourceforge.net/config_imports.html-->
        <module name="AvoidStarImport"/>
        <module name="UnusedImports"/>

        <!-- Checks for Size                                          -->
        <!-- See http://checkstyle.sourceforge.net/config_sizes       -->
        <module name="ParameterNumber">
            <property name="max" value="6"/>
        </module>

        <!-- other rules ignored for brevity -->
    </module>
</module>

findbugs (Java)

A program which uses static analysis to look for bugs in Java code

app/build.gradle

apply plugin: 'findbugs'

task findbugs(type: FindBugs) {
    ignoreFailures = false
    effort = "max"
    reportLevel = "low"
    classes = files("$project.buildDir/intermediates/javac")

    excludeFilter = file("$rootProject.rootDir/tools/findbugs-exclude.xml")

    source = fileTree('src/main/java/')
    classpath = files()

    reports {
        xml.enabled = false
        html.enabled = true
        html.destination file("$project.buildDir/outputs/findbugs/findbugs-output.html")
    }
}

tools/findbugs-exclude.xml

<FindBugsFilter>
    <!-- Do not check auto-generated resources classes -->
    <Match>
        <Class name="~.*R\$.*"/>
    </Match>
 
    <!-- Do not check auto-generated manifest classes -->
    <Match>
        <Class name="~.*Manifest\$.*"/>
    </Match>
 
    <!-- Do not check auto-generated classes (Dagger puts $ into class names) -->
    <Match>
        <Class name="~.*Dagger*.*"/>
    </Match>
     
    <!-- http://findbugs.sourceforge.net/bugDescriptions.html#ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD-->
     <Match>
        <Bug pattern="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" />
    </Match>
</FindBugsFilter>

pmd (Java)

PMD is a source code analyzer. It finds common programming flaws like unused variables, empty catch blocks, unnecessary object creation, and so forth

app/build.gradle

apply plugin: 'pmd'

task pmd(type: Pmd) {
    ruleSetFiles = files("${project.rootDir}/tools/pmd-rules.xml")
    ignoreFailures = false
    ruleSets = []

    source 'src'
    include '**/*.kt'
    exclude '**/gen/**'

    reports {
        xml.enabled = false
        html.enabled = true
        html.destination = file("$project.buildDir/outputs/pmd/pmd.html")
    }
}

tools/pmd-rules.xml

<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
         xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
	<exclude-pattern>.*/R.java</exclude-pattern>
    <exclude-pattern>.*/gen/.*</exclude-pattern>

    <rule ref="rulesets/java/basic.xml" />

    <rule ref="rulesets/java/braces.xml" />

    <rule ref="rulesets/java/strings.xml" />

    <rule ref="rulesets/java/design.xml" >
        <exclude name="AvoidDeeplyNestedIfStmts"/>
    </rule>

    <rule ref="rulesets/java/unusedcode.xml" />

</ruleset>

lint

Android Studio provides a code scanning tool called lint that can help you to identify and correct problems with the structural quality of your code without your having to execute the app or write test cases

app/build.gradle

android {
  lintOptions {
        lintConfig file("$project.rootDir/tools/lint-rules.xml")
        htmlOutput file("$project.buildDir/outputs/lint/lint.html")
        warningsAsErrors true
        xmlReport false
        htmlReport true
        abortOnError false
    }
}

tools/lint-rules.xml

<?xml version="1.0" encoding="UTF-8"?>

<lint>
    <issue id="GoogleAppIndexWarning" severity="ignore" />

    <issue id="InvalidPackage" severity="error">
        <ignore regexp="okio.*jar" />
        <ignore regexp="retrofit.*jar" />
    </issue>

    <!-- Disable the given check in this project -->
    <issue id="IconMissingDensityFolder" severity="ignore" />
 
    <!-- Change the severity of hardcoded strings to "error" -->
    <issue id="HardcodedText" severity="error" />
</lint>

Strict mode

import android.app.Application
import android.os.StrictMode

class App: Application() {
    override fun onCreate() {
        super.onCreate()

        enableStrictMode()
    }

    private fun enableStrictMode() {
        if (BuildConfig.DEBUG) {
            StrictMode.setThreadPolicy(
                StrictMode.ThreadPolicy.Builder()
                    .detectAll()
                    .penaltyLog()
                    .build()
            )

            StrictMode.setVmPolicy(
                StrictMode.VmPolicy.Builder()
                    .detectAll()
                    .penaltyLog()
                    .build()
            )
        }
    }
}

Version code

tools/grgit.gradle

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'org.ajoberstar:grgit:1.5.0'
    }
}

import org.ajoberstar.grgit.Grgit

ext {
    git = Grgit.open(currentDir: projectDir)
    gitCommitCount = git.log().size()
}

task printVersion() {
    println("Commit count: $gitCommitCount")
}

app/build.gradle

android {
    defaultConfig {
        versionCode gitCommitCount
    }
}

Obfuscation

To make your app as small as possible, you should enable shrinking in your release build to remove unused code and resources. When enabling shrinking, you also benefit from obfuscation, which shortens the names of your app’s classes and members, and optimization, which applies more aggressive strategies to further reduce the size of your app

When you use Android Studio 3.4 or Android Gradle plugin 3.4.0 and higher, R8 is the default compiler that converts your project’s Java bytecode into the DEX format that runs on the Android platform

app/build.gradle

android {
  buildTypes {
        debug {
            signingConfig signingConfigs.debug
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), '$project.rootDir/tools/proguard-rules-debug.pro'
        }

        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), '$project.rootDir/tools/proguard-rules.pro'
        }
    }
}

tools/proguard-rules.pro

-ignorewarnings

# Remove logs
-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(java.lang.String, int);
    public static int v(...);
    public static int i(...);
    public static int w(...);
    public static int d(...);
    public static int e(...);
}

tools/proguard-rules-debug.pro

-ignorewarnings
-dontobfuscate
-dontoptimize
-ignorewarnings

More proguard snippets https://github.com/krschultz/android-proguard-snippets

quality

tools/quality.gradle

task findbugs
task pmd
task checkstyle

app/build.gradle

apply from: "$project.rootDir/tools/quality.gradle"

File format

You don’t need to use ktlint or detekt to ensure that your code is formatted consistently. Simply enable “File is not formatted according to project settings” in the inspection settings.

ktlint (Kotlin)

An anti-bikeshedding Kotlin linter with built-in formatter

app/build.gradle

apply from: "$project.rootDir/tools/ktlint.gradle"

tools/ktlint.gradle

repositories {
    jcenter()
}

configurations {
    ktlint
}

dependencies {
    ktlint "com.pinterest:ktlint:0.32.0"
    // additional 3rd party ruleset(s) can be specified here
    // just add them to the classpath (e.g. ktlint 'groupId:artifactId:version') and
    // ktlint will pick them up
}

task ktlint(type: JavaExec, group: "verification") {
    description = "Check Kotlin code style."
    classpath = configurations.ktlint
    main = "com.pinterest.ktlint.Main"
    args "src/**/*.kt", "--reporter=checkstyle, output=${buildDir}/outputs/ktlint.xml"
}


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

.editorconfig

[*.{kt,kts}]
indent_size=4

detekt (Kotlin)

Static code analysis for Kotlin

build.gradle

buildscript {}

plugins {
    id "io.gitlab.arturbosch.detekt" version "1.0.0-RC14"
}

allprojects {}

tools/detekt.gradle

detekt {
    toolVersion = "1.0.0-RC14"
    input = files("src/main")
    filters = ".*/resources/.*,.*/build/.*"
    baseline = file("${project.rootDir}/tools/detekt-baseline.xml")
    config = files(file("$project.rootDir/tools/detekt.yml"))
}

tools/detekt.xml

The intention of a whitelist is that only new code smells are printed on further analysis. The blacklist can be used to write down false positive detections (instead of suppressing them and polute your code base).

<SmellBaseline>
    <Blacklist>
        <ID>CatchRuntimeException:Junk.kt$e: RuntimeException</ID>
    </Blacklist>
    <Whitelist>
        <ID>NestedBlockDepth:Indentation.kt$Indentation$override fun procedure(node: ASTNode)</ID>
        <ID>TooManyFunctions:LargeClass.kt$io.gitlab.arturbosch.detekt.rules.complexity.LargeClass.kt</ID>
        <ID>ComplexMethod:DetektExtension.kt$DetektExtension$fun convertToArguments(): MutableList&lt;String&gt;</ID>
    </Whitelist>
</SmellBaseline>

tools/detekt.yml

detekt uses a yaml style configuration file for various things:

autoCorrect: true

build:
  maxIssues: 10
  weights:
    # complexity: 2
    # LongParameterList: 1
    # style: 1
    # comments: 1

Run

./gradlew detekt

check

app/build.gradle

check.dependsOn 'checkstyle', 'findbugs', 'pmd', 'lint', 'ktlint', 'detekt'

Run

./gradlew check

Gradle Kotlin DSL

Reference