#BABBQAmsterdam The other Android getting started guide: Gradle power

Post on 12-Apr-2017

556 views 6 download

Transcript of #BABBQAmsterdam The other Android getting started guide: Gradle power

THE OTHER ANDROID GETTING STARTED GUIDE: GRADLE POWER

Javier de Pedro López

StrengthsExperience

Android Developer at Mobgen

2 Years professionally 2 Different companies

Time spent in 4 Projects Free time

Design Patterns Gradle

Clean architecture Code quality

@droidpl javier.pedro@mobgen.com javierdepedrolopez@gmail.com

`Why the other getting started guide?

Beginner Junior

Medior

Getting started

The other getting started

This talk

Gradle in Android

Continuous Integration

Android Testing and

Unit testing

Code coverage DocumentationPut it all together

GRADLE IN ANDROID: THE PLUGIN UNIVERSE Round 1

• Google I/O 2013 (Android Studio)

• Gradle: default build system

• Configuration in Groovy

• Gradle version 2.4 (out of 2.8)

• Android plugin 1.3.0 (exp 1.5.0-beta1)

Gradle in Android

Context

The project

Closures / DSL

TASKS

Gradle in Android

Context

The project

Closures / DSL

TASKS

/—— (project) —build.gradle —settings.gradle —/app (module)

—build.gradle

Android Studio Project

gradle project ($project)

apply from apply plugin

android { }

org.gradle.api.Project

root project ($rootProject)allprojects

Gradle in Android

The project

Closures / DSL

TASKS

Context

myExtension { test true inner { test false } }

DSL (Domain Specific Language)class MyExtension { InnerObject obj def test(boolean value){} def inner(Closure closure){ project.configure(obj, closure) } }

Closure

project.task(‘copy’, type: Copy, Closure)

task('copy', type: Copy) { from(file('srcDir')) into(buildDir) }

Plugin extension

project.extensions.create(‘android”, Ex)

android {

}

Gradle in Android

The project

Closures / DSL

TASKS

Context

Android DSLandroid { ...

... }

buildTypes { debug { } release { }

}

Build types

productFlavors { pro {

dimension 'version' } x86 {

dimension ‘abi' }

}

Flavors

flavorDimensions 'abi', 'version' Dimensions

Variant == Flavor(s) + BuildType

Gradle in Android

The project

Closures / DSL

TASKS

Context

org.gradle.api.Task

Execute task

./gradlew app:myTask

Android StudioDefine tasks

task mytask << { println "Ending" }

mytask.doFirst { println "Beginning" }

mytask.dependsOn(assembleDebug)

Gradle in Android

The project

Closures / DSL

TASKS

Context

Sample execution (mytask). . . :app:generateDebugResValues UP-TO-DATE :app:generateDebugResources UP-TO-DATE :app:mergeDebugResources UP-TO-DATE :app:processDebugManifest UP-TO-DATE :app:processDebugResources UP-TO-DATE :app:generateDebugSources UP-TO-DATE :app:processDebugJavaRes UP-TO-DATE :app:compileDebugJavaWithJavac :app:compileDebugNdk UP-TO-DATE :app:compileDebugSources :app:preDexDebug :app:dexDebug :app:validateDebugSigning :app:packageDebug :app:zipalignDebug :app:assembleDebug :app:mytask Beginning Ending

CONTINUOUS INTEGRATION TIPSRound 2

You shall not pass

CI Tips

CI version code

ci Version name

WhyAndroid versioning problemandroid { ... defaultConfig { versionCode 1

versionName "1.0-SNAPSHOT" } ... }

“Be a hater of hardcoded values, if you can get rid of them"

Solution

• Use your repository for versioning• Use gradle to be smart

CI Tips

CI version code

ci Version name

WhyCount the commit

Android Version code == commit number (why not?)

Code

final String COMMAND = "git rev-list HEAD --first-parent —count"

public int getCommitRevision(){ def count = 0 def commitNumber = COMMAND.execute().text if(!commitNumber.isEmpty()){ count = commitNumber.toInteger() } return count }

Version name

1.0.134(-SNAPSHOT)

CI Tips

ci Version Name

Why

CI version code

public String getTag(){ def tag = "git describe".execute().text.trim() if(tag.isEmpty()){ tag = "v0" } return tag }

//BAMBOO or other CI system def CI_BUILD = System.getenv(“bamboo_buildNumber")

def version = "${getTag}.$CI_BUILD" gradle.taskGraph.whenReady { taskGraph -> if(taskGraph.hasTask(assembleDebug)) { version += "-SNAPSHOT" } }

UNIT TESTING AND ANDROID TESTINGRound 3

Android testing (espresso,...) Unit testing (JUnit)

Android testing / Unit testingWhy two types

Android Testing

Unit Testing

CI tasks

Unit testing

Pros• Runs on the JVM • Uses JUnit • Is lightning fast • Allows TDD

Cons• No real views • Have to mock android.jar

Android testing

Pros• You can have a coffee • Test views • Activity lifecycle • Real behaviour

Cons• SLOW, seriously • Needs a device • Difficult to setup the CI • Automation

The android.jar trouble

Why two types

Android Testing

Unit Testing

CI tasks

• Goes in the src/androidTest folder

• Extend from: ApplicationTestCase, ActivityInstrumentationTestCase2, ActivityUnitTestCase, ServiceTestCase, ProviderTestCase

• Use libraries: AndroidJUnitRunner: JUnit compatible test runner UIAutomator: System tests Espresso: UI testing

Android testing / Unit testing

Why two types

Android Testing

Unit TestingCI tasks

• Goes in the src/test folder

• Uses jUnit 4 standard @Test.

• You can use the default runner@RunWith(BlockJUnit4ClassRunner.class)

• Robolectric 3 (gradle support) @RunWith(RobolectricGradleTestRunner.class) @Config(constants = BuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")

Android testing / Unit testing

Why two types

Android Testing

Unit Testing

CI tasksCI Unit test tasks//One variant ./gradlew app:test{flavor}{buildType}UnitTest

// All variants ./gradlew app:test

CI Android test task//Variant ./gradlew app:connected{flavor}{buildType}AndroidTest

//All flavours ./gradlew app:connectedAndroidTest

//All checks ./gradlew app:connectedCheck

Android testing / Unit testing

CODE COVERAGE WITH JACOCO: NOW IN THE JVMRound 4

Code coverage

Coverage on Android tests

Coverage on Unit tests

Simply works

• Enable it in the build type • Execute the task • Report located on build/reports/coverage/{flavor}/{buildtype}/

testCoverageEnabled true

Report

Merge reports

CI tasks

Code coverage

Coverage on Android tests

Coverage on Unit tests

Not covered (yet)

• https://code.google.com/p/android/issues/detail?id=144664 • Manual task

Merge reports

CI tasks

Gradle saves the day

Code coverage

Coverage on Android tests

Coverage on Unit tests

Merge reports

CI tasks

sourceDirectories = files("src/main/java", "src/debug/java") executionData =

files("$buildDir/jacoco/test$variantNameUnitTest.exec") reports { xml.enabled = false html.enabled = true }

Jacoco report tasktask (name: “create${variantName}UnitTestCoverageReport”,

type: JacocoReport, dependsOn: "test${variantName}UnitTest") {

}

def classes = "$buildDir/intermediates/classes/$flavor/$buildType" classDirectories = project.fileTree( dir: "${baseLocation}", excludes: ['**/R.class', '**/R$*.class', '**/*$ViewInjector*.*', '**/BuildConfig.*', '**/Manifest*.*', '**/*_*Factory.*'] )

Code coverage

Coverage on Android tests

Coverage on Unit tests

Merge reports

CI tasks CI Unit test coverage tasks

Task name provided:./gradlew app:create{flavor}{buildType}UnitTestCoverageReport

CI Android test coverage tasks./gradlew app:create{flavor}{buildType}AndroidTestCoverageReport

ONLY AVAILABLE FOR DEBUG

Code coverage

Coverage on Android tests

Coverage on Unit tests

What if…

"I want one report to rule them all"

Gradle againproject.afterEvaluate { def dest = "$buildDir/outputs/code-coverage/connected/coverage.ec" testDebugUnitTest.jvmArgs "-javaagent:$buildDir/intermediates/jacoco/jacocoagent.jar=append=true,destfile=$destfile" createDebugAndroidTestCoverageReport.dependsOn testDebugUnitTest }

Merge reports

CI tasks

./gradlew clean createDebugAndroidTestCoverageReport

CODE QUALITYRound 5

Code QUAlity

sonarQube

Configuration

CI Tasks

• Provides metrics

• Supports Android

• Reports issues by priority

• Integrates with JIRA, Mantis…

• Implements SQALE

Code QUAlity

sonarQube

Configuration

CI Tasks

SonarQube task

apply plugin : "sonar-runner"

task (name:"codeQuality", dependsOn: "test${variantName}UnitTestCoverageRepor") {

}

sonarRunner { sonarProperties {

$PROPERTIES } }project.findByName("sonarRunner").execute()

doLast {

}

Code QUAlity

sonarQube

Configuration

CI Tasks

SonarQube task

property "sonar.host.url", “$sonarHost" property "sonar.projectKey", "$sonarProjectKey" property "sonar.projectName", "$sonarProjectName"

property "sonar.jdbc.url", "$databaseHost" property "sonar.jdbc.driverClassName", "$databaseDriver" property "sonar.jdbc.username", "$databaseUsername" property "sonar.jdbc.password", "$databasePassword"

property "sonar.sources", "$SOURCES" property "sonar.binaries", "${project.buildDir}/$BINARIES/$flavorName/$buildTypeName" property "sonar.jacoco.reportPath", "${project.buildDir}$JACOCO_PATH/test${variantName}UnitTest.exec"

private final String BINARIES = "/intermediates/classes" private final String JACOCO_PATH = "/jacoco" private final String SOURCES = "src/main"

Code QUAlity

Configuration

CI Tasks

sonarQube

CI execution tasks

./gradlew app:codeQuality

Task name provided:

DOCUMENTATION: WHY NOT DOCLAVA?Round 6

DOcumentation

vs javadoc

Configuration

CI Tasks

• Enhanced look and feel

• Versioning inside documentation

• Customizations using templates

• Embedding in bigger pages

• Extended markup (@hide, @undeprecate…)

DOcumentation

vs javadoc

Configuration

CI Tasks

Doclava task

project.configurations { docLava }

project.dependencies { docLava "com.google.doclava:doclava:1.0.6" }

DOcumentation

vs javadoc

Configuration

CI Tasks

Doclava taskproject.task("create${variantName}Documentation", type: Javadoc) {

}

options { addStringOption "templatedir", "$yourTemplateDir" doclet “com.google.doclava.Doclava"

bootClasspath new File(System.getenv('JAVA_HOME') + "/jre/lib/rt.jar")

docletpath = project.configurations.docLava.files.asType(List) }

title = null

source = variant.javaCompile.source ext.androidJar = “${project.android.sdkDirectory}/platforms/“ +

“${project.android.compileSdkVersion}/android.jar"

classpath = project.files(variant.javaCompile.classpath.files) + project.files(ext.androidJar)

exclude '**/BuildConfig.java' exclude '**/R.java'

DOcumentation

vs javadoc

Configuration

CI Tasks

CI execution tasks

./gradlew app:create{flavor}{buildType}Documentation

Task name provided:

PLUGIN: ALL TOGETHERRound 7

Plugin

how to create one

grill (ALPHA)

Create a groovy project• src/main/groovy• apply plugin: “groovy" • Add dependencies

Declare the plugin• Create src/main/resources/META-INF/gradle-plugins• Choose a name for the plugin • Create name.properties • Add: implementation-class=class.name.of.Plugin

Declare the plugin

• apply plugin: “maven” • ./gradlew plugin:install • apply plugin: "myCustomPlugin"

Plugin

how to create one

grill (alpha)

BBQ Grill Android Plugin https://github.com/droidpl/GrillPlugin

DSL for quality projects

grill { CI { $PROPERTIES } testing { $PROPERTIES } codeQuality { $PROPERTIES } documentation { $PROPERTIES } }

• Multiple variants

• Libraries and apps

• Many tools - one place

Conclusions (CI)

Unit testing

test$variantUnitTest

Android Testingconected$variant

AndroidTest

Unit Testing Code coverage

create$variantUnit TestCoverageReport

Android Testing Code coverage

create$variantAndroid TestCoverageReport

quality

codeQuality

documentationcreate$variant Documentation

Conclusions

Gradle IS OUR FRIEND

Multiple variants PROBLEMS

Configuring everything is hard

Quality measurements matters

Testing EVERYTHING IS possible

Take care of your build logic

……………………Q&A

……………………Thank you!