[Ultracode Munich #4] Short introduction to the new Android build system including Android Studio,...
-
Upload
bemyapp -
Category
Technology
-
view
925 -
download
0
description
Transcript of [Ultracode Munich #4] Short introduction to the new Android build system including Android Studio,...
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?