How to setup Android projects

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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

1
2
3
4
5
6
7
8
9
10
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

1
2
3
4
5
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

1
2
3
4
5
6
7
8
9
10
11
-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

1
2
3
4
-ignorewarnings
-dontobfuscate
-dontoptimize
-ignorewarnings

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

quality

tools/quality.gradle

1
2
3
task findbugs
task pmd
task checkstyle

app/build.gradle

1
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

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

tools/ktlint.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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

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

detekt (Kotlin)

Static code analysis for Kotlin

build.gradle

1
2
3
4
5
6
7
buildscript {}

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

allprojects {}

tools/detekt.gradle

1
2
3
4
5
6
7
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).

1
2
3
4
5
6
7
8
9
10
<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:

1
2
3
4
5
6
7
8
9
autoCorrect: true

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

Run

1
./gradlew detekt

check

app/build.gradle

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

Run

1
./gradlew check

Gradle Kotlin DSL

Reference

Comments