General thoughts on the state of DI frameworks
A few days ago, I was reading Matthias’ Reverse Your Arrows and couldn’t help of thinking about my first experience with a dependency injection (DI) framework — Spring, back then. I remember pressing play in Eclipse, Tomcat launched, and after a few minutes I saw a red message. There was a mistake in my XML bindings :(
When I started Android development (api version 1.5) Roboguice was the only DI framework for Android. Guice API was, and still is, amazing. Light years ahead of Spring. The same year Dagger was released, I moved to the US to join Groupon. In Groupon, I had the opportunity to have long talks over beers with Roboguice’s main contributors, and friends of mine, Mike Burton and Stephane Nicolas. Stephane got a bit obsessed with DI, he kept improving RG and he even modified Guice itself to make RG faster. After his change was not approved by Guice’s maintainers, he created a completely new DI library: ToothPick. Also around that time Dagger 2 was released, and a few weeks ago, Tiger got released.
We’ve definitely come a long way.
Nowadays, I am working at Fitbit, where the dependency injection inside the app is done without libraries. Would it be worth it to add one? Perhaps. In my free time I have been playing with different DI frameworks to figure out the answer to that question. While doing so, I found three concepts I would like to explore with you.
Every app has dependencies. Some dependencies are critical to start an app and some others are “nice to have”. Let’s say our app considers a network library dependency as critical to start. Since it takes some time to load those dependencies, it’s a good idea to show a branded launch while we wait (See @cyrilmottier’s Launch Screens: From a Tap to Your App). How can we do that?
I found two ways of implementing async injections of dependencies:
Dagger 2’s Producers is a no-go for Android because of the Guava usage. The RxJava solution is very smart, but it has a caveat: what if my critical module has more than one dependency? For example, what if apart from loading my network library, I also want to load my DB manager. ̶T̶h̶e̶n̶,̶ ̶o̶u̶r̶ ̶o̶n̶l̶y̶ ̶o̶p̶t̶i̶o̶n̶ ̶i̶s̶ ̶d̶o̶i̶n̶g̶ ̶i̶t̶ ̶b̶y̶ ̶h̶a̶n̶d̶ ̶w̶h̶i̶c̶h̶ ̶i̶s̶ ̶f̶i̶n̶e̶,̶ ̶b̶u̶t̶ ̶I̶ ̶a̶m̶ ̶l̶a̶z̶y̶.̶ Jake Wharton pointed out here that this can be done with RxJava easily. Here’s the code he used to explain the logic:
What if we could listen to the DI framework creation? The code would look something like this in the Dagger 2 world:
This API would not require Guava, solving this problem in an elegant way.
The first custom scope I used was the request scope. Mind blown. Handling lifecycle of objects based on scopes is powerful and allows developers to architect the app in an simple way. I was thrilled when I heard that Dagger 2 would “improve” the custom scope situation in Dagger 1, but after trying it out, I have to say, I was a bit disappointed. Let me explain you why.
The most common custom scope everyone would use is the UserScope. User logs in, the app enters the UserScope. User logs out, the app exits the UserScope. Two possible implementations I have seen so far of the UserScope are:
While Fernando saves the userComponent in the application, Miroslaw creates a singleton to hold the userComponent. From his blog post:
UserManager is a singleton object so it lives as long as Application and is responsible for UserComponent which reference is kept as long as user session exists. When user decides to close session, component is removed so all objects which are @UserScope annotated should be ready for GC.
I feel Dagger 2 is doing only half of the work here. The developer needs to take care of the lifecycle of the component. What’s the point then? I really don’t see the benefit in defining a UserScope if it’s just the same thing as defining a separate component and creating/destroying it when needed.
Think about an example in which you would use nested scopes: Singleton, User and ShoppingCart. Where would you put the ShoppingCart component? Would you create another manager class? Would it live inside the UserManager class?
ToothPick, on the other hand, nailed scopes. It offers the user an openScope() and closeScope(). TP will take care of the lifecycle of your objects, you just need to tell it when you are entering or exiting a scope. Why Dagger 2 doesn’t offer something like this?
Objects changing based on scopes
This next section is a somewhat controversial. I have discussed this with other developers and some of them think it’s an abuse of the DI framework. I haven’t thought of an API for this section, but the use case is the following: on a scope change certain objects will change their behavior. A trivial example would be:
- My main component has an analytics logger
- Scope change: User logs in (UserScope)
- The analytics manager starts sending the user encoded id in all logged events
Another example is the API entity. While outside of the UserScope we would only be able to do non authenticated calls, when the user logs in and we enter the UserScope, we would be able to do more calls.
Aren’t you applying this same logic somewhere in your app? What does your logout() method look like? Wouldn’t it be easier if objects were scope-aware?
Is it overkill to bake this functionality inside the DI library? How would the DI framework implement it? Would entering a scope decorate an instance? Should this use case have two different instances? Should the graph be recreated when the scope changes?
I think I need to grab a beer with Stephane… :)