Dependency injection android

Dependency injection:

Dependency injection refers to a set of techniques used for construction and/or initialization of composite objects in Object Oriented Programming.

The ultimate result of correct usage of dependency injection is that the codebase can be divided into two disjoint sets of classes:

  • Functional set: set of classes that encapsulate application’s functionality
  • Factory set: set of classes that encapsulate details of how objects from functional set should be constructed

Since the above sets of classes are disjoint, objects that encapsulate application’s functionality mustn’t, by definition, construct other objects from functional set. Dependency injection techniques represent a set of rules and practices that aim at achieving such a separation of concerns.

In context of dependencies between classes, when one class from functional set depends on another class from functional set in order to perform its function, we can say that dependent class is a client, whereas the class upon which the client depends is a service (not to confuse with Android Service class). This terminology will be used throughout this post.

Introduction to DI is outside the scope of this post, but reading three highest voted answers to this SO question should provide sufficient background for our discussion here.

Dependency injection in Android:

The topic of Dependency Injection has been neglected for a very long time by Android official documentation and guidelines. Recently, however, it started to gain a lot of attention. This is, undoubtedly, a welcome change and a sign of ongoing maturing of Android platform, but a lack of good guidelines in this context causes a massive abuse of dependency injection frameworks, which is the opposite extreme that should be avoided.

Today, terms dependency injection and dependency injection framework are being used interchangeably in Android community, even though these terms are not synonyms. This is not that surprising given the fact that today, search for “android dependency injection” on Google yields two references to Dagger framework homepages as the topmost results (one for each Google and Square).

However, what is way worse is that the techniques themselves are also being used interchangeably – dependency injection frameworks are being used instead of simpler dependency injection approaches, like constructor injection (will be discussed later). Such an abuse of dependency injection frameworks will negatively impact the codebase in the long run, even if it looks pretty innocent on the first glance.

Dependency injection frameworks:

Dependency injection frameworks are third party libraries that assist in performing dependency injection. Please note the “assist” word – frameworks are not required for dependency injection, but simply make it easier to perform dependency injection in some cases.

The most popular dependency injection frameworks for Android today are Dagger 2 (originally created by Square, now being maintained by Google) and RoboGuice (Google). Review of either of these dependency injection frameworks is outside the scope of this post. For our current discussion, it is enough to know that both above dependency injection frameworks are annotation based, and that the following code states that mSomeService member can be injected using dependency injection framework after instantiation of SomeClient object:

public class SomeClient {
    @Inject SomeService mSomeService;

    public SomeClient() {}

    public void doSomething() {
        mSomeService.doSomething();
    }    
}

Dependency injection in Android best practices:

The following list of best practices is my personal list of “rules of thumb” when it comes to dependency injection in Android.

1. Use constructor injection wherever possible:

Constructor injection simply means that the client receives a reference to the service as constructor parameter. So, instead of this:

public class SomeClient {
    @Inject SomeService mSomeService; // will be injected using dependency injection framework

    public SomeClient() {}

}

do this:

public class SomeClient {
    private SomeService mSomeService; 

    public SomeClient(SomeService someService) {
        mSomeService = someService;
    }

}

Advantages of constructor injection over injection using a framework:

  • Client does not depend on dependency injection framework and its annotations
  • Client’s API reflects all its dependencies
  • Client can be unit tested as “black box” – no need to read its code in order to find out what classes should be mocked

And when I say “use constructor injection wherever possible”, I mean that only if you don’t have control over client’s source code, or you don’t instantiate the client yourself (as is the case with e.g. Activiy), should you resolve to dependency injection techniques other than constructor injection.

This is the most important statement in this post, and if you ought to remember just one best practice, remember this one. The importance of this best practice stems from the fact that dependencies need to be not only injected, but also managed.

When I find myself in position of adding sixth service parameter to client’s constructor, it is a hint for me that this client might have too many responsibilities. Dependency injection frameworks “obfuscate” client’s dependencies, which makes it very easy to end up with a very strong coupling between classes of functional set. In addition, if tomorrow a better dependency injection framework will be published (or current framework will be updated with non compatible changes), I wouldn’t want to edit hundreds of classes in order to switch to it.

2. Use method injection for custom views:

If you need any service to be injected into a subclass of View which you declare in XML layout file, you can’t use constructor injection. However, don’t resolve to dependency injection frameworks in this case either, but use method injection instead. So, instead of this:

public class SomeClient extends LinearLayout {
    @Inject SomeService mSomeService; // will be injected using dependency injection framework

    public SomeClient(Context context) {
        super(context);
        init();
    }

}

do this:

public class SomeClient extends LinearLayout {
    private SomeService mSomeService;

    public SomeClient(Context context) {
        super(context);
        init();
    }

    public void setSomeService(SomeService someService) {
        mSomeService = someService;
    }

}

The advantages are the same as with constructor injection, though method injection requires more work in this case.

3. Use dependency injection frameworks for top level components only:

When not abused, dependency injection frameworks provide a great deal of convenience in implementation of what we designated as factory set. At the end of a day, the logic that wires together all the classes from functional set must reside somewhere within your application, and it is where dependency injection frameworks come in very handy. Furthermore, somewhere within your application there must be interfaces between factory set and functional set of classes – these interfaces are the classes into which you inject dependencies using dependency injection framework.

In my opinion, top level components of Android application are the best candidates for becoming the aforementioned interfaces. When I say “top level components” I mean: Application, Activity, Service, ContentProvider and Fragment.

You can notice that all listed components except Fragment are being instantiated by Android framework for us, which makes them non-eligible for constructor injection. Since we can’t perform constructor injection, and it is not evident at all how to perform method injection on these components, it is only logical to employ dependency injection frameworks in order to inject services into them. So, instead of this:

    public class MainActivity extends Activity {
        private SomeService mSomeService;
        private SomeGlobalService mSomeGlobalService;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            setContentView(R.layout.layout_main);
            mSomeService = new SomeService();
            mSomeGlobalService = ((MyApplication)getApplication()).getSomeGlobalService();
        }
    }

do this:

    public class MainActivity extends Activity {
        @Inject SomeService mSomeService;
        @Inject SomeGlobalService mSomeGlobalService;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            setContentView(R.layout.layout_main);
            getFrameworkInjector().inject(this); // perform injection into "this" using a framework
        }
    }

Fragment is an exception here because we could use method injection in order to inject its dependencies from the containing Activity, but it would be very cumbersome due to the fact that Fragment has its own life-cycle, and there would be too many injection methods. Therefore, it is cleaner approach to inject Fragment's dependencies using dependency injection framework.

The technique of injection into Fragment is almost identical to the technique shown for an Activity above, except that, from my experience, the best place to call inject(this) method on Fragment is in its onAttach(Activity) method.

4. Don’t violate the Law of Demeter:

Law of Demeter, when applied in context of dependency injection, can be stated as “a client should be injected with the exact services that it needs”. One very common violation of Law of Demeter in Android is when a Context being injected into client, while what this client really needs is a reference to SharedPreferences (or reference to some system service, or any other reference retrieved from Context). So, instead of this:

    public class SomeClient {
        private SharedPreferences mSharedPreferences;

        public SomeClient(Context context) {
            mSharedPreferences = 
                    context.getSharedPreferences(PREFS_FILE_NAME, Context.MODE_PRIVATE);
        }
    }

do this:

    public class SomeClient {
        private SharedPreferences mSharedPreferences;

        public SomeClient(SharedPreferences sharedPreferences) {
            mSharedPreferences = sharedPreferences;
        }
    }

Law of Demeter should be obeyed both when you use dependency injection framework and when you don’t.

Advantages of dependency injection without violations of Law of Demeter:

  • Client’s API reflects all its real dependencies
  • Client can be unit tested as “black box” – no need to read its code in order to find out what classes should be mocked
  • Unit testing is easier because you don’t need to mock services’ getter methods in order to mock the real service that the client uses

Conclusion:

When it comes to dependency injection, there is no question whether to use it or not – it is a must. But there are no strict guidelines about how exactly dependency injection should be carried out in Android, which can lead to inefficient practices being employed.

In this post we discussed a general aspects of dependency injection and introduced a set of best practices for dependency injection in Android. In my experience, following these best practices yields a nicely decoupled code and very natural non-abusive integration of dependency injection frameworks into a workflow on Android.

Please leave your comments and questions below, and consider subscribing to our newsletter if you liked the post.

Leave a Comment

Your email address will not be published. Required fields are marked *