In the past couple of years dependency injection became a hot topic among professional Android developers. That’s great because dependency injection is one of the best architectural patterns in object-oriented design and applications that use it are much easier to develop and maintain. However, many developers still struggle with its adoption. Well, no wonder.
Dependency injection is a very complex architectural pattern which, in addition, suffers from a terminological ambivalence. Furthermore, the term itself is often conflated with another closely related, but distinct concept: dependency injection framework. Usually these frameworks are very complex, so if you start your journey with them, you risk being overwhelmed by complexity.
Therefore, in my opinion, if you want to master dependency injection, then your best bet is to learn it from the very first theoretical principles. In this post I’ll explain the theoretical foundation of dependency injection and give several practical recommendations about its usage in Android applications.
Client service terminology:
First things first, so let’s talk about the terminology a bit.
When code in class A references class B, I say that class A depends on class B. I can also say that class A is a “client” of class B, or class B is a “service” for class A. This client-service terminology is very common in software development and has nothing to do with Android specific classes like Service.
Please note that if code in class B also 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 when looked from different perspectives.
Dependency injection:
Dependency injection is an overloaded term and different developers can mean different things when using it. Therefore, let me explain what I mean when I use this term exactly.
The basic usage of “dependency injection” refers to the action of providing services to clients from “outside”. In other words, whenever clients need services they simply ask for them instead of trying to construct these services internally.
Dependency Injection (note the capitalization), on the other hand, is an architectural pattern (you can think of architectural patterns as design patterns on steroids). Dependency Injection architectural pattern includes the theory and the practices provide you with a guidance as to how to structure components in your application. Don’t worry if this sounds unclear right now. I will explain these concepts in great details in the following sections.
Note that the action of injecting dependencies is related to a single class, while Dependency Injection architectural pattern involves the entire application. In other words, these concepts “live” at vastly different levels of abstraction and, therefore, should be discussed separately.
Dependency injection fundamental techniques:
As I already said, the basic meaning of dependency injection is the action of providing (injecting) services to clients from “outside”.
In Java (which is similar to Kotlin and many other object-oriented languages), there are three ways of doing that: Constructor Injection, Method Injection and Field Injection. You can think of these three injection methods as the fundamental techniques of dependency injection.
Dependency Injection architectural pattern:
The main characteristic of correct implementation of Dependency Injection architectural pattern is segregation of application’s logic into two disjoint 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
Note that I used the word “disjoint”, which is a rigorous mathematical definition. In order for the Functional and Construction sets of classes to be disjoint, the following conditions must be satisfied:
- Classes that encapsulate core application’s functionality mustn’t resolve dependencies or instantiate classes from Functional set
- Classes that resolve dependencies or instantiate classes from Functional set mustn’t encapsulate any of core application’s functionality
So, if you use dependency injection you’ll end up with two piles of classes. One pile contain just pure functionality, and another pile knows how to wire objects from the former pile together. This segregation of logic into Functional and Construction sets of classes is manifestation of Separation of Concerns principle at application level of abstraction.
Functional and Construction sets integration:
Though disjoint, Construction and Functional sets must be integrated together. At the end of a day, they complement each other and constitute a single application.
There are two main approaches to this integration:
- Pure Dependency Injection (aka. Poor Man’s Dependency Injection)
- Dependency injection frameworks
Let’s review each of them individually.
Pure Dependency Injection:
Pure Dependency Injection is a manual approach. If you choose to use Pure Dependency Injection, you are in charge of designing and implementing all the integration logic.
The advantage of Pure Dependency Injection is that you have a complete control over implementation and do not depend on any third party libraries and tools. In many cases, this can also be the simplest approach because the flow of control is easy to follow and there is no “magic” involved.
The downside of Pure Dependency Injection is that it is very easy to get wrong. If the team is not skilled or not disciplined, an attempt at Pure Dependency Injection can turn your application into a mess. In addition, all the logic, including a considerable amount of boilerplate, will need to be written manually from scratch.
While it sounds kind of risky (and it is), don’t e quick to dismiss Pure Dependency Injection. It might become a good alternative to dependency injection frameworks if your project grows to the point when the overhead introduced by frameworks becomes an issue.
Dependency Injection frameworks:
Dependency injection frameworks are libraries that assist you with dependency injection architectural pattern. In essence, these frameworks are templates for your implementation of Construction set of classes and the logic that integrates it with Functional set.
The template provided by the framework will usually promote many best practices which will make your life easier. In addition, if you use dependency injection framework you’ll need to make much fewer decisions. This means that you’ll have less space to make a mistake, and that’s a big plus if you implement dependency injection for the first time.
The integration template provided by dependency injection frameworks is usually built according to Convention over Configuration principle. These conventions can be annotation based, use XML documents, or other similar approaches. In addition, frameworks can resolve dependencies at different stages: some frameworks resolve dependencies at compile time, while others postpone the resolution to runtime.
All in all, if you use a mature dependency injection framework and you understand how it works, it can spare you a lot of effort and headache.
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 the platform. However, lack of good guidelines in this context causes a massive abuse of dependency injection frameworks, which is the opposite extreme that should be avoided. In the remaining of this article I’ll share with you several best practices related to Dependency Injection in Android that will help you avoid some of the more common mistakes.
Since the most popular choice for dependency injection in Android is Dagger 2 dependency injection framework, the code snippets that you’ll see will use its syntax. However, keep in mind that these best practices are universal and apply to any other framework you might want to use as well.
1. Use constructor injection by default:
Whenever possible, clients should ask for all their services through constructor arguments.
The advantages of constructor injection are:
- The code becomes more readable because all the dependencies are explicitly stated in constructors.
- You can’t forget to pass services to clients because the compiler will flag missing constructor arguments.
- Services injected into constructors can be finalized which is important in the context of multi-threaded code.
- Constructor arguments are the easiest to mock in unit tests.
So, the first rule is that you should always use constructor injection, unless there is very specific reasons not to do that.
2. Use field injection for Android top-level components:
There are two groups of “top-level” components in Android:
- Components that Android framework instantiates for you:
Application
,Activity
,Service
, etc. Fragment
Since you don’t instantiate the components from the first group yourself, you can’t use constructor injection with them. And even though you can instantiate Fragments, you should still do that using their “no-arguments” default constructor, so you won’t be passing services into them directly.
Since you can’t use constructor injection, you must fall back to either method injection or field injection. I recommend that you choose field injection in these cases because method injection won’t provide you any benefits, but can definitely make your code more difficult to read and understand.
3. Don’t use dependency injection framework to inject into custom View subclasses:
If you need any service to be injected into a subclass of View
, and this View
can be instantiated programmatically, use constructor injection. That’s simple.
However, even if the View
is declared in XML, don’t resolve to dependency injection frameworks. Use regular 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; 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 are visible at the API level.
- Method Injection does not open door to Single Responsibility Principle violation.
- No dependency on the framework.
- Better performance.
Let’s unpack the above claims a bit.
First of all, dependencies injected with method injection will appear as part of clients’ public API and readers of the source code will immediately see them.
Secondly, there are not many use cases in which sub-classes of View
need additional dependencies. However, by injecting even one single dependency using a framework you basically open a door for Single Responsibility Principle violation. In many cases it will be very tempting to compromise the quality of the design a bit and inject “that one additional object” into a custom View to implement a little hack.
These little compromises will accumulate and after some time your custom Views might turn into spaghetti of UI and business logic. And if you’re confident that it won’t happen to you, don’t forget that other less experienced developers can make this mistake and they won’t be able to realize their mistake.
The third advantage of using Method Injection with custom Views is that you don’t couple them to dependency injection framework. Just imagine that some time from now the framework needs to be replaced or completely removed. The fact that you will probably have tens of Activities
and Fragments
to start with already make such a refactoring a big project. You definitely don’t want to additionally handle tens or hundreds of custom Views.
The last 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.
4. Don’t violate the Law of Demeter:
Law of Demeter, when applied in context of Dependency Injection, states that “a client should be injected with the exact services that it needs”.
In Android, Law of Demeter is commonly violated when you inject Context
into clients to just obtain yet another object from it.
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; } }
Not violating the Law of Demeter gives you the following advantages:
- Clients’ APIs reflect their real dependencies.
- Clients can be unit tested as “black boxes” – no need to read their code to find out which classes should be mocked.
- Unit testing is easier because you don’t need to mock chains of objects
As a starting point to obeying the Law of Demeter, just stop passing Context
around when it’s not strictly required.
5. Differentiate between objects and data structures:
As Matt Carroll explained in this post , subclasses of Object class in Java can be divided into two sets: (object-oriented) objects and data structures.
Objects expose behavior and hide implementation details. For example, UserManager
class could expose logIn()
method.
Data structures expose data. For example, User
class could expose getFirstName()
, getLastName()
, etc. methods.
Dependency Injection is 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 your application’s 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:
In this article you learned what dependency injection is and understood the distinction between fundamental dependency injection techniques and dependency injection architectural pattern.
Now you know that dependency injection frameworks are basically templates for your implementation of Construction set. You also understand that frameworks aren’t strictly required because you can always use Pure Dependency Injection, even though it requires a fair bit of experience to get right. The provided list of best practices for dependency injection in Android will further make your adoption of dependency injection easier and help you avoid common pitfalls.
As I said at the beginning of this post, dependency injection is one of the most beneficial architectural patterns in object-oriented design and it’s a hot topic in Android community. If you learn it properly, you’ll advance your skill of writing decoupled and maintainable code. That’s both professionally and commercially rewarding.
Now, if you’ve read this far, you might also be interested in my best-selling course Dependency Injection in Android with Dagger 2. In this course I explain the theory of dependency injection and show how to implement Pure Dependency Injection in Android application form ground up. Then I refactor that application to use Dagger 2 dependency injection framework. Such a structured, step-by-step approach allows you to see what dependency injection really is and fully appreciate the role of dependency injection frameworks.
That’s all for now, thanks for reading.
Feel free to comment and ask questions below, and consider subscribing to our newsletter if you liked this post.
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
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.
It is clearer now, thank you :-).
I think you could still grook it a little bit if you want to use constructor injection in your custom view to take advantage of onCreateView of AppCompatActivity and do the magic there.
Hey Carl,
I’m not sure I understand what you’re saying. Could you elaborate a bit?
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.
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.
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.
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.
“Method Injection does not open door to Single Responsibility Principle violation”
only when class that relies on Method Injection, uses abstraction that gets concrete implementation via the method param.
Hey Mujahid,
If you mean that the injected objects should not open door to SRP violation by themselves then I strongly agree. That’s one of the reasons Context objects should not be passed around – any class that has an access to Context effectively has an access to the entire Android framework as well.
I found this on a recent comment you made on a post in the android reddit. I look forward to going through your blog and eventually your course. Thanks!
Hi Lee,
Welcome aboard. Can’t wait to hear your feedback on the blog after you read it.
Very thoughtful, I love your reasoning’s and crystal clear explanations.
I agree 100% with you on the fact that people shouldn’t be using Dagger Android, it pollutes code and makes it harder to track down the usages within dependency graph.
Also I see very often that people are providing Fragments, Adapters, ViewHolder’s, LayoutManager’s etc as dependencies through Dagger, that according to me is clearly wrong, I see that this pattern will cause performance issues, especially when people don’t understand the concepts of scoping and scope these to Application level, there’s no official word on this from either the Android team or Dagger team, but I found this issue on their Github page in which atleast one person agrees that this is wrong. (https://github.com/google/dagger/issues/720).
I have one suggestion, please add created/updated date to your blogs on top or bottom, I reached this blog from your StackOverflow post (https://stackoverflow.com/a/45195508/892055) and I could’t figure out if it was up-to date, the rest as usual is marvelous.
Hey Arif,
Thanks for the feedback and for the warm words.
I agree that some objects are very tricky to put on the objects graph. For instance, I don’t think that injecting individual Android Views or Adapters is a good idea. That said FWIW, I always add LayoutManager to the objects graph.
This specific post is relatively up-to-date. I would like to revisit the best practices a bit though: they need a better formulation and, potentially, I might add another 1-2. I’ve been thinking about this since I released my DI course, but I haven’t found a form that satisfies me entirely. Feels like I’m missing a bit of insight still.
Your suggestion about having the post date is absolutely great. If I’ll find a way to have the last update date it will be totally awesome. Thanks for recommendation.