AndroidAnnotations for faster development

Writing the same code for each project all over again can be quite tiresome and time-consuming. In this post, I’ll introduce an Android library which could help you get rid of unnecessary boiler-plate code in several common areas of Android app development. The library is called AndroidAnnotations and it will help you develop your applications faster and with less bugs while writing less code at the same time.

AndroidAnnotations library

The AndroidAnnotations library aims at simplifying and speeding-up the development of often repeated parts of code in your projects. It does this by using the Java annotations. The developers just show their intention and then let AndroidAnnotations generate the boiler-plate code at compile time automatically.

Some of the features of the library include dependency injection for views, system services, resources, etc., simplified threading model, event binding without the need for anonymous listener classes, an easy-to-use REST client, etc.

All this is provided with no performance impact at runtime as the code is generated only during the project build and also it uses no code reflection.

Set up

There are a few things you need to do first in order to start using the AndroidAnnotations library. First, the project-scope build.gradle file has to contain the following android-apt plugin dependency:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.2'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

And the module build.gradle file will look like this with the android-apt plugin applied and library dependencies included:
apply plugin: 'com.android.application'
apply plugin: 'android-apt'
def AAVersion = '4.1.0'

android {
    ...
}

dependencies {
    ...
    apt "org.androidannotations:androidannotations:$AAVersion"
    compile "org.androidannotations:androidannotations-api:$AAVersion"
}

After that, sync your project and you’re ready to start enjoying the benefits of fast Android development provided by the AndroidAnnotations library. Now, let’s talk about the enhanced Android components first.

Enhanced components

In order to use the AndroidAnnotations features, all of the main components have to be annotated too (including Activities, Fragments, Services, etc.).

An enhanced Activity class must be annotated by the @EActivity annotation. You can even supply the layout id to be used with your Activity by supplying the annotation argument, e.g. @EActivity(R.layout.activity_main).

Because the generated classes extend your own classes, you also have to modify the name of the Activities in your manifest file. Just add an underscore to the end of the Activity name, like this:

<application
    ... >
    <activity android:name=".MainActivity_">
        ...
    </activity>
</application>

Don’t forget that every time you need to reference your component classes, you should use the generated ones (with underscore at the end), otherwise your code won’t work.

Similarly to the enhanced Activity, there is an enhanced Fragment which is created by annotating a Fragment with the @EFragment annotation (you can inject the layout resource id similarly as with the enhanced Activity). Among others there is @EApplication, @EIntentService, @EService, @EView, etc. to be used with the AndroidAnnotations.

Only inside of the enhanced components, you can use any of the annotations available in AndroidAnnotations library.

Injection

AndroidAnnotations library provides ways to easily inject Views, resources, intent extras, Android system services, and many more. Let’s have a look at some of the examples.

View Injection

AndroidAnnotations can help you get rid of the View glue code easily, similarly to other View Dependency Injection libraries like ButterKnife.

Instead of using the findViewById method, use the @ViewById annotation to find the View in layout. You can specify the View ID as an argument or simply omit it. In that case, the field name will be used to identify the View. The field mustn’t have private access for the injection to work.

@ViewById
TextView title;

@ViewById(R.id.subtitle)
TextView secondaryTitle;

Although injections are always made as soon as possible, you should not refer the injected Views in onCreate method. Instead, use the @AfterViews method annotation to execute its code right after the Views are injected and can be used safely.
@AfterViews
void updateTitles(String titleText, String subtitleText) {
    title.setText(titleText);
    subtitle.setText(subtitleText);
}

Several Views can be injected at once when using the @ViewsById annotation. Just supply the View IDs as the annotation arguments and apply the annotation on a List of Views, like this:
@ViewsById({R.id.myTextView1, R.id.myTextView2})
List<TextView> textViews;

Resources injection

The resource annotations indicate that given Activity field should be injected with specified Android resource from your project’s res folder. The resource ID can be passed as an argument of the annotation. In case it’s not, the name of the field will be used to identify the resource.

The resource annotations you can use include: @StringRes, @ColorRes, @AnimationRes, @DimensionRes, @DrawableRes, and many more. Here are some examples of resource annotations used in code:

@StringRes(R.string.title_text)
String text;

@ColorRes(R.color.backgroundColor)
int bgColor;

@AnimationRes
Animation fadein;

@DimensionRes
float fontSize;

@DrawableRes(R.drawable.icon)
Drawable icon;

Extras injection

Using the @Extra annotation, you can inject values to Activity fields with the corresponding Extras from the Intent that was used to start the Activity. A nice bonus is that you can then also use the intent builder to pass the Extra values conveniently.

@Extra("myExtraString")
String extraString;

/* start the Activity with extras passed */
MainActivity_.intent(this).extraString("This is extra").start();

Fragment Arguments injection

Similarly to passing Extras, you can use the @FragmentArg to inject a Fragment field with the corresponding Fragment argument. As usually, either you set the name as annotation argument or leave it blank for default field name. Again, you can also use the generated convenient methods provided by the Fragment builder to set the Fragment arguments.

@EFragment
public class MyFragment extends Fragment {
  @FragmentArg("myStringArgument")
  String myMessage;

  @FragmentArg
  String anotherStringArgument;
}

/* using the Fragment builder to pass arguments */
MyFragment myFragment = MyFragment_.builder()
  .myMessage("Hello")
  .anotherStringArgument("World")
  .build();

Event binding

AndroidAnnotations library provides a simple way of avoiding anonymous classes with event listeners. A wide range of event annotations is provided. Let’s see examples of the most common ones now.

Click Events

One of the most repeated actions in Android development is defining the click listeners on Views. AndroidAnnotations comes with @Click, @LongClick and @Touch annotations to help you define click, long-click and touch listeners respectively in an enhanced way.

There are several ways you can define a View click listener with the library. Either you supply the View’s ID as an annotation argument, or let the library derive it from the method name, or you can supply the View object as a parameter of the annotated method. Also, you can define a single listener for multiple Views. Code examples follow:

@Click(R.id.myButton)
void myButtonClicked() {
    //...
}

@Click
void anotherButton() {
    //...
}

@Click
void yetAnotherButton(View clickedView) {
    //...
}

@Click({R.id.myButton, R.id.myOtherButton})
void handlesTwoButtons() {
    //...
}

Key Events

Handling key events can be done easily with the @KeyDown, @KeyUp and @KeyLongPress annotations. The usage is similar to previous annotations and should be clear from the code examples:

@KeyDown
void enterPressed() {
    //...
}

@KeyUp(KeyEvent.KEYCODE_ESCAPE)
boolean handleEscapeActionUpEvent() {
    //...
    return false;
}

@KeyLongPress({ KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_B })
void aOrBKeyLongPress(KeyEvent keyEvent) {
    //...
}

Menu Events

Adding and handling options menu support with AndroidAnnotations is a matter of using just two annotations. @OptionsMenu lets you specify the menu resource to be used in your Activity and @OptionsItem annotates methods that receive menu selection events.

Here is an example implementation:

@EActivity
@OptionsMenu(R.menu.activity_menu)
public class MyActivity extends Activity {

    @OptionsMenuItem
    MenuItem menuSearch;

    @OptionsItem(R.id.menuShare)
    void myMethod() {
      // ...
    }

    @OptionsItem
    boolean homeSelected() {
      // ... for R.id.home item
      return true;
    }

    @OptionsItem({ R.id.menu_search, R.id.menu_delete })
    void multipleMenuItems() {
      // ...
    }
}

Threading

Threading with AndroidAnnotations is simplified greatly. All you really need are the two provided annotations: @Background and @UiThread to make given methods run in a background or UI thread.

To make a method run in a background thread, just add the @Background annotation to it. If you want to cancel a background task later, you can use the optional id field with it. Another useful feature of the @Background annotation is an option to specify a time delay before the background method is run using a delay parameter.

void mainMethod() {
    backgroundTask("hello", 42);
    
    /* to cancel the task: */
    BackgroundExecutor.cancelAll("cancellable_task", true);
}

@Background(id="cancellable_task", delay=2000)
void backgroundTask(String param, long anotherParam) {
    // ...
}

The library internally uses a shared cached thread pool executor to prevent creating too many threads.

The @UiThread annotation works exactly the same as the @Background one, except for that the annotated method is run on the UI thread. With the two annotations combined, propagating progress of a task running in background is really simple:

@EActivity
public class MyActivity extends Activity {
  @Background
  void doInBackground() {
    publishProgress(0);
    // ...
    publishProgress(10);
    // ...
    publishProgress(100);
  }

  @UiThread
  void publishProgress(int progress) {
    // Update progress views
  }
}

Other annotations

AndroidAnnotations library provides many other features, some of which I’m going to introduce briefly now.

Customizing window features of your Activity can be done using the @WindowFeature annotation. Putting your activity to full-screen mode is done by annotating the Activity with @Fullscreen annotation.

@Fullscreen
@WindowFeature({ Window.FEATURE_NO_TITLE, Window.FEATURE_INDETERMINATE_PROGRESS })
@EActivity
public class MyActivity extends Activity {
    //...
}

Acquiring a WakeLock for a method is effortlessly done by annotating it with the @WakeLock annotation. Don’t forget to also add the required permission to your manifest file.
@WakeLock(tag = "myTag", level = WakeLock.Level.FULL_WAKE_LOCK, flags = WakeLock.Flag.ACQUIRE_CAUSES_WAKEUP)
void methodWithWakeLock(String aParam, long anotherParam) {
    // ...
}

Another provided feature concerns SharedPreferences. The added functionality includes convenient definition and a typesafe usage. First, create an interface annotated with @SharedPref to define the SharedPreference’s variables. Then use the @Pref annotation to get an instance of the generated SharedPreferences helper class (notice the compulsory underscore in the class name).
@SharedPref
public interface MyPrefs {
    @DefaultString("John")   /* or use a String resource: R.string.john */
    String name();

    @DefaultInt(42)
    int age();

    long lastUpdated();      /* default value is 0 */
}

@EActivity
public class MyActivity extends Activity {
    @Pref
    MyPrefs_ myPrefs;
}

The usage of the generated SharedPreferences helper class is very intuitive:
myPrefs.name().put("John");

// Batch edit
myPrefs.edit()
  .name()
  .put("John")
  .age()
  .put(42)
  .apply();

// Preference clearing:
myPrefs.clear();

// Check if a value exists:
boolean nameExists = myPrefs.name().exists();

// Reading a value
long last = myPrefs.lastUpdated().get();

// Reading a value and providing a fallback default value
long last = myPrefs.lastUpdated().getOr(System.currentTimeMillis(););

A lot of additional functionality is provided through the AndoridAnnotations library plugins. These include for example a REST API client or integration with several 3rd party frameworks (such as RoboGuice, OrmLite, Otto event bus, Dagger, etc.).

Conclusion

In this post, I’ve introduced the AndroidAnnotations library which can help you develop Android applications in shorter time and with less boiler-plate code. Among the features it provides, there is dependency injection, event binding, simplified threading model, and many more. For a full list of provided annotations, go to the project’s Wiki on Github.

If you want to learn more about how to enhance your code with annotations, head over to my previous post on the Android Support Annotations library.

Sources

Share this: