Typical EventBus Design Patterns

EventBus is an special Publish & Subscribe Pattern implementation. EventBus enable message to be delivered between components without requiring the components to register itself to others.

Comparing EventBus to other Pub & Sub implementations, EventBus is:

  1. Designed to replace the original event distribution system, which requires the components register itself to each other.
  2. Not designed for general use, EventBus adds additional complexity and runtime overhead to the system. So it is not designed to replace normal method calls.
  3. Not designed for inter-process communication as some other Pub&Sub.

EventBus saves Android developers’ life

When developing Android application, I strongly recommend developer to introduce an EventBus library. EventBus from GreenRobot, Otto from Squqre are good solutions. Or even to choose the original EventBus in Guava library from Google.

With EventBus, components can communicate with each other without need to hold direct reference of each other. This is very important to avoid app crashes when calling a method on a detached or destroyed component. Usually this kind of issue isn’t that easy to handle due to Android’s over-complicated life cycle management. So even in the worst case that one component is sending message to another component which has been destroyed (which should unregister itself from EventBus on that time), it only triggered a norecipient waring instead of crashing the app.

Also with EventBus as intermedia, you can avoid to exposing objects to root activity or application just for registering callbacks on each other. Components are higher cohesion & lower coupling.

As the result, EventBus saves tons of time to debugging crash, and make your code more readable, maintainble, extensible, and flexible.

Abusing EventBus kills your app easily

EventBus is awesome! It is so convenient, so let’s replace everything with event”… This kind of saying is commonly heard from the developers who just realized the benefits from EventBus. Since EventBus is too convenient to use, they tends to replace everything with EventBus.

But we mentioned in the introduction of EventBus that EventBus is design to replace traditional event system in Java, and is not designed for general use. Abusing makes your code hard to understand, hard to debug. Long event chain is a common reason that cause of unexpected behavior.

Broadcast Event, Command and Request

EventBus is powerful, but can be easily abused. To make a better use of EventBus, I learn a number of EventBus usages, and evaluated the scenario, and summarized out 3 typeical EventBus usage patterns: Broadcast Event, Command and Request.

Broadcast Event

Broadcast Event is a special kind of event that used by a specific component to broadcast its status updated to other components.

Declaration

Broadcast Event should be used when several components cares about the specific component’s status update. So usually Broadcast Event should have more than one recipients or, at least, potential recipients.

Broadcast Event should be an immutable data object, so:

  1. It is immutable, so once it is created, its status should not changed, which guarantees the equality between recipients.
  2. It is data object, so it should not has methods that might change other classes status.
  3. It might have methods that helps its consumer to consume the data it contains.
  4. It should be declared as a nested static class of publisher.

NOTICE
If you thought a Event is “Broadcast Event”, but later you found it has only one recipient, then you might need to consider refact it into a Command or a Request.

Command

Command is a special kind of event that has the ability to update specific object or specific type of objects. In most cases, Command should have only one recipient. In some special cases, it might has a group of recipients with exactly same type.

Precisely, the latter case is a variant of Command pattern, Batch Command, which works as same as Command but have multiple recipients so it can update multiple instances in a batch.

Command should be a immutable object that able to update specific object, so:

  1. It should have 1 execute method with 1 parameter, which represents the target that the command updates.
  2. When invoking execute method shouldn’t change its own status, so it does exactly same thing when applying it to multiple targets.
  3. It behavior should be stable across time, so its behavior won’t change when it is apply asynchronously.
  4. The object that execute method accepts is not necessarily to be the recipient. It could be the object that recipient holds or it has access to.
  5. It should be declared as nested static class of the recipient.
  6. If recipient accepts multiple events, these events are recommended to derived from the same base class. So The recipient could subscribe to the base class, rather than every command.

NOTICE
Command can be seen as recipient’s special method that can be invoked without known recipient instance. So its behavior should fully contained in the class. The subscribing method on recipient should contain one line of code to invoke execute method on command.

Request

Request is a special kind of event that has the ability to accept data from another object. If the request has multiple recipients, to avoid ambiguous behavior, there should be only one of Request‘s respond the request.

But there is one exception, that is for the request collects data from multiple objects, multiple objects might respond to the request. This special kind of Request is called Collector.

Request should be a object that accept data from recipient, so:

  1. It should have respond method with 1 parameter, which represents the data the request asks for.
  2. Request should contains enough information for the recipients to determine whether to respond the request.
  3. The recipients should check request’s status to determine whether it should respond request.
  4. The request can update the publisher directly in the respond method
  5. It should be declared as nested class of publisher. If possible, also declare it as static, which will be helpful to simplify the tests.
  6. Request might has methods to help recipient to determine whether to respond the request.

NOTICE
Request can be seen as a special method that publisher exposes to a specific recipient, so the specific recipient can invoke the method to provide data. For Request, you might need to aware that that sometimes request might not found proper recipient to respond. If the responder is not guaranteed to exist, then the publish to watch out no-recipent warning from EventBus.

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.

NPMethod called on non-NPObject wrapped JSObject

I’m working on a “hybrid” android appilcation. In the app, part of the UI was written in HTML and hosted in a WebView. And I exposed several Java objects to JavaScript as Java Script Interface.

Setup WebView
1
2
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(irBlaster, "irBlaster");

The irBlaster object contains several methods, and js will invoke the specific method according to the data-attribute bound to the element.

Script that handles the button click in HTML
1
2
3
4
5
6
7
8
9
10
onIrButtonClicked: (e) =>
$button = $(e.currentTarget)
type = $button.data('irType')
sendFunc = irBlaster[type]
code = @parseCode($button.data('irCode'))
length = $button.data('irLength')
sendFunc(length, code)

The previous coffee-script works fine in my Jasmine tests with javascript mock version of irBlaster. When the button clicked, the proper method was invoked with proper arguments.
But when I run this code with real android app, WebView yields error says “NPMethod called on non-NPObject wrapped JSObject”.

The error message looks quite hard to understand the meaning, so I spent quite time to diagnose the code.

After several try, I found the following code works fine, but original one doesn’t:

Code works
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
onIrButtonClicked: (e) =>
$button = $(e.currentTarget)
type = $button.data('irType')
switch irBlaster[type]
when 'NEC'
code = @parseCode($button.data('irCode'))
length = $button.data('irLength')
irBlaster.NEC(length, code)
.
.
.

So I realize the issue was occurd in the javascript contextual binding.

Javascript is function-first-citizen language, and method call on object was simulate by invoking the function with specific context, and the code behavior won’t change if there is no reference to this pointer in the method. So it is quite normal that fetch a method from a object than invoke it without context.

That is why the Jasmine tests passed successfully. But the irBlaster isn’t real or simple javascript, but a native java object provided by js binding interface, so there is a limitation that the method on it cannot be invoked without context. Or it causes error.

So the issue can be resolved as following code by invoking the method in a “reflection” flavor:

Invoking native binding object with context provided in HTML
1
2
3
4
5
6
7
8
9
10
onIrButtonClicked: (e) =>
$button = $(e.currentTarget)
type = $button.data('irType')
sendFunc = irBlaster[type]
code = @parseCode($button.data('irCode'))
length = $button.data('irLength')
sendFunc.call(irBlaster, length, code)

In previous code, I invoke the sendFunc with call, and provids irBlaster as the context as this. So the problem solved, the code runs smoothly without issue.