[Ultracode Munich #4] Short introduction to the new Android build system including Android Studio,...

Preview:

DESCRIPTION

By Thomas Endres & Andres Würl both Senior Consultant from TNG Technology Consulting https://www.tngtech.com Join the Ultracode Munich meetup: http://www.meetup.com/Ultracode-Munich/

Citation preview

New Android build systemFlavored with Roboguice and Robolectric

, Ultracode Meetup, 2013-11-13Andreas Würl Thomas Endres

OverviewA short introduction

New Android build systemRoboguice

Robolectric

The speakersAndreas Würl is an IT consultant for TNG Technologyconsulting currently working in Unterföhring. In his freetime, he is contributing to the Blitzortung app available forAndroid and in development for iOS.

Thomas Endres is also an IT consultant for TNGTechnology consulting. In his free time, he is developingsoftware for controlling drones with bare hands, buildingapps and contributing to HTML5 frameworks.

Our apps

Blitzortung

Simple to use map based application visualizing real timelightning data provided by blitzortung.org. The currentthunderstorm situation at your fingertips.

Be Quiet - The noise alert

Whether you work in an office or in a class room, BeQuiet will help you reduce noise. When the volume is toohigh, it will blink and play a siren sound.

OverviewA short introduction

New Android build systemRoboguice

Robolectric

Building Android applicationsOld school

Based on AntNo built-in dependency managementQuite inflexibleUsing old built-in library versionsNo support for real unit testsTest project needed for instrumentation tests

The build xml file<target name="compile" depends="-resource-src, -aidl" description="Compiles project's .java files into .class files"> <!-- ... --> <javac encoding="ascii" target="1.5" debug="true" extdirs="" destdir="${out.classes.absolute.dir}" bootclasspathref="android.target.classpath" verbose="${verbose}" classpath="${extensible.classpath}" classpathref="android.libraries.jars"> <src path="${source.absolute.dir}" /> <src path="${gen.absolute.dir}" /> <src refid="android.libraries.src" /> <classpath> <fileset dir="${external.libs.absolute.dir}" includes="*.jar" /> <fileset dir="${extensible.libs.classpath}" includes="*.jar" /> </classpath> </javac></target>

Customization is very difficult

Building Android applicationsThe alternative

Based on Maven → Maven pluginAllows for dependency managementA lot more flexible→ But still far from being perfectReal unit tests are possibleStill using a test project for instrumentation tests

The POM file<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.tngtech.internal.android</groupId> <artifactId>android-demo</artifactId> <version>1.0-SNAPSHOT</version> <packaging>apk</packaging>

<dependencies> <dependency> <groupId>com.google.android</groupId> <artifactId>android</artifactId> <version>${platform.version}</version> <scope>provided</scope> </dependency> </dependencies>

<build> <plugins> <plugin> <groupId>com.jayway.maven.plugins.android.generation2</groupId> <artifactId>android-maven-plugin</artifactId> <version>3.7.0</version> <configuration> <androidManifestFile>${project.basedir}/AndroidManifest.xml</androidManifestFile> <assetsDirectory>${project.basedir}/assets</assetsDirectory> <resourceDirectory>${project.basedir}/res</resourceDirectory> <sdk><platform>18</platform></sdk> </configuration> </plugin> </plugins> </build></project>

Building Android applicationsThe new way

Based on Gradle → Gradle-PluginBuilt-in dependency managementUsing common Java patterns→ But flexible enough to change thatReal unit tests through pluginsInstrumentation tests within the same project

The Gradle build filebuildscript { repositories { mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:0.6.+' }}

apply plugin: 'android'

repositories { mavenCentral()}

dependencies { // Put all dependencies here}

android { compileSdkVersion 18 buildToolsVersion "18.1.1"

defaultConfig { minSdkVersion 18 targetSdkVersion 18 }}

Android Studio

Android StudioThe facts

Based on IntelliJ

Ready to use

No additional plugins needed

Brings shortcuts for AVD and SDK manager

Out of the box support for the new build system

Possibility to migrate old projects

OverviewA short introduction

New Android build systemRoboguice

Robolectric

What the heck is Roboguice?A dependency injection containerAn implementation of JSR 330A fork of the Guice framework for the JDKEasy to configure and to use

Dependency injectionInstead of taking

be given

public class MainActivity extends Activity{ private LocationManager locationManager;

public void onCreate(Bundle savedInstance) { // ... locationManager = (LocationManager)

getSystemService(Activity.LOCATION_SERVICE);

}

}

public class MainActivity extends RoboActivity{ @Inject

private LocationManager locationManager;

public void onCreate(Bundle savedInstance) { // ... }

}

Principles of DIDon't let a class create objects on its ownInstead, pass them the objects they needThen you can exchange them for test purposesYou can pass in test doublesBut you can also exchange the "real" object easily

Loose coupling becomes a reality

How can you inject objects?Through the constructor:

Into the field itself:

Into a property:

@Inject public MainActivity(LocationManager locationManager) { // ... this.locationManager = locationManager; }

@Inject private LocationManager locationManager;

@Inject public void setLocationManager(LocationManager locationManager) { this.locationManager = locationManager; }

What can be injected?Arbitrary objects with a zero-arg constructor

Objects with a constructor managed by Roboguice

Views:

Resources:

A lot of standard Android objects:

LocationManager, AssetManager, ...

AlarmManager, NotificationManager, ...

Vibrator

@InjectView(R.id.specialButton) private Button button;

@InjectResource(R.drawable.specialPicture) private Drawable picture;

Robo* classesFor DI to work, you have to extend the robo classes:

Use them instead of the standard Android classes

RoboActivity instead of ActivityRoboListActivity instead of ListActivityRoboService instead of ServiceRoboFragment instead of Fragment...

Injecting providers:Sometimes, you need more than one object of a class

public class SomeObjectProvider implements Provider<SomeObject> {

@Inject

private SomeOtherObject someOtherObject;

@Override

public SomeObject get() {

return new SomeObject(someOtherObject);

}

}

private class SomeObjectUser {

@Inject

private Provider<SomeObject> someObjectProvider;

private SomeObject getObject() {

return someObjectProvider.get();

}

}

Injecting injectorsYou can also inject an injector

Then, you can get arbitrary objects out of the injector

@Inject

private Injector injector;

public <T> T giveMeAnObjectOf(Class<T> clazz) { return injector.getInstance(clazz); }

ConfigurationBy defining a module, you can configure the objects injected

public class SomeModule extends AbstractModule { @Override

public void configure() { // Bind an interface to a specific class bind(SomeInterface.class).to(SomeImplementation.class);

// Bind a standard provider to the class bind(SomeClass.class).toProvider(SomeClassProvider.class);

}

}

Modules are discovered via "roboguice_modules.xml"<?xml version="1.0" encoding="utf-8"?><resources>

<string-array name="roboguice_modules">

<item>com.mypackage.SomeModule</item>

</string-array>

</resources>

Integrate Roboguice (1)Add roboguice to the compile dependencies:

Extend the Robo* classes in your objects:

Inject your dependencies:

// build.gradle dependencies {

// ... compile 'roboguice:roboguice:2.+'

}

public class SomeActivity extends RoboActivity { // ... }

@Inject

private SomeObject someObject;

Integrate Roboguice (2)Configure the module:

Register the module:

Write unit tests:

public class SomeModule extends AbstractModule { @Override

protected void configure() { bind(SomeClass.class).toProvider(SomeClassProvider.class);

// ...

}

}

<?xml version="1.0" encoding="utf-8"?><resources>

<string-array name="roboguice_modules">

<item>com.mypackage.SomeModule</item>

</string-array>

</resources>

public class SomeActivityTest { // How to do that?

}

OverviewA short introduction

New Android build systemRoboguice

Robolectric

Android testingin new build system

Based on JUnit3Requires separate test projectRequires emulator or device for executionLacks real mockingBut initial support for some frameworks

Can I run tests locally?No. It's impossible!

Any method of the SDK will throw the followingexception when called:

java.lang.RuntimeException: Stub! at android.*

Why is that?

Android SDK jars for development only contain method stubs

Is there a solution?

Yes! Use Robolectric

What the heck is Robolectric?Android SDK wrapper/enabler for local test executionJust another dependency of your projectSometimes dependency order is importantUses some magic to enable use of the stubbed SDK jarsUnfortunately not yet complete

What do I get?Tests are running on the dev machineCurrent version of JUnit 4 is usedAny Mock- or Match-Framework can be usedCan be used in parallel with instrumentation tests

How do I enable Robolectric?buildscript { repositories { mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:0.6.+' classpath 'com.squareup.gradle:gradle-android-test-plugin:0.9.+' }}

apply plugin: 'android'apply plugin: 'android-test'...

How do I implement a test?Just use the RobolectricTestRunner

@RunWith(RobolectricTestRunner.class) class SomeActivityTest { @Before public void setUp() { // Preparation for every test }

@Test public void testSomething() { // Your test code belongs here

assertThat(1, is(not(2)); } }

Basic conceptsShadows

TextView textView = (TextView) mainActivity.findViewById(R.id.helloWorld); final ShadowTextView shadowTextView = Robolectric.shadowOf(textView);

assertThat(shadowTextView.innerText(), is("Hello World!"));

Implementation in Robolectric @Implements(TextView.class) public class ShadowTextView extends ShadowView { @RealObject TextView realTextView;

@Override public String innerText() { CharSequence text = realTextView.getText(); return (text == null || realTextView.getVisibility() != View.VISIBLE) ? "" : text.toString(); }

@Implementation public void setPaintFlags(int paintFlags) { this.paintFlags = paintFlags; } }

Basic conceptsRobolectric builds up a full application contextActivities can be built

Testing resource access is possible as well

Modify preferences for tests

activity = Robolectric.buildActivity(MainActivity.class).create().get();

Resources resources = Robolectric.application.getResources(); assertThat(resources.getColor(R.color.Red), is(0xffff0000));

SharedPreferences defaultSharedPreferences = ShadowPreferenceManager.getDefaultSharedPreferences( Robolectric.application);

defaultSharedPreferences.edit() .putBoolean("test", true).putFloat("limit", 1.0f).apply();

But ...Android Studio integration is not yet availableTests can be run via gradle task 'test'

IDE support only through ugly hacks

> gradle test

SummaryThe new build system is a lot more flexible than the old oneAndroid Studio is a cool new tool for app developmentIt comes bundled with the SDK, you can start developmentimmediatelyBut there are still some issues with itRoboguice makes it possbible to decouple your applicationRobolectric can be used for local test execution

Thank you!Are there any questions?

, andreas.wuerl@tngtech.com thomas.endres@tngtech.com

Recommended