There are a few libraries for Android, which implement a lot of widely used features and concepts from the well known Java ecosystem for less powerful devices. Some of then provide the base for my Android technology stack, which I would like to present today.
Android Annotations provides a whole lot of features, which really provide value for the developer in terms of readability and maintainability. The main features are:
- Dependency injection
- Event handling
- Simple threading
- Consuming REST APIs
Android Annotations uses APT and generates optimized classes at compile time. This was a design choice to reduce the launch time (respectively don’t increase it) at startup and prevent sluggish runtime behavior. It’s simple to include in your build.gradle files (both the app’s build.gradle and the project level build.gradle):
buildscript {
// …
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
// …
} |
buildscript {
// …
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
// …
}
build.gradle (Projekt)
apply plugin: 'android-apt'
android {
// …
}
dependencies {
// …
apt('org.androidannotations:androidannotations:4.2.0')
compile('org.androidannotations:androidannotations-api:4.2.0')
} |
apply plugin: 'android-apt' android {
// …
} dependencies {
// …
apt('org.androidannotations:androidannotations:4.2.0')
compile('org.androidannotations:androidannotations-api:4.2.0')
}
build.gradle (App)
public class ExampleActivity extends Activity {
private Button exampleButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_example);
exampleButton = (Button) findViewById(R.id.exampleButton);
exampleButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
// do something
}
});
}
} |
public class ExampleActivity extends Activity {
private Button exampleButton; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_example);
exampleButton = (Button) findViewById(R.id.exampleButton);
exampleButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
// do something
}
});
}
}
Vanilla Android
@EActivity(R.layout.layout_example);
public class ExampleActivity extends Activity {
@ViewById
Button exampleButton;
@Click
void exampleButtonWasClicked() {
// do something
}
} |
@EActivity(R.layout.layout_example);
public class ExampleActivity extends Activity {
@ViewById
Button exampleButton; @Click
void exampleButtonWasClicked() {
// do something
}
}
Android with Android Annotations
When Android Annotations provides to much features which are not used, one can use a combination of Butterknife (view injection, http://jakewharton.github.io/butterknife), Dagger (dependency injection, https://google.github.io/dagger) and Retrofit (REST client, https://square.github.io/retrofit).
To decouple an Activity or Fragment from the business logic, it may be worth to have a look at the publish/subscribe pattern and an established library called EventBus from greenrobot:
apply plugin: 'android-apt'
android {
// …
}
dependencies {
// …
compile('org.greenrobot:eventbus:3.0.0')
} |
apply plugin: 'android-apt' android {
// …
} dependencies {
// …
compile('org.greenrobot:eventbus:3.0.0')
}
build.gradle (App)
public class ExampleActivity extends Activity {
protected final EventBus eventBus = EventBus.getDefault();
@Override
protected void onStart() {
super.onStart();
eventBus.register(this);
}
@Override
protected void onStop() {
super.onStop();
eventBus.unregister(this);
}
} |
public class ExampleActivity extends Activity {
protected final EventBus eventBus = EventBus.getDefault(); @Override
protected void onStart() {
super.onStart();
eventBus.register(this);
} @Override
protected void onStop() {
super.onStop();
eventBus.unregister(this);
}
}
Other steps, like publishing an event and subscribing to it, can be found in the EventBus documentation at GitHub.
IcePick reduces the boilerplate code which arises as a result of having to manage instance states from activities and fragments. This is accomplished by by the means of APT and code generation (remember Android Annotations?).
@EActivity(R.layout.layout_example);
public class ExampleActivity extends Activity {
@State
String exampleText;
@ViewById
Button exampleButton;
@Click
void exampleButtonWasClicked() {
// do something
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
}
} |
@EActivity(R.layout.layout_example);
public class ExampleActivity extends Activity { @State
String exampleText;
@ViewById
Button exampleButton; @Click
void exampleButtonWasClicked() {
// do something
} @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
}
}
The content of exampleText will be restored on all configuration change events (i.e. OrientationChanges).
Memory leaks are not a harmless crime! In order to find them you can use the library LeakCanary, which – once it is initialized in the Application implementation, shows a notification when it discovered a memory leak while testing the debug build.
public class ExampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
} |
public class ExampleApplication extends Application { @Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
}
Espresso is a test framework included in the Android test support libraries. It provides a DSL for automated UI tests. The implemented concepts (based on JUnit, JUnit TestRules, Matchers) are well known to developers, so this framework should be easy to learn. Espresso runs on emulators and real devices.
Conclusion
This is just a small, selected list of libraries, which focusses on code quality, maintainability and testability.
A few rough edges, which sometimes make Android development so cumbersome, are smoothed over.
Praise the community!
Which libraries are you using? Leave a comment and discuss this article with me.