Android optimize

Speed up your app development with AndroidAnnotations

AndroidAnnotations may help you get your next Android app to market before the competition.

android_thumb_090613.jpg
AndroidAnnotations eliminates the boilerplate code that is common in Android projects by replacing it with annotations. Since there is less code to write and maintain, this helps to speed development and improve maintainability.

AndroidAnnotations heavily utilizes dependency injection. This design pattern not only allows you to eliminate boilerplate, it also provides the added benefit of introducing consistency into a code base. This can be a significant advantage when new developers are introduced to a code base.

The following example demonstrates a basic Android app developed using AndroidAnnotations. The first step will be to show the basic application without the use of AndroidAnnotations. Then I will show the application with AndroidAnnotations, and I will make enhancements using various annotations. Follow along with the step-by-step tutorial, or download and import the project in its entirety.

The basic Android app

For the purpose of our example, we will use a very simple layout. In the /res/ layout/activity_calculator.xml file, we create a linear layout. The layout contains two EditTexts that allow the user to input two numbers. The next element is a Button that will perform the addition when clicked. The last element is a TextView that displays the result of the addition operation.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:layout_gravity="left"
    android:gravity="left"
    tools:context=".Calculator" >

    <EditText
        android:id="@+id/firstNumber"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:ems="10"
        android:inputType="number">
        <requestFocus />
    </EditText>

    <EditText
        android:id="@+id/secondNumber"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="number" />

    <Button
        android:id="@+id/addButton"
        android:layout_width="140dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="30dip"
        android:text="Add" />

    <TextView
        android:id="@+id/total"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="70dip" />

</LinearLayout>

In the /src directory of the project, we create an activity that handles rendering our layout and registering an OnClickListener with the addButton. The OnClickListener calculates the total of the two numbers the user input and renders the total directly below the Add button.

public class Calculator extends Activity {
  private EditText firstNumber;
  private EditText secondNumber;
  private TextView total;
  private Button addButton;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_calculator);

    firstNumber = (EditText) findViewById(R.id.firstNumber);
    secondNumber = (EditText) findViewById(R.id.secondNumber);
    addButton = (Button) findViewById(R.id.addButton);
    total = (TextView) findViewById(R.id.total);

    addButton.setOnClickListener(new View.OnClickListener() {
      public void onClick(View v) {
        Integer totalResult = 
          getFirstNumberResult() + getSecondNumberResult();
        total.setText("Total = " + Integer.toString(totalResult));
      }
    });
  }

  private int getFirstNumberResult() {
    return Integer.parseInt(firstNumber.getText().toString());
  }

  private int getSecondNumberResult() {
    return Integer.parseInt(secondNumber.getText().toString());
  }
}

The basic app with AndroidAnnotations

Now we want to convert the example to AndroidAnnotations and eliminate the boilerplate code found in the onCreate method. If you are creating your own project, follow the instructions on GitHub to set up the project. The provided example works in Eclipse. The converted Calculator class is shown below.

@EActivity(R.layout.activity_calculator)
public class Calculator extends Activity {
  @ViewById EditText firstNumber;
  @ViewById EditText secondNumber;
  @ViewById TextView total;

  @Click
  public void addButton() {
    Integer totalResult = getFirstNumberResult() + getSecondNumberResult();
    total.setText("Total = " + Integer.toString(totalResult));
  }

  private Integer getFirstNumberResult() {
    return Integer.parseInt(firstNumber.getText().toString());
  }

  private Integer getSecondNumberResult() {
    return Integer.parseInt(secondNumber.getText().toString());
  }
}

We have eliminated the need for the onCreate method; the behavior associated with this method is now divided between several annotations. The @EActivity annotation on the class specifies that AndroidAnnotations should apply annotation-based behavior to this class. The view id that is passed to the @EActivity annotation defines the layout associated with the activity when it is activated; this avoids the need for a call to setContentView.

The @ViewById annotation injects the view object with the attribute name; this avoids the need to retrieve those view objects manually. In the example, the @ViewById applied to firstNumber will set that attribute to the EditText with the firstNumber id. The @Click annotation applies an OnClickListener to the view object with the same id as the method name. In the example, the @Click annotation on the Add button ensures the method will execute when the Add button is clicked.

AndroidAnnotations provides the dependency injection behavior through code generation. Any class that is annotated for processing will have a corresponding generated class. The generated classes can be recognized by their name, as they always end with an underscore. In the example, Calculator.java has a corresponding Calculator_.java. You can always review these generated classes to analyze the code that is being created by AndroidAnnotations. This can be useful when debugging.

Due to the code generation behavior, it is necessary to reference the corresponding generated Activity class in the AndroidManifest.xml file.

<application
  android:allowBackup="true"
  android:icon="@drawable/ic_launcher"
  android:label="@string/app_name"
  android:theme="@style/AppTheme">
  <activity
    android:name="com.solutionsfit.calculator.Calculator_"
    android:label="@string/app_name">
    <intent-filter>
      <action android:name="android.intent.action.MAIN" />
      <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
  </activity>
</application>

We reference Calculator_ instead of Calculator, because this ensures the AndroidAnnotations behavior will be applied to the Calculator class during execution.

Extending the basic app with AndroidAnnotations

A common requirement in an Android app is retrieving a string from strings.xml or an integer from an error_codes.xml file. These types of resources can be easily injected using the @StringRes and @IntegerRes annotations, respectively. Let's apply this to our example by extracting the Total string to the strings.xml file.

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <!-- ... -->
  <string name="total_label">Total</string>
</resources>

Now we can refactor the addButton method to use the injected totalLabel.

@EActivity(R.layout.activity_calculator)
public class Calculator extends Activity {
  // ... ...
	
  @StringRes String totalLabel;
	
  @Click
  public void addButton() {
    Integer totalResult = calculateTotal();
    total.setText(totalLabel + " = " + Integer.toString(totalResult));
  }
}

We can also use annotations in a class that is not a standard Android component; this allows certain reusable operations or behavior to be refactored into injectable components. While it is a bit contrived for our example, we could break out the addition behavior into a CalculatorBean.

@EBean
public class CalculatorBean {
	public Integer add(Integer num1, Integer num2) {
		return num1 + num2;
	}
}

Similar to the @EActivity annotation, the @EBean annotation informs AndroidAnnotations that the class should be processed for annotated behavior; it also makes the class available for injection into another annotated class. We can now inject CalculatorBean within the Calculator class and invoke the add method when the Add button is clicked.

@Bean CalculatorBean calculatorBean;

@Click
public void addButton() {
  Integer totalResult = 
    calculatorBean.add(getFirstNumberResult(),  getSecondNumberResult());
  total.setText(totalLabel + " = " + Integer.toString(totalResult));
}

The @Bean always corresponds to some @EBean class that is to be injected. The @EBean annotated class supports the same types of behavior that an annotated Android component would. To demonstrate this, let's add a divide button to activity_calculator.xml.

<Button
    android:id="@+id/divideButton"
    android:layout_width="140dp"
    android:layout_height="wrap_content"
    android:layout_marginLeft="30dip"
    android:text="Divide" />

Now we can add the expected divide behavior to the CalculatorBean.

@EBean
public class CalculatorBean {
  @RootContext Context context;

  @StringRes String divideByZeroMessage;

  // ... ...
  public Integer divide(Integer num1, Integer num2) {
    if (num2 == 0) {
      showMessage(divideByZeroMessage);
      return null;
    }

    return num1 / num2;
  }

  @UiThread
  public void showMessage(String message) {
    Toast.makeText(context, message, Toast.LENGTH_LONG).show();
  }
}

Notice the use of new annotations. Divide by zero must be handled to avoid an overflow condition. The showMessage method displays a Toast message to notify the user that the operation could not be performed. The @UiThread annotation ensures that the method executes on the UI thread. When adding UI behavior to @Bean classes, it may be necessary to specify @UiThread if the method could be invoked in a background thread.

The @RootContext annotation allows for the injection of the current Context. This annotation can also be used to inject the Activity if the root context is an Activity, or a Service if the root context is a Service. Let's invoke the divide method from the Calculator activity.

@StringRes String undefinedLabel;

@Click
public void divideButton() {
  handleDivide();
}

@Background
public void handleDivide() {
  showDivideResult(
    calculatorBean.divide(getFirstNumberResult(),  getSecondNumberResult()));
}

@UiThread
protected void showDivideResult(Integer divideResult) {
  if(divideResult == null) {
    total.setText(totalLabel + " = " + undefinedLabel);
  } else {
    total.setText(totalLabel + " = " + Integer.toString(divideResult));
  }
}

Here the division is handled on a background thread by using the @Background annotation. While it may not be necessary in our case, this is useful when a process may execute for an extended period of time.

Try it and see for yourself

Now that you've experienced the power of AndroidAnnotations, give it a try. It may help you get your next Android app to market before the competition.


About

Jacob Orshalick is a software consultant, open source developer, speaker, and author. He is the owner of solutionsfit and co-author of the best-selling Seam Framework: Experience the Evolution of Java EE. His software development experience spans the...

0 comments