Dependency injection android

Dependency Injection is a hot topic today, but do we really understand what Dependency Injection is? Can we distinguish between “good dependency injection” and “bad dependency injection”?

This post is all about Dependency Injection. It is not yet another tutorial of Dagger or Guice. Instead, we will take a step back from tooling and frameworks, and review the theory underlying Dependency Injection and dependency injection frameworks.

After thorough discussion of the general theory, we will “zoom in” and present a set of best practices that should be followed in order to implement proper Dependency Injection in Android.

If you like watching recorded sessions, I gave a talk about Dependency Injection at Android Makers 2017 conference. It was primarily based on the ideas presented in this post, but is not as detailed. You can watch it here.

First things first:

Let’s first clarify the terminology that will be used throughout this article.

When code in class A references class B we say that class A depends on class B. In context of Dependency Injection, we also say that class A is a “client” of class B, and class B is a “service” for class A.

Please note that if code in class B references class C, then class B is a client of class C, and class C is a service for class B. Therefore, the same class can be a client and a service at the same time.

Dependency injection:

“Dependency injection” is an overloaded term.

The basic usage of “dependency injection” (all in lower case) refers to the action of providing (hereinafter: “injecting”) services into clients from “outside” (as opposed to e.g. instantiation of services inside clients).

“Dependency Injection” (note the capitalization), on the other hand, is the name of an architectural pattern that define the techniques, the principles and the associated best practices that “extrapolate” the fundamental techniques of dependency injection to a system level.

Note that the two meanings above imply completely different levels of abstraction: dependency injection “lives” at class level, whereas Dependency Injection “lives” at system level.

The multiplicity of meanings of “dependency injection” causes quite a bit of confusion: most people aren’t aware of the ambiguity of the term, which leads to endless debates over what dependency injection is and what it isn’t. When the level of abstraction is explicitly stated, the discussions tend to turn away from semantics and become more technically oriented.

Dependency injection fundamental techniques:

As stated above, dependency injection refers to the action of injecting services into clients from “outside”.

In Java, there are just three fundamental techniques for performing dependency injection: Constructor Injection, Method Injection and Field Injection.

The interesting fact to note with respect to fundamental dependency injection techniques is that we all use them from day one of our programming career (in object-oriented languages). Therefore, the question “do you use dependency injection” may be automatically translated to “do you use Dependency Injection architectural pattern” (because otherwise it makes no sense).

Dependency Injection architectural pattern:

As was mentioned previously, Dependency Injection architectural pattern define the techniques, the principles and the associated best practices that “extrapolate” the fundamental techniques of dependency injection to a system level.

The main characteristic of correct implementation of Dependency Injection architectural pattern is segregation of application’s logic into two disjoint (in mathematical sense) sets of classes:

  • Functional set: classes in this set encapsulate core application’s functionality
  • Construction set: classes in this set resolve dependencies and construct objects from the Functional set

In order for the above sets of classes to be disjoint, the following conditions must be satisfied:

  1. Classes that encapsulate core application’s functionality mustn’t resolve dependencies or instantiate classes from Functional set
  2. Classes that resolve dependencies or instantiate classes from Functional set mustn’t encapsulate any of core application’s functionality

If you’ll look at the definitions of Functional and Construction sets carefully, you will realize that this segregation of logic is just a manifestation of Separation of Concerns principle. In other words, we are just trying to separate two system level concerns: core application’s functionality, and application’s “wiring” logic.

Though disjoint, Construction and Functional sets must be integrated together – at the end of a day, they constitute a single application. There are two main approaches for their integration:

  • Pure Dependency Injection (Poor Man’s Dependency Injection)
  • Dependency injection frameworks

Let’s review each approach individually.

Pure Dependency Injection:

Pure Dependency Injection, more widely known as Poor Man’s Dependency Injection, is a “manual” approach to integration of Construction and Functional sets. When using Pure Dependency Injection, you are in charge of designing and implementing all the logic required for the integration.

The change of name from Poor Man’s Dependency Injection to Pure Dependency Injection was proposed by Mark Seemann in his blog post about Pure DI. Mark’s posts made me realize that Poor Man’s Dependency Injection is in fact Poor Man’s Choice of Name, and that Pure Dependency Injection is a much better term that does not introduce prejudices into developers’ minds (as it happened to me).

The advantage of Pure Dependency Injection approach is that you have complete control over the logic and do not depend on any third party code. It is also the most readable approach because the flow of control is “linear” and easy to follow, and there is no “magic” involved.

The downside of Pure Dependency Injection is that it is very easy to get it wrong. If the team is not skilled or not disciplined, or chooses non-optimal implementation, Pure Dependency Injection approach might lead to maintainability overhead. Additional downside is that all the logic, including a considerable amount of boilerplate, should be written from scratch.

I don’t have experience with Pure Dependency Injection myself, but I felt that this approach must be included for completeness. There are very authoritative professionals (like Mark Seemann mentioned above), who claim that Pure Dependency Injection is the best approach to implementation of Dependency Injection architectural pattern. Furthermore, from my own experience, I know that it is not always possible to introduce third-party dependency injection library (discussed below) into application. Reasons might vary from aggressive size limitations (e.g. SDKs development), to a very strict company rules regarding inclusion of any third party code (e.g. security products). Since Pure Dependency Injection can always be implemented, there is really no reason for not using Dependency Injection.

Dependency Injection frameworks:

Dependency injection frameworks are libraries that assist in implementation of Dependency Injection. Please note the “assist” part – frameworks are not required for Dependency Injection, but simply make it easier to implement Dependency Injection in some cases.

These frameworks wrap around Construction set and integrate with Functional set using a pre-defined scheme. This scheme can be annotation based, use XML documents, or be based on any other way of declaration. Once dependency injection library is introduced into the application, the integration between Construction and Functional sets should follow the scheme defined by the framework.

Except for different approaches to declaration of integration schemes, different frameworks can also resolve dependencies at different stages. Some frameworks resolve dependencies at compile time, others postpone the resolution to runtime.

However different the dependency injection frameworks might be, they will usually have much in common. The main characteristics of dependency injection frameworks are:

  1. Inversion of control: this is the main characteristic of any framework in general.
  2. Pre-defined scheme for integration of Construction and Functional sets: this scheme is what differentiates dependency injection frameworks from any other frameworks.
  3. Use fundamental dependency injection techniques under the hood: as we said earlier, there are just three fundamental techniques for dependency injection (Constructor, Method and Field Injections). Any framework will ultimately make use of one or more of these techniques.
  4. Pre-defined schemes for services lifecycle management: frameworks will allow to alter the lifecycle of injected services in order to e.g. implement global instances that are being injected into all clients.
  5. Boiler-plate reduction: dependency injection frameworks will greatly reduce the amount of boilerplate code required for implementation of Dependency Injection.
  6. Testing support: frameworks might provide support for mocking injected services for testing purposes.

While different dependency injection frameworks might have different characteristics, points 1-3 above are common to all of them.

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.

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 the 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();
    }    
}

In the remaining of this post, we will review several best practices for dependency injection in Android.

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 by default:

When I say “use constructor injection by default”, 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.

2. 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 Construction 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 Construction 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. There are two groups of top level components in Android:

  • Components which are being instantiated by Android framework: Application, Activity, Service, etc.
  • Components which are not top level hierarchically, but which are functionally decoupled from their parent component: Fragment, etc.

3. Use Field Injection while injecting using frameworks:

You can notice that all top level components listed in the previous paragraph are non-eligible for Constructor Injection. Since we can’t perform Constructor Injection, we must decide between Method Injection and Field Injection.

In terms of encapsulation, Method Injection does not have any advantage in this case. But Method Injection has some disadvantages:

  • The additional “setter” method pollutes client’s API
  • More boilerplate code required

Due to above reasons I advice using Field Injection while injecting using a framework.

4. 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.

For example, if you need to inject ImageLoader into a custom View, then instead of this:

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

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

}

do this:

public class SomeClient extends LinearLayout {
    private ImageLoader mImageLoader;

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

    public void setImageLoader(ImageLoader imageLoader) {
        mImageLoader = imageLoader;
    }

}

Advantages of using Method Injection in this case are:

  • Dependencies will need to be propagated from top level component (Activity or Fragment)
  • Method Injection does not open door to Single Responsibility Principle violation
  • No dependency on the framework
  • Better performance

The first advantage might come as a surprise because propagation from top level component is harder than adding annotation to fields, and involves more boilerplate code. This is surely a bad thing, right?. Not in this case. In fact, there are two good aspects associated with such a propagation of dependencies. First of all, the dependencies will be visible at the top level component. Therefore, just by looking at e.g. Fragment‘s fields, the reader of the code will immediately understand that this Fragment shows images. Such optimizations for readability makes the system more easily maintainable in the long term. Secondly, there are not many use cases in which sub-classes of View need additional dependencies. The fact that you need to actually work in order to provide these dependencies will give you a bit of time to think about whether providing them is a good design decision to start with.

The second advantage is related to collaborative construction. You might be very experienced software engineer yourself, but you’ll probably have also less experienced teammates. Or it is possible that you’ll leave the project one day, and the guy who will take over will not be as good as you. By injecting one single dependency using a framework, you basically open a door for other injections. Imagine that some data from SharedPreferences becomes required in custom View in order to e.g. fix a bug. One of the less experienced developers might decide that it is a good approach to inject SharedPreferences into custom View directly. Doing this violates Single Responsibility Principle, but that developer might not even be aware of such a concept. Therefore, in the long term, such injection “backdoors” can reduce design quality and lead to long debug sessions.

The third advantage of using Method Injection with custom Views is that you don’t couple the View to dependency injection framework. Just imagine that few years from now you (or some other poor guy) need to replace the framework. The fact that you’ll probably have tens of Activities and Fragments to start with will make your life miserable. If you’ll have additional tens or hundreds of custom Views to handle, then it might bring you into suicidal mood.

The last (but not least) advantage is performance. One screen can contain one Activity, several Fragments and tens of custom Views. Bootstrapping this number of classes using dependency injection framework might degrade application’s performance. It is especially true for reflection based frameworks, but even Dagger carries some performance cost.

5. 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 final SharedPreferences mSharedPreferences;

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

do this:

    public class SomeClient {
        private final 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

6. Differentiate between objects and data structures:

As discussed in this post by Matt Carroll, subclasses of Object class in Java can be divided into two sets: [object-oriented] objects and data structures.

Objects expose behavior. For example, UserManager class could expose authenticateUser method.

Data structures expose data. For Eample, User class could expose first name, last name, etc.

Dependency Injection in general, and the discussion in this post specifically, are both applicable to objects, but not applicable to data structures.

I would even go as far as saying that Construction set should not be aware of data structures at all. If you find yourself in position of referencing data structures in Construction set, then you’re probably already polluting Construction set with functional logic.

Conclusion:

When it comes to Dependency Injection, there is no question whether to use it or not – it is one of the most beneficial architectural patterns in terms of code maintenance. 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 the difference between dependency injection and Dependency Injection, discussed general aspects and quality criteria 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.

This article has 7 comments

  1. Jonathan Reply

    Hello

    First of all, thanks for the excellent article. I have a question about the 4th best practice: “Use method injection for custom views”. Why it is not good to use a DI framework to inject services into the custom view? I couldn’t think of a good reason to not inject it with a framework. Thanks in advance for your time.

    Regards

    • Vasiliy Reply

      Hello Jonathan and thanks for your question.
      You are right – this is the most controversial best practice and it requires justification. In order for all the readers to be able to understand the motivation behind this best practice, I added additional information in the post. Please re-read the respective section.
      Please let me know if this addition won’t clarify matters.

  2. Android dev Reply

    Hi Vasiliy,

    Your post is very clear. Thanks for that.
    However I need to know whether dependency injection is possible in android library without any change needed at application side. As an android library I don’t have access of UI (Activity/Application) and not able to inject dependency.

    Thank you.

    • Vasiliy Reply

      Hello and thanks for your question,

      IMHO you should definitely use Dependency Injection architectural pattern while developing libraries, but not dependency injection framework.

      The downsides of using DI framework in libraries are:

      1) Increases library’s size
      2) Adds unnecessary dependency to the library
      3) Transitively adds unnecessary dependency to the client of the library
      4) Can lead to conflicting dependencies if the client of the library also uses the same DI framework, but different version

      I would do this instead:

      For external dependencies that need to be supplied by the client use constructor injection: when the client wants to use your library, it will need to instantiate some MyLibraryFacade class and pass all the required dependencies into its constructor.

      In order to wire the internal classes of the library that are being used by MyLibraryFacade, you can use either Pure Dependency Injection, or, in some cases, implement Service Locator pattern.

  3. Android dev Reply

    Thanks a lot Vasiliy for your quick support.

    I got your point but I would like to know more on this. As per my findings on Dagger-2 dependency injection is only possible during initialization of android components(Activity/Application/Fragment,etc). We can add modules & components classes in library but injection is only possible in Application/Activity.

    Is it correct understanding? What is your view on this?

    Thanks in advance.

    • Vasiliy Reply

      AFAIK, there is no such limitation on injection – you can use Dagger components in order to inject from any class and into any class. In fact, you can use Dagger even in non-Android projects.

      You CAN use Dagger in order to perform DI inside your library, but, IMHO, you SHOULDN’T do this due to reasons stated above.

Leave a Comment

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