Whilst I am aware of, and appreciative of the merits of both Roboguice and Dagger, I felt that they were not suitable for what I had initially intended to be a simple application.
That is not to say that they are complex or difficult to use but rather I didn't want my application to require another 3rd party library to function correctly. Especially given that my investigation into dependency injection (in Android) had only come about as a result of wanting to unit test a complete and working app.
The answer that I ended up with came about based on my own knowledge and research as well as general trial and error. Hopefully the information below will help someone else.
Constructor injection
My first thought was constructor injection. It seemed logical that injecting the service(s) on which my activity depended would be the easiest way to allow for mocking.
Unfortunately the Android frameworks do not allow for direct instantiation of activities, and as such injecting a dependency in the constructor is not easy enough to be worth doing.
You can however instantiate fragments directly, and as such constructor injection does have a part to play in my overall solution.
Fragments must have a constructor without parameters. That said, a common pattern for constructing fragments with parameters is to use a static factory method which creates the fragment instance. An example of this pattern can be seen here.
I decided that for the purposes of testing my fragments I would inject a dependency factory (a class which lazily creates instances of my fragments dependencies) into my static factory methods.
The benefits of this are twofold:
-
Lazy loading. We do not waste memory instantiating dependencies up front. We create them as/when we need them.
-
Dynamic instantiation parameters. Whilst you might know what dependencies a fragment relies upon, at the point of injection you may well not know the parameters than your dependency requires to instantiate itself correctly. By using a dependency factory we can build our dependency with the correct parameters when we known what they are.
One small problem
There is one small 'problem' with this method (unfortunately).
The example linked above demonstrates passing in simple values and setting them in a bundle - the fragments arguments bundle. What I want to do is pass in an instance of a (potentially very complex) dependency factory. Are there any extra considerations?
The example above sets this data in the arguments bundle because it is this bundle that the Android framework saves/restores when the fragment is recreated. The Android framework recreates fragments by calling their no parameter constructor. As such if you do not save parameters in the arguments bundle they will not be present when the fragment is recreated (for example on screen rotation).
The resolution for this is to either implement the Parcellable interface in your dependency factory and save your instance in the bundle (this is pretty advanced stuff) OR to retain your fragments state on recreation.
When I instantiate my fragment, I set the dependency factory as a property of said fragment. I can then access it to request a dependency anywhere within.
By using setRetainInstance(true)
within my fragments 'onCreate' method, my fragments state (including its instance properties) are maintained by the Android framework. Likewise, if you replace a fragment (adding the transaction to the backstack) and then pop the backstack the instance variables are also retained.
For more information on retained state or for general clarification, I suggest you have a play with my fragment demo from a previous post. You can set your dependency factory as an instance variable, rotate your screen and still have access to that same instance - the arguments bundle is not required.
What about the activities?
As mentioned above, constructor injection does not work with activities.
The only simple way to inject a dependency into an activity is with setter injection. The problem with this however is that (as mentioned above) the instantiation of, and lifecycle of an activity is pretty well defined by the Android framework. There is no opportunity to set your dependency before it is needed.
Even if it were possible one would certainly want to avoid "two-step construction" (an antipattern) - there is a certain irony to making your code patently worse whilst trying to make it testable :)
With no obvious or apparent resolution to my issue it got me thinking about the environment in which I want to test. I have no interest in using Robolectric for the same reasons that I am avoiding Dagger. All I want is a nice simple ActivityUnitTestCase. Eureka !
A call to startActivity
within an ActivityUnitTestCase creates an isolated test environment whereby only the onCreate
method of the activity under test is executed. As such I can inject my dependency factory before it is needed.
What about two step construction you say? Well.. whilst my activities do require a dependency factory to function, the default factory is not injected but rather hard coded. The setter is only used for cases where we want to override the default.. for example testing.
Whilst this does mean modifying your code to facilitate testing (which is admittedly less than ideal), I am happy (personally) with the tradeoff to make my activities testable.
If you really want to, you could subclass your activities within your test package for the purposes of testing.
So where are we?
At this point I have outlined how you can make dependencies available to both activities and fragments in a way which allows for them to be swapped out (mocked) for testing.
It is not a perfect solution but it is simple, and it is easy to implement.
If you have any questions, I would be happy to answer them.