Sunday, March 13, 2011

DIY Dependency Injection

Most Java people will tell you "you've got to use Dependency Injection". One valid argument I've seen lately is to replace global static singletons, which make apps easier to unit-test.

Now in my Android projects, I don't like to drag in huge libraries. So sure we have Guice, but really I don't want to include that just to inject a class somewhere. A singleton is finally pretty cheap. But then I realize I don't need that kind of heavyweight library. The core of an injection mechanism is pretty obvious with a bit of reflection.

Let's say I have an annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Inject {
}
and somewhere else I have something to inject into some field. It becomes trivial to explore a class instance, retrieve the fields that have our annotation and replace them by that something if it has the right type:
public class MainActivity extends Activity { 
    private static final String TAG = MainActivity.class.getSimpleName();

  @Inject
  private IProvider mProvider; // never initialized directly

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    performInjection(this, new Provider());
    TextView text = (TextView) findViewById(R.id.the_text);
    text.setText(mProvider.getValue());
  }

  private void performInjection(Object target, Object...injected) { 
    for (Field f : target.getClass().getDeclaredFields()) { 
      try { 
        if (f.get(target) == null && f.getAnnotation(Inject.class) != null) { 
          Class<?> t = f.getType(); 
          for (Object in : injected) { 
          boolean inject = t.equals(in.getClass()); 
          if (!inject) { 
            for (Class<?> i : in.getClass().getInterfaces()) { 
              if (t.equals(i)) { 
                inject = true; 
                break; 
              } 
            } 
          } 
          if (inject) { 
            // Inject stuff 
            f.set(target, in);
            break; 
          } 
        } 
      } catch (IllegalArgumentException e) { 
        Log.w(TAG, e); 
      } catch (IllegalAccessException e) { 
        Log.w(TAG, e); 
      } 
    }
  }
} 

And... we're done. That was it. In this case I have a field mProvider of type IProvider. The performInjection() method takes a target, look for all fields that have our @Inject annotation, and if they are null tries to find an argument matching the interface and apply the value.

Typically an Android application or activity instance can configure a few fields like this, which can then be picked up later in the same context. Obviously you could have done the same thing in this case with some getter or setter... In a more complex example, the idea is that you can simplify a bit of code by not having to keep passing some parameter around just to pass it to a method 15 levels down. Instead, isn't much better to have this field auto-magically set, loosely coupled, impossible to make sure who allocated it, with no hard guarantee it's actually set when necessary?

Of course you could just pass a context structure around, like activities do in Android. That's just so simple, efficient and clear, who would want that?

No comments:

Post a Comment