December 11, 2018

Android Annotations in Android Studio

This post will show how to integrate Android Annotations and some basic implementation from it.

If you wonder what is Android Annotations, it is essentially an annotation-driven framework that allows you to significantly reduce the boiler plate code of an Android application by generating classes at compilation time (generates a class extending the annotated class with an underscore as suffix by default) that contains well implemented code for you. It also relies in the Spring Framework for Android for the network-related annotations.

The code for this demo is here: https://gitlab.com/rastadrian/aaintegration

The master branch contains the code without AndroidAnnotations, the aa-integration branch will contain Android Annotations integration.

For this guide I will have an application that has one activity, which contains one text view and a button, if the button is tapped, the text disappears. So let's start with the activity's layout, one text view and a button below it.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.rastadrian.aaintegration.MainActivity">
  <TextView
    android:id="@+id/tv_description"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/tv_description" />;

  <Button
    android:id="@+id/btn_hide"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@id/tv_description"
    android:text="@string/btn_hide"/>;

For the sake of TDD, we'll make a test to assert our expected behavior. We first get the description label, we assert that it is visible, then we perform the click to the hide button and the we verify that the description is not visible anymore.

MainActivityTest.java
@Test
public void onHideButtonClick_descriptionTextShouldBeInvisible() throws Exception {
    TextView descriptionText = (TextView)    mActivity.findViewById(R.id.tv_description);
    //at first the description is visible
    assertEquals(View.VISIBLE, descriptionText.getVisibility());
    //click the hide button
    onView(withId(R.id.btn_hide)).perform(click());
    //the description is not visible anymore.
    assertEquals(View.INVISIBLE, descriptionText.getVisibility());
}

And now that we have our test, we write the code for it. It is very straight forward, but for the sake of our example it will suffice.

MainActivity.java
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final TextView descriptionTextView = (TextView) 	findViewById(R.id.tv_description);
    final Button hideButton = (Button) findViewById(R.id.btn_hide);
    hideButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            descriptionTextView.setVisibility(View.INVISIBLE);
        }
    });
}

Now that our app is all done, let's get Android Annotations in the scene.

To integrate AA to the Application, we will require to add the android-apt plugin to your app's build.gradle file. This plugin allows to enhance the integration of annotation processors to Android Studio. Integrating the plugin involves adding the buildscript declaration with the android-apt dependency, applying the plugin and setting the arguments for the plugin.

build.gradle
apply plugin: 'android-apt'

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.3.0'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
...
apt {
	arguments {
		resourcePackageName android.defaultConfig.applicationId
		androidManifestFile variant.outputs[0]?.processResources?.manifestFile
	}
}

Now we will add the android annotations library as a dependency.

String androidAnnotationsVersion = '3.3.2';

dependencies {
    ...

    // Android-Annotations
    apt "org.androidannotations:androidannotations:${androidAnnotationsVersion}"
    compile "org.androidannotations:androidannotations-api:${androidAnnotationsVersion}"
}

Click [Sync Project with Gradle Files] to download and sync the new dependencies.

Now that we have the library all set, let's annotate our MainActivity.

MainActivity.java
@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

@ViewById(R.id.tv_description)
TextView mDescriptionTextView;

@Click(R.id.btn_hide)
protected void onHideButtonClick() {
    mDescriptionTextView.setVisibility(View.INVISIBLE);
}

As seen, we use the @EActivity(layout) annotation to leverage the setContentView() code, the @ViewById(id) to leverage the findViewById(id) and the @Click(id) to leverage the view.setOnClickListener() boilerplate code. (For more annotations please take a look at the Android Annotations cookbook)

Now that our activity is annotated, we will have to register the generated class in our manifest, so we will make a small update to it. The generated activity classes will have the same name as our activity, but with an underscore suffix. So we will change MainActivity to MainActivity_.

AndroidManifest.xml
<application
    ...>
    <activity android:name=".MainActivity_">
        <intent-filter>
            ...
        </intent-filter>
    </activity>
</application>

You will probably get a red marker saying that this class doesn't exist yet, don't worry, let's re-build our project (hint: the building process generates, or re-generates the annotated classes) by going to Build > Rebuild Project, this will take a couple of seconds. After the building, you will see that the AndroidManifest doesn't mark an error anymore.

Now that everything is in place and the "annotated" class is generated, let's update our test case.

MainActivity.java
@RunWith(AndroidJUnit4.class)
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity_> {
    
private MainActivity mActivity;

public MainActivityTest() {
    super(MainActivity_.class);
}

...

Here we just updated the class definition in the constructor and the ActivityInstrumentationTestCase2 generic definition to the generated version of the activity class.

And we are all set! If you execute the app, the behavior should be the same as the non-annotated version, but the activity's code looks significantly cleaner.

Conclusion

AndroidAnnotations is a great library to implement, it really eases things up, but there are also trade-offs. I've experienced issues with @Background in certain devices with Android 4.0.1 (the custom BackgroundExecutor class fails to dispatch the call sometimes).

There's also the fact that your project now contains 2x classes, i.e. for each annotated bean/activity you now have ActivityA and ActivityA_ which kicks up the method count (not much of a problem nowadays due to the multi-dex).

Always take caution while adding dependencies to your projects, as the name states, you now depend on their infrastructure and you inherit their mindsets and bugs.