In this talk from 360|AnDev, Daniel Lew talks about Dependency Injections Made Simple!
“Dependency injection” and the “dependency inversion principle” sound like difficult, complex patterns but are actually quite simple! In fact, you probably use them all the time without realizing it. By understanding it, you can begin to truly harness its power. This talk from 360|AnDev will go over what it means to use dependency injection and why it’s an important pattern in software development. Then we’ll dive into the practical application of dependency injection via the Dagger library.
I’m Dan Lew. I work at Trello and today I am here to talk about dependency injection. For me, dependency injection is one of those phrases that I heard pretty early on in my career and then immediately shied away from because it sounds really complicated. But as it turns out, it’s incredibly simple, and if there were to be a thesis of the talk, it’s that you’re already using dependency injection. I want to explain to you what exactly it is and how you can better take advantage of it now that you know you’re using it all over the place.
Let’s talk about what a dependency is. Very simply, it’s when one component of your app depends on another component of your app. And in fact, this happens all over the place in programming. You would have to create a trivially small program in order to not have any actual dependencies. One could probably argue that the monitor, the keyboard, or input and output is still a dependency of your program. For example, you might have a data provider that provides your actual data objects, and that might depend on a database layer. You might have an image loader that’s loading all your images from the network and that requires some HTTP layer. You might be having some REST calls in your app, and again, that would depend on HTTP existing. REST itself also may depend on a de-serializer, so once you get the data from your REST service, then you need to turn it into something you can work with on the system. Not only can one dependency be provided to multiple things, but a single thing can also require multiple dependencies. Then if you’re talking about a login screen, maybe it depends on all that stuff above. That’s the dependency.
Here’s a class that doesn’t have dependency injection. I have my Example class, inside is the Dependency, and in the constructor, I create an instance of that dependency.
Your basic view constructor takes a context. Ta da. That’s a dependency. The view requires the context to do all sorts of work within itself and this is a case of constructor dependency injection.
When you style a view a lot of the style attributes come from the theme, and the theme is stored in the context. You can use what’s known as a ContextThemeWrapper to take a context and then wrap it with a new theme. If you want a particular view to look different, you can use a ContextThemeWrapper on it and then in the constructor pass that wrapped context to use that new theme. In fact, this is how AppCompat was able to backport all of the view level themeing that seemed it shouldn’t be possible to backport because it was a 5.0 feature. They managed to get it much further back because this feature has always been around. That is really handy because now I can configure my context externally. For example, if I want it to have my theme as the base theme, and then pass it into a view, the view doesn’t have to know anything about what sort of styling I’m doing to it. That’s dependency injection in a nutshell.
That is super uninterpretable. I still would have trouble explaining what the dependency inversion principle is to someone based on this text alone. Let’s ignore all that and go through an example from one of Uncle Bob’s papers about what the dependency inversion principle is, to understand what all that text was trying to get to. To go back to what dependencies are, again, I’m saying A depends on B. Another way to frame that is to say that A is coupled to B. The two pieces of code have to have some coupling point and what the dependency inversion principle gets at is how tightly these two are coupled together. When they are tightly coupled, it makes it harder to modify the code, whereas if they’re loosely coupled, it makes it much easier to modify the code.
Let’s go through an example where we say that we have an application. In this application, you have a keyboard and whatever you type gets printed out to a printer. If I were to write a very simple version of it, I might have three modules. I would have a module that tells me how to read a keyboard, a module that allows me to write to a printer, and then I’d have some copy module that combines the two and creates the behavior that I want.
Here we have some very simple code; I have a while loop that’s saying, “Keep reading from the keyboard until it gets to some end-of-file indicator,” which would be minus one. While I’m reading from it, I’ll keep writing to the printer. So while this code is very simple, it turns out it’s very hard to modify. For example, what if I want to have multiple options for where I want to read from? Maybe I don’t always want to read from the keyboard; sometimes I want to read from the disk.
A very naive version would be, let’s have a configuration value in copy where I tell you what the input device will be. Now I need to be constantly checking the enum. I guess you could also split this into maybe a large if and have multiple whiles, but it’s a pain, and you’re going to have to repeat this code for every new type of input that you would want. The same pain happens if you want to modify where you would output.
Maybe you don’t want to always be printing out and wasting paper, especially while you’re developing this program of yours. If I want to print out to the monitor sometimes, again, I might create an enum and allow some external configuration. You can imagine that this code will get very gnarly, very quickly, especially if you wanted to allow configuration of the input and output device. Another problem is that these modules are so tightly coupled it gets hard to know what exactly I want to test in the read and write modules. I don’t know which parts of the read and write modules are important to copy. There might be a whole bunch of methods inside each of them, and I don’t know which of them are important to the outside world. Even more importantly, there is absolutely no way to test that copy works correctly because copy as it sits right now, is reading from a keyboard and writing to a printer. To truly test its capabilities, I’d have to come up with some mechanical robot that would type on the keyboard and then I’d have to have a printer nearby and some scanner that can make sure that what’s being printed on the paper is exactly what I was expecting. There is absolutely no way to test copy.
This is where dependency inversion comes into play. What we do is we invert the normal dependencies that you’d expect, where instead of having copy, and keyboard, and printer all depending directly on each other, what we do is stick an interface in between. The idea of the inversion here is that instead of them being directly coupled, now they both point to this interface in the middle. Now I have an interface Reader, an interface Writer, and in copy I’m passing in a Reader and a Writer.
The logic is pretty much the same, but now I’m no longer tightly coupled to that particular implementation of the keyboard reader and the printer writer. Why would we want to do this? Again, we get the same things that we got from dependency injection. We can share dependencies. You probably only have one keyboard and one printer hooked up to your computer. You want to be able to share that amongst multiple parts of your application. Again, you can configure dependencies externally, and this is extremely useful because I can pass any sort of reader or writer into my copy module. I don’t have to have it be a keyboard reader, I can also make a disk reader, and as long as that also implements the Reader interface, I can use that. The same with the printer writer. As long as I implement that Writer interface, I can use any sort of writer that I want. Now my copy module, the logic is simple. It stays the same regardless of whatever implementation I pass in. If I want to add a new reader device or a new interface implementation, I don’t have to go and modify copy itself, which is great. That also gives me a nice clean separation of modules because now I know in this set up where the different actual implementations are because I have this copy reader and writer, but they’re completely separate. All I know is that they have this contract in the middle that is the interface, and I can write my keyboard writer however I want, test it however I want, as long as I know that all that really matters is the fact that it implements reader. That’s the external facing API for it. That makes testing easier and possible with the copy module because all that copy sees are the interfaces. It doesn’t see the actual implementations. That means if I want to test the logic of how copy works, all I need to do is provide some test versions of the reader and a writer. What I’d end up with is some method this.
I’ve written a test reader, and all it has is a string that I pass into it and each time read is called, it’ll shoot out another character from that string and a test writer. All it does is hoovers up the different characters that are passed to it and then I can assert later on what was written to it. Before, my copy module needed a robot arm reading, typing into a keyboard, and then a scanner reading up from the printer. Now I can test my copy module in four lines of code.
Along those same lines, you can also much more easily do debugging or development with the dependency inversion principle. For the printer example, I don’t want to have to each time I work in the copy module drag out my printer and waste paper. I want to print out to a monitor. But in more practical terms, in an application, maybe when you’re developing your server API, you don’t have access to your server on an airplane. You can create a mock version of your server if you have an interface that you’re attacking, if you’re not having to make HTTP calls every time you want to go to the server. Or maybe you’re on an early start up and the server isn’t even done yet, you can write an interface that are these things that I expect the server to be able to do eventually. You can develop your app against the server that doesn’t exist yet. But I don’t want to paint this as though this is all perfect and wonderful, there are always drawbacks to any choice that you make. The drawbacks are that it is more verbose and complex. You end up with this interface that you’re having to define which implementation, if it turns out there’s only ever one implementation that you’re using, it’s not necessarily that important to do this. You only want to have that interface separation where necessary. It’s very easy to re-factor code that had a copy implementation. My general advice would be to start with the concrete implementations, and then the moment you realize you do want to swap implementations in order to do some testing, or in order to do debugging or something that, at that point, then you can extract an interface. It’ll refactor all your code, and you’ll get to the state.
Dependency injection and dependency inversion are very closely related because they get you the same sort of benefits, but they’re not the same. Dependency inversion is about how you structure the code to take advantage of these contracts in the middle, the interfaces in the middle, whereas dependency injection is how you get those dependencies to those locations.
You might be surprised that I’ve been talking up here for 15 minutes about dependency injection and I haven’t mentioned Dagger yet. Or maybe I did briefly early on, but I haven’t talked about how to use Dagger. That’s surprising because at least the Android world right now, whenever people talk about dependency injection, they’re talking about Dagger. The fact of the matter is, you don’t need Dagger for dependency injection. Dagger helps, and I’m about to explain how Dagger works, but all the stuff I’ve talked about up until now is basic Java logic, and it doesn’t require any dependency injection framework. If you want to use one, I’m going to talk about why you’d want to use one, but as I said, if you want to tune out for the rest of this talk, that’s totally fine because you can implement your own Dagger-less dependency injection if you want.
One is that injection in these simple examples is fairly simple, or easy, but once you start getting a rather larger graph of dependencies, it can be a lot of busy work. One of the main things that these frameworks give you is the ability to skip all that busywork. It does a lot of the wiring for you.
Then a third advantage is that it gives you a nice unified system for dependencies, and this, I think, is a little under appreciated. People all over code might know how dependency injection works and think it’s a pretty cool pattern, but they each come up with their own pattern in their own individual part of the code and suddenly you’ve got 10 different ways to do dependency injection across your app. It’s nice to have one unified system for it. This is the world of using libraries right now because every library that has dependency injection has to do it in its own way.
There are a bunch of frameworks that you can choose from. Some of these are more Java server based and don’t work as well in Android. The ones that are, that were written specifically for Android are Dagger. They also work and have proven to be very useful outside of Android. There used to be annotation processing slow downs on Android which is why people wrote Dagger, which had a lot of compile time optimizations. My talk is going to focus on Dagger One, which may surprise people because I see that there is a Dagger Two, which sounds better. But the fact of the matter is, I know Dagger One a lot better, and I haven’t had a reason to upgrade to Dagger Two yet because it’s some amount of work to upgrade. I’d rather talk about something that I know a lot about than something I know very little about. Suffice to say, though, it’s not a huge upgrade from Dagger One to Dagger Two and the things I’m going to be talking about are the basic utilities that a dependency injection framework gives you.
The usefulness of having @Inject annotated member fields is in case you have a constructor that isn’t a list of dependencies. If you have a constructor that’s also taking some configuration on top of that, then you might need to take those variables in and then inject the dependencies later.
Again, with my reader-writer example from earlier, I have this interface Reader and I have an implementation of it as a keyboardReader. In the code, I don’t say inject a keyboardReader, I say, “I would like you to inject a reader,” but it doesn’t know that the reader that you want is keyboardReader. That’s where provides comes in. But in my module now I can say, when people are looking for an instance of reader, explicitly return an instance of keyboardReader. This is really handy because now I can do that dependency inversion, and also maybe in tests, you can have a different module, and in that different module, what you return is a text reader instead of a keyboard reader.
This has been really helpful in Trello because we have multiple HTTP clients that have different interceptors that we’re using with them. One of them wants to talk to the Trello server, and it needs special auth-headers to talk to the server. Some of it’s talking to images on the web and AWS is not happy if we’re sending weird headers to it. So in that case, it’s useful to have qualifiers to say this version is one that talks to Trello and this version, the client is the one that talks to images or the world in general.
If I have some activity with a dependency, I can say in onCreate to get a copy of my object graph. In this case, I’m going to store the object graph in the application. You can come up with your own solutions for where you want to store the object graph. Then once you have your object graph, you can call inject this and it’ll put that dependency into your activity. This is a fairly common pattern with things that the Android framework creates that you don’t control. A really good sample project that I highly recommend anyone who’s getting into Dagger to look at is U2020. It was one of Jake Wharton’s sample apps and was written by him for a talk about Dagger. If you’re wondering about the name, that’s the unicode character for Dagger. U2020. It has this cool injector pattern for injecting object graphs via the context, which is a very nifty way to get your object graphs into your system. You can find it on GitHub.
Q: Why not upgrade from Dagger One to Dagger Two?
Daniel: The main thing for me has to do more with refactoring. Refactoring is a surprisingly high cost in development and it’s very easy to get in this process where as a programmer it feels really good to refactor code, but it doesn’t necessarily get you anything new. The advantages that Dagger Two were going to give us were minimal. One of the things it does is it doesn’t use reflection anymore, which means it’s a little bit faster. I think it’s about 50% faster than Dagger One, which is really cool, but we don’t spend most of our time injecting dependencies on an Android app. We create an activity, and then we inject some dependencies, and we’re done. That matters a lot more if you’re running some gigantic server and every new request that comes in, you’re injecting, and you’re trying to do thousands of requests a second. That sort of performance matters there. It doesn’t matter as much to us. Also, Dagger Two has a slightly more academic view to how graphs should be set up. One thing that Dagger One does allow is overrides. So you can say that my module has a reader and then in my application it provides a keyboard reader, but then in my test, I can say, “I’m going to take that module, and I’m going to override what it’s providing.” Now instead of saying I have two different modules, and I’m going to access two different ones; instead I’m going to say I have one module, and then I’m going to put another module on top that overrides. We’re using that setup right now. If we were to switch to Dagger Two, we’d have to restructure our module such that we don’t use overrides, which might be a worthy goal at some point for improving the architecture of our general app, but at the moment, again, refactoring is expensive. It doesn’t get us anything. Those are the main reasons we haven’t upgraded yet, but there’s certainly reasons to upgrade.
Q: If sometimes I have to use field injections, why not always use field injections?
Daniel: You could do that. I like doing constructor injection when possible because then I can say very upfront, these are the dependencies that this class depends on, right here. It makes testing a lot easier because, in your tests, you might be tempted to use Dagger to inject all the dependencies for constructing your test objects, but if you have everything as a constructor injection, you’re not depending on some magical setter happening from the outside. You can avoid your whole Dagger stuff in the test entirely. If I’m testing my class with dependencies A and B, I can create all of that in a set up before the test runs. Whereas if I was doing field injection, I would have to provide both ways. The main reason is that it makes the logic in my head a lot easier to know that this is the actual list of dependencies I need right off the bat.
Q: If you were to set up your own dependency injector, how would you do it?
Daniel: My answer is I wouldn’t. I don’t want to go through that work. I think more what I was implying is that on a small scale, you can do it all by hand. For me, it’s not an interesting problem to write my own dependency injector because they already exist. That’s a useful skill to have if you want to understand exactly what’s going on under the hood, but I’ve never done it myself.
Dan has been working on Android for over six years. He first started at Mobiata, went on to lead the Android team at Expedia, and is currently working at Trello. He’s a Google Developer Expert for Android and has been giving talks on various Android topics for years. He’s released a couple of open-source Android libraries and several open-source apps on Github. He also writes regularly about development.