2 Modules Android project with Robolectric, Gradle, Android Studio

Start a new Android project this week, so I started to setup the development environment. To be honest, it is not an easy experience to me.

Features

Here are the features I got now:

  • Unit test with Robolectric. (Very fundamental requirement)
  • Unit tests are separated into an independent module. (Provide flexibility when project growing larger. And provide a more clear view of code organization)
  • Running unit tests recognized by Android Studio/IntelliJ unit test plugin and debug UTs in IDE. (This is very important when diagnose failure UT)
  • Running unit tests via CLI with gradle wrapper. (This is necessary requirement for CI servers)
  • Using resources in Robolectric test. (Avoid Resource Not Found exception in unit tests)
  • Test Android Annotation powered async implementation. (AA introduces new Async implementation, which is not supported by Robolectric by default)
  • AssertJ core support. (Fest has been deprecated since no one is maintain it now.)

Versions

The major difficulties that I met are version compatibility, I almost tried all available version combinations to make them all works. Here are the versions that I uses

  • Gradle: 2.1
  • Groovy: 2.3.6
  • Ant: 1.9.3
  • JVM: 1.6.0_65 (Apple Inc. 20.65-b04-462)
  • OS: Mac OS X 10.9.5 x86_64
  • Android Studio: 0.8.11 (build AI-135.1446794)
  • IntelliJ: IDEA 14 CE EAP, build IC-138.2458.8
  • Android gradle plugin: com.android.tools.build:gradle:0.13.0
  • Android ADT gradle plugin: com.neenbedankt.gradle.plugins:android-apt:1.4+
  • Compile SDK version: 20
  • Build tool version: 20.0.0
  • Robolectric: 2.3

Known Issues

My current solution isn’t perfect. But for now, I haven’t working solution for them. Hope it could be fixed in the future

  • AAR is not supported in Unit Test. (A tricky issue, I’ll explain more in detail later)
  • AssertJ-Android is not supported. (Cause by AAR support issue, alternative available.)

Project Structure and configurations

Here are the project structure:

1
2
3
4
5
6
7
8
RootProject
|- settings.gradle
|- build.gradle
|- Launcher
\- builde.gradle
\- UnitTest
|- build.gradle
\- UnitTest.iml

Here are the contents

\Settings.gradle
1
include ':Launcher', ':UnitTest'
\build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.13.0'
}
}
allprojects {
repositories {
mavenLocal()
mavenCentral()
}
}
\Launcher\build.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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.13.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4+'
}
}
repositories {
mavenLocal()
mavenCentral()
}
apply plugin: 'com.android.application'
apply plugin: 'android-apt'
android {
compileSdkVersion 20
buildToolsVersion '20.0.0'
defaultConfig {
minSdkVersion 9
targetSdkVersion 19
versionCode 1
versionName "1.0"
}
buildTypes {
debug {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
apt {
arguments {
androidManifestFile variant.processResources.manifestFile
resourcePackageName 'me.timnew.game.launcher'
}
}
def AAVersion = '3.1'
dependencies {
apt "org.androidannotations:androidannotations:$AAVersion" // Process AA annotations
/*
* Android Studio will remove this line if you try to edit project configuration with GUI.
* It seems it is a bug of Android Studio since it does not understand DSL `apt`
*/
compile "org.androidannotations:androidannotations-api:$AAVersion" // AA Runtime API. Becareful
compile 'de.greenrobot:eventbus:2.2.1'
compile 'org.easytesting:fest-reflect:1.4.1'
compile 'com.google.guava:guava:18.0'
compile 'com.koushikdutta.ion:ion:1.3.8'
compile fileTree(dir: 'libs', include: ['*.jar', '*.aar']) // Well although I mentioned aar here, but it doesn't load correctly.
compile 'com.android.support:support-v4:20.0.0'
compile 'com.android.support:support-annotations:20.0.0'
compile 'com.android.support:appcompat-v7:20.0.0'
}
\UnitTest\build.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
30
31
32
33
34
35
36
37
38
39
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.13.0'
}
}
apply plugin: 'java'
repositories {
mavenLocal()
maven { url "$System.env.ANDROID_HOME/extras/android/m2repository" } // Fix 'com.android.support:*' package not found issue
mavenCentral()
}
dependencies {
def appModule = project(':Launcher')
compile appModule
testCompile appModule.android.applicationVariants.toList().first().javaCompile.classpath // Include classes from main project
testCompile appModule.android.applicationVariants.toList().first().javaCompile.outputs.files
testCompile files(appModule.plugins.findPlugin("com.android.application").getBootClasspath())
testCompile('junit:junit:4.+') {
exclude module: 'hamcrest-core' // Exclude problematic 'hamcrest'
}
testCompile 'org.robolectric:robolectric:2.3'
testCompile 'org.mockito:mockito-core:1.9.5'
testCompile 'org.assertj:assertj-core:1.6.1'
}
tasks.withType(Test) {
scanForTestClasses = false
include "**/*Should.class"
include "**/*Test.class"
include "**/*Tests.class"
}

Why uses java plug-in instead of android unit test plug-ins

Well, Gradle DSL provides a lot of flexibility to developer. But it also brought a lot complexity to IDE implementation. To figure out project configuration, IDE need to parse and understand the gradle scripts. Not only DSLs provided by gradle but all stuff come with plug-ins. From IDE, this is almost an impossible mission. So IDE need to figure out a way to simplify the process, such as support a subset of DSL.

For Android project, IntelliJ has difficulties to understand the all variations of android unit test plug-ins. So it is not easy to make unit test runnable from IDE. To solve the issue, you either try to teach IDE about the DSL by providing plug-in to IDE, or uses some languages that IDE understood.

I tried some plug-in available today, but none of them works for me. So I decide to use java DSL, which IntelliJ understood natively. As a trade off, since java gradle plugin doesn’t understand Android Library, so it cannot import .aar libries.

Besides I tried all android unit test gradle plugins, I found all of them depends on android gradle plugin. And android plugin depends on AndroidManifest.xml and some other stuff. It is wield to provide an manifest for your unit test.

So as the final solution, I uses java plug-in, avoid using aar in the test.

Why so complicate

Configurate an working Android project isn’t as easy as it sounds. Differ from iOS community, Google isn’t strong-minded as Apple. As a consequence that Android community is fragmented and lack of unified solution. There are tons of solutions available, but you might need to try them one by one to figure out which fits your requirement.

To make your tool chain, dependencies, IDE work together, compatibility is always your enemy. Even Google has to publish Version Compatibility Table to mitigate the pain.

What a mess!!!!!

References posts, plugins or template projects

Here is a list of things that I tried but failed to fit my requirement. List here since it might be helpful to other people.

Trello Confetti Effect

Trello just created a page to celebrate their user number reaches 5 million. In their celebration page, they introduce an interesting effect, a number of blue squares rotating and falling from the sky.

By inspecting their source code, I extracted the effect implementation from the page.

As you can see, the effect is implemented with Canvas animation. Trello guys created a light weight particle system.

  • ConfettiMachine is the particle system controller, which takes the responsibility to create new particles and call draw method on them one by one.
  • Particle is the representation of the blue square, it takes the rotation, fading out and rendering.

The source code is extracted for study purpose, all code and credits belongs to Trello guys.

Otto and Android Annotations Compatibility Issue Analysis

Introduction

Otto is a great Android event bus solution developed by SquareUp guys. These guys extract the event bus related classes from Google’s Guava, and optimized it for Android. Event Bus is a great solution to keep your code away from messy anonymous or internal classes only used for event handling! And Otto’s simplicity and performance makes it used to be the most popular event bus solution in Android development.

Android Annotations, as known as AA, is another great library that makes Android developers’ lives a lot easier. Just annotating the code, AA helps developer handles the most boring or error-proning tasks for you, including binding widgets binding, asynchronous tasks, life time management, etc…

Issue

Otto and AA are the libraries focusing on different aspects. Theoretically speaking, there shouldn’t be any compatibility issue between them. But the reality Otto event is never delivered to AA annotated classes. By inspecting the code with step by step debugging, I found the root cause of the issue is Otto failed to located @Produce and @Subscribe annotated methods in AA generated code.

Analysis

After a few study work, I finally understood what is the reason behind:

For performance consideration, Android Annotations actually does it work during the compilation time instead of Runtime. AA will derive the annotated classes, and generate some code according to the annotations applied. During the runtime, the classes got instantiated is actually the derived classes instead of the original one, although in the code is accessed via the original class as interface.

Here is a simple example:

I have the class ServerListAdapter, which is used to provide data for a grid view.

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@EBean
public class ServerListAdapter extends AdvBaseAdapter<InetAddress, ServerView> {
@RootContext
protected Context context;
@SystemService
protected LayoutInflater inflater;
@Bean
protected Bus bus;
public ServerListAdapter() {
super(new ArrayList<InetAddress>());
}
@AfterInject
protected void afterInject() {
bus.register(this);
}
@Override
protected ServerView createView(ViewGroup parent) {
return ServerView_.build(context);
}
@Override
protected ServerView updateView(ServerView itemView, InetAddress item) {
itemView.update(item);
return itemView;
}
@Subscribe
public void fetchServerStatus(DiscoveryStatusEvent event) {
setItems(event.addresses);
}
@Subscribe
public void onServerStatusUpdated(DiscoveryStatusChangedEvent event) {
switch (event.type) {
case SERVER_ONLINE:
getItems().add(event.address);
break;
case SERVER_OFFLINE:
getItems().remove(event.address);
break;
}
notifyDataSetChanged();
}
}

And this is the derived class generated by AA during the compiling-time:

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
public final class ServerListAdapter_
extends ServerListAdapter
{
private Context context_;
private ServerListAdapter_(Context context) {
context_ = context;
init_();
}
public static ServerListAdapter_ getInstance_(Context context) {
return new ServerListAdapter_(context);
}
private void init_() {
inflater = ((LayoutInflater) context_.getSystemService(Context.LAYOUT_INFLATER_SERVICE));
context = context_;
bus = Bus_.getInstance_(context_);
afterInject();
}
public void rebind(Context context) {
context_ = context;
init_();
}
}

And here is how it is consumed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@EFragment(R.layout.fragment_server_list)
public class ServerListFragment extends Fragment {
@Bean
protected DiscoveryService discoveryService;
@Bean
protected ServerListAdapter adapter;
@ViewById(R.id.server_list)
protected GridView serverGridView;
@AfterViews
protected void afterViews() {
serverGridView.setAdapter(adapter);
discoveryService.start();
}
@Override
public void onDetach() {
super.onDetach();
discoveryService.stop();
}
}

As you can see, in the ServerListFragment, the ServerListAdapter instance injected into bean is actually the instance of ServerListAdapter_. And due to polymorphic, the instance just behaves like a ServerListAdapter instance.

On the other hand, according to the description on Otto’s Home Page:

In order to receive events, a class instance needs to register with the bus.

Registering will only find methods on the immediate class type. Unlike the Guava event bus, Otto will not traverse the class hierarchy and add methods from base classes or interfaces that are annotated. This is an explicit design decision to improve performance of the library as well as keep your code simple and unambiguous.

Otto only search annotations in direct class, which is ServerListAdapter_ in instance, and there isn’t any annotation included. As a consequence, all the @Subscribe annotated methods are ignored by com.squareup.otto.AnnotatedHandlerFinder. So the posted events become dead event due to no subscriber found.

Comments

There is rumor that this issue will be fixed in Otto 2.0. But according to the comment from Jake Wharton, Otto’s developer, Otto 2.0 will take forever to release.

In fact Otto 2.0 Repo has been not touched for 2 years already. Although we could check out the code and build Otto 2.0 by ourselves, but it takes efforts. Especially when some bug is found.

Conclusion

Luckily, although Otto turns into “maintenance mode”, the compatibility issue takes forever to resolve. I found a great Otto alternative, EventBus from GreenRobot. A boring name, but great library.

According to EventBus‘s document, it provides richer feature and better performance than Otto.
The most important EventBus is friendly to AndroidAnnoations. They two works well together.

EventBus and Otto Feature Comparison

EventBus Otto
Declare event handling methods Name conventions Annotations
Event inheritance Yes Yes
Subscriber inheritance Yes No
Cache most recent events Yes, sticky events No
Event producers (e.g. for coding cached events) No Yes
Event delivery in posting thread Yes (Default) Yes
Event delivery in main thread Yes No
Event delivery in background thread Yes No
Asynchronous event delivery Yes No

EventBus and Otto Performance Comparison

EventBus over Otto
Posting 1000 events, Android 2.3 emulator ~70% faster
Posting 1000 events, S3 Android 4.0 ~110% faster
Register 1000 subscribers, Android 2.3 emulator ~10% faster
Register 1000 subscribers, S3 Android 4.0 ~70% faster
Register subscribers cold start, Android 2.3 emulator ~350% faster
Register subscribers cold start, S3 Android 4.0 About the same