Simplify your development with roboject

The Android API can be quite complex, especially when dealing with services, resources, assets, views. The threading model of Android makes even simple tasks hard to develop. Roboject is an injection framework drastically simplifying the development of Android components. It's lightweight, extensible and easy to use.

  • Source Code: GitHub
  • License: Apache License 2.0

Motivation

Almost every activity relies on external objects, that are e.g. defined in XML files or come from another activity. Getting a hold of those objects implies using various lookup calls, which tend to be distributed across your code. Things get even more complicated when relying on services. Services need to be actively bound to, they are leading to boiler-plate code and they are reducing readability and maintainability of your activities. Roboject injects dependent objects for you through the use of annotations, easing the process of acquiring those objects and making your activity much easier to read and matintain. Robojects also extends Android's lifecycle and thus allows to even inject services to your activity, notifying you when all services are successfully bound. Roboject has a minimal footprint, having no dependency to other libraries or frameworks.

Brief description

To use Roboject, your activity must be a subclass of RobojectActivity. This allows you to use the following annotations to inject your dependent objects. More Injections will be introduced soon:

  1. @InjectLayout: Injects an XML layout to your activity.

  2. @InjectView: Inject an XML view to a field of your activity.

  3. @InjectExtra: Inject an Intent extra passed from another activity to a field of your activity.

  4. @InjectService: Inject the binder object of a service to a field of your activity.

And two additional methods:

  1. onServicesConnected: Runs in brackground and is called, when all Services were bound. Use this method to process time consuming parts, such as clietn requests to a server.

  2. onReady: Is called subsequently to onServicesConnected on the UI-Thread. Manipulate the layout here.

Here is an example activity that uses Roboject:

// Inject a layout for your activity
@InjectLayout(R.layout.resultListActivity)
public class ResultListActivity extends RobojectActivity {
    // Inject intent extra using field name as key ("searchTerm")
    @InjectExtra
    private String searchTerm;

    // Inject intent extra using specified key
    @InjectExtra("sort")
    private boolean sorted;

    // Inject service via class name.
    // The binder object gets injected, while you specify
    // the Android service as parameter.
    @InjectService(clazz = FetchAndroidService.class)
    private FetchService fetchService;

    // Inject view using field name as key ("listView")
    @InjectView
    private ListView listView;

    // Inject view using specified key
    @InjectView("button")
    private Button closeButton;

    private List results;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ...
    }

    // This gets called in the background once all services are bound.
    // Fetch all the data you need
	// to display from here (e.g. server calls).
    @Override
    public void onServicesConnected() {
        super.onServicesConnected();

        this.results = fetchService.fetch(searchTerm);
        // ...
    }

    // This gets called on the main thread (UI thread) once
    // onServicesConnected completed successfully.
    // Here you can populate your views.
    @Override
    public void onReady() {
        super.onReady();

        // Populate views
        this.listView.setAdapter(...);
        // ...
    }
}

Create an Android application project

First, you need an Android project. You can use Eclipse, Ant or Maven to achieve this task. In this tutorial, we just use Eclipse. So, make sure you have the Android Eclipse plugin installed as well as the Android SDK.

In Eclipse, go to File - New - Android Project and fill the new project wizard.

wizard image

Add roboject to the project

Create a lib folder in your project, and copy the roboject jar in this folder. Right-click on the jar, and add it to the build path.

roboject lib

For Ant, you just need to add roboject to the project classpath. For maven, just add roboject as a dependency (in the compile scope).

Example Project

Download Sources

The following example project uses each of the four injects and both methods. It consists of two activities and two simple services.

The starting activity shows only one button that starts the other activity and passes an StringExtra.

button.setOnClickListener(new OnClickListener(){

            @Override
            public void onClick(View arg0) {
                Intent intent = new Intent();
                intent.setClass(StartActivity.this,
					RobojectGuideActivity.class);
                intent.putExtra(
					RobojectGuideActivity.INTENTEXTRA, "Everything worked!");
                startActivity(intent);
                finish();
            }
        });

The second activity icludes nearly all of the roboject benefits.

package de.akquinet.android.roboject.guide;

import android.os.Bundle;
import android.widget.TextView;
import de.akquinet.android.roboject.RobojectActivity;
import de.akquinet.android.roboject.annotations.InjectExtra;
import de.akquinet.android.roboject.annotations.InjectLayout;
import de.akquinet.android.roboject.annotations.InjectResource;
import de.akquinet.android.roboject.annotations.InjectService;
import de.akquinet.android.roboject.annotations.InjectView;
import de.akquinet.android.roboject.guide
	.GuideService.GuideAndroidService;
import de.akquinet.android.roboject.guide
	.RoboService.RoboAndroidService;

// instead of
// setContentView(R.layout.main)
@InjectLayout(R.layout.main)
public class RobojectGuideActivity extends RobojectActivity {

    public final static String INTENTACTION_ROBOSERVICE =
		"INTENTACTION_ROBOSERVICE";
    public final static String INTENTEXTRA = "INTENTEXTRA";

    // instead of
    // TextView helloView = (TextView)findViewById(R.id.helloTextView)
    @InjectView(R.id.helloTextView)
    TextView helloView;

    // From roboject V1.0.3 Resources will be injectable.
    // e.g. instead of
    // Integer col = getResources().getColor(R.color.textColor);
    // use
    // @InjectResource(R.color.textColor)
    // Integer col;

    // instead of
    // Intent intent = new Intent();
    // intent.setClassName(context, clazz);
    // bindService(intent, Service.BIND_AUTO_CREATE)
    //
    // Furthermore, the service is observed in onServicesConnected.
	// It's not required to take care about by yourself,
	// if it's bound.
    @InjectService(clazz= GuideService.class)
    GuideAndroidService guideService;

    // instead of
    // Intent intent = new Intent();
    // intent.setClassName(context, clazz);
    // intent.setPackage(context.getPackageName());
    // intent.setAction(INTENTACTION_ROBOSERVICE);
    // bindService(intent, Service.BIND_AUTO_CREATE)
    //
    // Furthermore, the service is observed in onServicesConnected.
    // It's not required to take care about by yourself,
	// if it's bound.
    @InjectService(action=INTENTACTION_ROBOSERVICE,
		restrictToThisPackage=true)
    RoboAndroidService roboService;

    // instead of
    // String helloTextExtra = savedInstanceState.get(INTENTEXTRA);
    @InjectExtra(value=INTENTEXTRA)
    private String helloTextExtra;

    boolean isCorrect = false;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    // The services are bound and can be used in background.
    @Override
    public void onServicesConnected() {
        super.onServicesConnected();

        isCorrect = guideService.getSomethingThatTakesAWhile();
        isCorrect = roboService.getSomethingThatTakesAWhileToo();
    }

    // No time consuming processes, since these should be done
    // in background (onServicesConnected). This runs in
	// the UI-Thread. Manipulate your UI here.
    @Override
    public void onReady() {
        super.onReady();

        if(isCorrect){
            helloView.setText(helloTextExtra);
        }
        else
        {
            helloView.setText("NotCorrect!");
        }
    }
}

If nothing went wrong, you should see the following screen:

roboject_finalscreen

Injections

Roboject provides everal injections for activities and fragments that aim to reduce the amount of boilerplate-code in your activities. The following injections are available.

InjectView

@InjectView provides a replacement for view lookups like findViewById. Lets assume you defined a text view

<TextView android:text="HelloWorld"
	android:id="@+id/helloWorldTextView"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"/>

To access this view in your activites you can use

@InjectView TextView helloWorldTextView;
// or
@InjectView("helloWorldTextView")
TextView helloWorld;
// or
@InjectView("R.id.helloWorldTextView")
TextView helloWorld;

InjectService

Services can be bound via @InjectService. You can either provide the attribute clazz which is the class type of the service or you can specify an intent. The type of the field you want to inject the service into must correspond to a binder object which is provided by the service. Lets consider the following service:

public class MyService extends Service
{
    public static class GreeterService extends Binder
    {
        public String addGreeting(String name) {
            return "Hello " + name;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return new GreeterService();
    }
}

To use this service in your activity you would inject it with

@InjectService(clazz=MyService.class)
GreeterService greeter;
// or if you specified an intent for the service
@InjectService(intentAction=GREETER)
GreeterService greeter;

Roboject add two new lifecycle methods concerning the handling of services.

The first one is onServicesConnected which is called after all injected services are bound. It is executed outside the UI-thread and may contain expensive computations. The second one is onReady which is called in the UI-thread. If any logic in the activity depends on injected services you can add this logic here and do not have to deal with the situation where a service might already be available or not.

@InjectServices also allows you to look for services in other applications with @InjectService(..., restrictToThisPackage=false). The default is a lookup restricted to the own application.

Local services

Roboject also allows the injection of non-android services that are defined as interface/implementation pairs. To use such a service it needs to be registered first:

ServiceRegistryServiceRegistry.registerService(
	MyService.class, MyServiceImpl.class);

You can inject this service with

@InjectService(clazz = MyService.class)
MyService service;

Non-android services take precedence over android services if both could be injected into a specific field.

InjectExtra

@InjectExtra allows you to access values of the intent of the activity. For example if the intent contains a string theString. You can access it with

@injectExtra private String theString;
// or
@InjectExtra("theString")
String intentString;

You can also specify the attribute mandatory. This will cause a runtime exception if the intent did not contain a proper value for the field.

InjectObject

If you want to transfer non-serializable objects between activity you can use @InjectObject. For this mechanism to work your application must extend RobojectApplication. Such a non-serializable object is tied to an intent (though not stored in it). When creating the intent you can use the following command to add such an object:

Intent intent = new Intent();
putObjectIntentExtra(intent, "objectExtra", new NonSerializableObject());

In the receiving activity you can use

@InjectObject private NoNSerializableObject objectExtra;
// or
@InjectObject( private NoNSerializableObject myObject;

You can also specify the attribute mandatory. This will cause a runtime exception if the intent did not contain a proper object for the field.

InjectLayout

This is a shortcut for setContentView and injected at the activity level:

@InjectLayout("my_layout")
public class MyActivity extends RobojectActivity {
...
}

InjectResource

This allows the injection of resources like strings, drawables or intArray:

@InjectResource private Integer theInteger;
// or
@InjectResource(name = "theInteger")
private Integer myInteger
// or
@InjectResource(name = "theInteger", type="integer")
private Integer myInteger

If no name or type is specified the injection will try to determine the resource via the name and type of the field.

Fragments

It is possible to use these injections in fragments by subclassing RobojectFragment or RobojectSupportFragment.

public class MyFragment extends RobojectFragment

    @InjectView TextView helloWorldTextView;
}

RobojectFragment is used for Honeycomb fragments, while RobojectSupportFragment is used with the android-support library

Arbitrary classes

You can use the roboject injections with arbitrary classes by explicitly calling the static injection methods from the class Roboject.

public class MyObject
    @InjectView TextView helloWorldTextView;
}
    MyObject x = new MyObject();
Roboject.injectViews(x, aRootView);

Download

Roboject is available at github. We also provide binaries for convenience:

  • roboject-1.2.1.jar (just add this jar to your build path)
  • Maven dependency (to use with the maven-android-plugin)
<dependency>
    <groupId>de.akquinet.android.roboject</groupId>
    <artifactId>roboject</artifactId>
    <version>1.1.0</version>
    <scope>compile</scope>
</dependency>

Roboject is available on maven central, so you don't need to customize your maven settings.