Implementing view injection, SlothFramework

So… I tried to make a Framework, I call it SlothFramework. But I consider it more of an experiment.

While I was developing my last app I decided that I wanted to get rid of as much boilerplate code as I could. So I started a package where I was putting all that code. With time that package become a library and now I’m sharing it with you.

It’s in a clearly alpha (pre-alpha?) state so don’t be too hard on me, but I will be pleased to hear suggestions and (constructive) critics.

For now I present you two of it’s main features, but I will only analyse the view injection:

  • Inject views
  • Save the state of a field to keep its value when you rotate your device.

Implementing view injection

The view injection it’s done through a custom annotation @InjectView, that has for parameter the id of the view. It’s done in a similar way to roboguice.

View injection example:

    @InjectView(R.id.breadCrum)   ListView breadCrumList;
    @InjectView(R.id.list_layout) ImageButton changeToList;
    @InjectView(R.id.context_bar) RelativeLayout contextBar;
    @InjectView(R.id.edit)        ImageButton edit;
    @InjectView(R.id.searchText)  EditText searchText;

It’s quite few code the necessary to implement view injection. Basically there are 3 parts:

  • Annotation: To mark the fields to process and pass the view id as parameter.
  • Base classes: To process the annotations in the activities and the fragments.
  • View injector: the one who injects the view. Implementing an injector helps to avoid code replication on base classes.

Annotation

First we need to define the annotation:

package es.slothdevelopers.slothframework.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
    int value() default Integer.MIN_VALUE;
}

Base classes

In order to process this annotations and inject the views our classes must extend an Sloth Class (depending if it’s an activity or a fragment).

Here you can check the available classes: available classes.

The view injection is made in different points depending of the base class. This is because the views are consolidated in different forms depending if it’s an activity or a fragment.

Activities

Here we have the code related to view injection for an Activity (SlothActivity)class:

public class SlothActivity extends Activity {
    ViewInjector viewInjector = new ViewInjector(this);

    public SlothActivity(){
        parseFields();
    }

    // HERE WE PROCESS THE ANNOTATION
    private void parseFields() {
        for (Field field : this.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            viewInjector.checkIfViewToInject(field);
        }
    }
...
    // AND WE OVERRIDE SETCONTENTVIEW TO INJECT THE VIEWS
    @Override
    public void setContentView(int layoutResID) {
        super.setContentView(layoutResID);
        viewInjector.injectViewList(this);
    }
}

In the constructor we parse fields to check the annotations, saving the fields we would inject later.

Then we override the setContentView method (in the Sloth class) to inject the views at this point. It’s made this way because is in this method where we have our view for the first time in the activity.

Fragments

For the fragments (bases on SlothFragment) we inject the views in the onViewCreated method:

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    viewInjector.injectViewList(view);
}

In resume: depending the class you are extending you would have access to the injected views on different points.

  • If you are extending an Activity class you could use the injected views after calling the setContentView method.
  • If you are extending a Fragment class you could use the injected views after calling the super method on onViewCreated.

View Injector

And here we have the code for the injector:

public class ViewInjector {
    List<Field> viewsToInject = new LinkedList<Field>();
    SlothLog log;
    Object caller;

    public ViewInjector(Object caller){
        this.log = new SlothLog(caller);
        this.caller = caller;
    }

    public void checkIfViewToInject(Field field) {
        if (field.isAnnotationPresent(InjectView.class)){
            viewsToInject.add(field);
        }
    }

    public void injectViewList(Activity activity) {
        for (Field field: viewsToInject){
            injectView(activity, field);
        }
    }

    private void injectView(Activity activity, Field field) {
        int id = field.getAnnotation(InjectView.class).value();
        if (id == Integer.MIN_VALUE){
            log.debug(errorObtainingViewIdToInject(field));
        }
        try {
            field.set(caller, activity.findViewById(id));
        } catch (IllegalAccessException e) {
            log.error(errorInjectingView(field, activity.getResources()));
        } catch (IllegalArgumentException ex){
            log.error(errorInjectingView(field, activity.getResources()));
        }
    }
}

As we can see it basically save the fields to be injected in a List and inject the views on demand. The view injection is done in this line:

field.set(caller, activity.findViewById(id));

Notifying errors

I tried to advise some typical errors injecting view. One of them is try to inject a different type that the one you declared.

In this example I’m trying to inject a ListView in a FrameLayout.

***********************************************************************
Error injecting view in: MainActivity.
It's possible that the type you are trying to inject differs from the one declared in the layout.
Check code.
@InjectView(R.id.list)
FrameLayout listView;
***********************************************************************

Saving the state of a field, @SaveState annotation

This annotation can be used in simple fields (primitive data types, POJOs, and list) to save the state of the field when we pause our activity and restore it when we resume it.

To use the annotation you must simply add the @SaveState annotation, no parameters needed.

Example:

    @SaveState    boolean waitingForRestartGame = false;
    @SaveState    int punctuation = 0;

This annotation can be used in Fragments and Activities.

I will post about this annotation with more time in future posts, but you can check the repository if you want to see how it’s done.

The process it’s similar to the inject view, it involves an annotation, the base classes and the save state injector.

The type support its not complete(I’ll try to make a list), there is still some work to do, and how you can see, it involves Reflection.

Example project

To show the framework in action I made a very simple project.

It consist of a button and a counter. Each time you press the button the counter grows.

Example application

The views are injected automatically. In this example we are injecting the Button and the TextView of the counter with the @InjectView annotation.

We are also saving the state of the counter with the @SaveState annotation. Without the annotation your counter will go down to 0 each time you rotate your device. You could write the code to restore the state, but the main point of the framework is not to do so.

Here is the resulting code of the main activity:

public class MainActivity extends SlothActionBarActivity implements View.OnClickListener {
    @SaveState
    int counter = 0;

    @InjectView(R.id.counter)       TextView counterCount;
    @InjectView(R.id.increaseCount) Button increaseCount;

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

    @Override
    public void onClick(View view) {
        counter++;
        updateCounterCount();
    }

    @Override
    protected void onResume() {
        super.onResume();
        updateCounterCount();
    }

    private void updateCounterCount(){
        counterCount.setText(String.valueOf(counter));
    }
}

You can check the rest of the code in the repository:

That’s all by now. I’ll present more features in future posts.

Thanks for reading.

Sloth logo 3