The Background:
I'm using Dagger2 for dependency injection in my Android app and I want to inject an object as a singleton into my BaseActivity to be available throughout the whole app, but the value of the Object can only be set after an authentication process and its value depends on the outcome of the authentication.
The Build:
This is what my current setup looks like:
public interface Tool {
...
}
public class ToolOne implements Tool {
...
}
public class ToolTwo implements Tool {
...
}
A general interface and two different implementations of it.
public class ToolConfig {
private Tool currentTool;
public ToolConfig(Tool tool) {
this.currentTool = tool;
}
...
}
A class that functions as configuration and is used throughout the whole app.
#Provides
#PerApplication
ToolConfig provideToolConfig(Tool tool) {
return new ToolConfig(tool);
}
The way the configuration is defined in my ToolModule for Dagger. This will be injected into the BaseActivity to be available for all classes.
The task:
How can I set the value of currentTool for the configuration? Depending on the authentication process, either ToolOne or ToolTwo should be set as value of currentTool. It should be possible to change it if the authentication process is done again. This will rarely be the case after it was set once, but it could happen and I want to be sure to always use the same instance of the object.
Is there a recommended way how I could do this with Dagger2?
You can just define a #Provides method that redirects between them:
#Provides Tool provideTool(
AuthenticationController authController,
Provider<ToolOne> toolOneProvider,
Provider<ToolTwo> toolTwoProvider) {
if (authController.useToolTwo()) {
return toolTwoProvider.get();
}
return toolOneProvider.get();
}
However, in a case like that, you'll need to be very careful to only inject a Provider<Tool> (which will check AuthController every time you call get) and not Tool (which will check AuthController once when the Tool-injected class is created but will not auto-update). For this reason it may make sense to create a custom one-method ToolProvider, where you inject the three deps as needed in the #Provides method and return the current tool as a getCurrentTool call (etc).
Related
I have a BIG Android app that needs to run different code for depending on the OS version, the manufacturer, and many other things. This app however needs to be a single APK. It needs to be smart enough at runtime to determine which code to use. Until now we have been using Guice but performance issues are causing us to consider migrating to Dagger. However, I've been unable to determine if we can achieve the same use case.
The main goal is for us have some code that runs at startup to provide a list of compatible Modules. Then pass that this list to Dagger to wire everything up.
Here is some pseudocode of the current implementation in Guice we want to migrate
import com.google.inject.AbstractModule;
#Feature("Wifi")
public class WifiDefaultModule extends AbstractModule {
#Override
protected void configure() {
bind(WifiManager.class).to(WifiDefaultManager.class);
bind(WifiProcessor.class).to(WifiDefaultProcessor.class);
}
}
#Feature("Wifi")
#CompatibleWithMinOS(OS > 4.4)
class Wifi44Module extends WifiDefaultModule {
#Override
protected void configure() {
bind(WifiManager.class).to(Wifi44Manager.class);
bindProcessor();
}
#Override
protected void bindProcessor() {
(WifiProcessor.class).to(Wifi44Processor.class);
}
}
#Feature("Wifi")
#CompatibleWithMinOS(OS > 4.4)
#CompatibleWithManufacturer("samsung")
class WifiSamsung44Module extends Wifi44Module {
#Override
protected void bindProcessor() {
bind(WifiProcessor.class).to(SamsungWifiProcessor.class);
}
#Feature("NFC")
public class NfcDefaultModule extends AbstractModule {
#Override
protected void configure() {
bind(NfcManager.class).to(NfcDefaultManager.class);
}
}
#Feature("NFC")
#CompatibleWithMinOS(OS > 6.0)
class Nfc60Module extends NfcDefaultModule {
#Override
protected void configure() {
bind(NfcManager.class).to(Nfc60Manager.class);
}
}
public interface WifiManager {
//bunch of methods to implement
}
public interface WifiProcessor {
//bunch of methods to implement
}
public interface NfcManager {
//bunch of methods to implement
}
public class SuperModule extends AbstractModule {
private final List<Module> chosenModules = new ArrayList<Module>();
public void addModules(List<Module> features) {
chosenModules.addAll(features);
}
#Override
protected void configure() {
for (Module feature: chosenModules) {
feature.configure(binder())
}
}
}
so at startup the app does this:
SuperModule superModule = new SuperModule();
superModule.addModules(crazyBusinessLogic());
Injector injector = Guice.createInjector(Stage.PRODUCTION, superModule);
where crazyBusinessLogic() reads the annotations of all the modules and determines a single one to use for each feature based on device properties. For example:
a Samsung device with OS = 5.0 will have crazyBusinessLogic() return the list { new WifiSamsung44Module(), new NfcDefaultModule() }
a Samsung device with OS = 7.0 will have crazyBusinessLogic() return the list { new WifiSamsung44Module(), new Nfc60Module() }
a Nexus device with OS = 7.0 will have crazyBusinessLogic() return the list { new Wifi44Module(), new Nfc60Module() }
and so on....
Is there any way to do the same with Dagger? Dagger seems to require you to pass the list of modules in the Component annotation.
I read a blog that seems to work on a small demo, but it seems clunky and the extra if statement and extra interfaces for components might cause my code to balloon.
https://blog.davidmedenjak.com/android/2017/04/28/dagger-providing-different-implementations.html
Is there any way to just use a list of modules returned from a function like we are doing in Guice? If not, what would be the closest way that would minimize rewriting the annotations and the crazyBusinessLogic() method?
Dagger generates code at compile-time, so you are not going to have as much module flexibility as you did in Guice; instead of Guice being able to reflectively discover #Provides methods and run a reflective configure() method, Dagger is going to need to know how to create every implementation it may need at runtime, and it's going to need to know that at compile time. Consequently, there's no way to pass an arbitrary array of Modules and have Dagger correctly wire your graph; it defeats the compile-time checking and performance that Dagger was written to provide.
That said, you seem to be okay with a single APK containing all possible implementations, so the only matter is selecting between them at runtime. This is very possible in Dagger, and will probably fall into one of four solutions: David's component-dependencies-based solution, Module subclasses, stateful module instances, or #BindsInstance-based redirection.
Component dependencies
As in David's blog you linked, you can define an interface with a set of bindings that you need to pass in, and then supply those bindings through an implementation of that interface passed into the builder. Though the structure of the interface makes this well-designed to pass Dagger #Component implementations into other Dagger #Component implementations, the interface may be implemented by anything.
However, I'm not sure this solution suits you well: This structure is also best for inheriting freestanding implementations, rather than in your case where your various WifiManager implementations all have dependencies that your graph needs to satisfy. You might be drawn to this type of solution if you need to support a "plugin" architecture, or if your Dagger graph is so huge that a single graph shouldn't contain all of the classes in your app, but unless you have those constraints you may find this solution verbose and restrictive.
Module subclasses
Dagger allows for non-final modules, and allows for the passing of instances into modules, so you can simulate the approach you have by passing subclasses of your modules into the Builder of your Component. Because the ability to substitute/override implementations is frequently associated with testing, this is described on the Dagger 2 Testing page under the heading "Option 1: Override bindings by subclassing modules (don’t do this!)"—it clearly describes the caveats of this approach, notably that the virtual method call will be slower than a static #Provides method, and that any overridden #Provides methods will necessarily need to take all parameters that any implementation uses.
// Your base Module
#Module public class WifiModule {
#Provides WifiManager provideWifiManager(Dep1 dep1, Dep2 dep2) {
/* abstract would be better, but abstract methods usually power
* #Binds, #BindsOptionalOf, and other declarative methods, so
* Dagger doesn't allow abstract #Provides methods. */
throw new UnsupportedOperationException();
}
}
// Your Samsung Wifi module
#Module public class SamsungWifiModule {
#Override WifiManager provideWifiManager(Dep1 dep1, Dep2 dep2) {
return new SamsungWifiManager(dep1); // Dep2 unused
}
}
// Your Huawei Wifi module
#Module public class HuaweiWifiModule {
#Override WifiManager provideWifiManager(Dep1 dep1, Dep2 dep2) {
return new HuaweiWifiManager(dep1, dep2);
}
}
// To create your Component
YourAppComponent component = YourAppComponent.builder()
.baseWifiModule(new SamsungWifiModule()) // or name it anything
// via #Component.Builder
.build();
This works, as you can supply a single Module instance and treat it as an abstract factory pattern, but by calling new unnecessarily, you're not using Dagger to its full potential. Furthermore, the need to maintain a full list of all possible dependencies may make this more trouble than it's worth, especially given that you want all dependencies to ship in the same APK. (This might be a lighter-weight alternative if you need certain kinds of plugin architecture, or you want to avoid shipping an implementation entirely based on compile-time flags or conditions.)
Module instances
The ability to supply a possibly-virtual Module was really meant more for passing module instances with constructor arguments, which you could then use for choosing between implementations.
// Your NFC module
#Module public class NfcModule {
private final boolean useNfc60;
public NfcModule(boolean useNfc60) { this.useNfc60 = useNfc60; }
#Override NfcManager provideNfcManager() {
if (useNfc60) {
return new Nfc60Manager();
}
return new NfcDefaultManager();
}
}
// To create your Component
YourAppComponent component = YourAppComponent.builder()
.nfcModule(new NfcModule(true)) // again, customize with #Component.Builder
.build();
Again, this doesn't use Dagger to its fullest potential; you can do that by manually delegating to the right Provider you want.
// Your NFC module
#Module public class NfcModule {
private final boolean useNfc60;
public NfcModule(boolean useNfc60) { this.useNfc60 = useNfc60; }
#Override NfcManager provideNfcManager(
Provider<Nfc60Manager> nfc60Provider,
Provider<NfcDefaultManager> nfcDefaultProvider) {
if (useNfc60) {
return nfc60Provider.get();
}
return nfcDefaultProvider.get();
}
}
Better! Now you don't create any instances unless you need them, and Nfc60Manager and NfcDefaultManager can take arbitrary parameters that Dagger supplies. This leads to the fourth solution:
Inject the configuration
// Your NFC module
#Module public abstract class NfcModule {
#Provides static NfcManager provideNfcManager(
YourConfiguration yourConfiguration,
Provider<Nfc60Manager> nfc60Provider,
Provider<NfcDefaultManager> nfcDefaultProvider) {
if (yourConfiguration.useNfc60()) {
return nfc60Provider.get();
}
return nfcDefaultProvider.get();
}
}
// To create your Component
YourAppComponent component = YourAppComponent.builder()
// Use #Component.Builder and #BindsInstance to make this easy
.yourConfiguration(getConfigFromBusinessLogic())
.build();
This way you can encapsulate your business logic in your own configuration object, let Dagger provide your required methods, and go back to abstract modules with static #Provides for the best performance. Furthermore, you don't need to use Dagger #Module instances for your API, which hides implementation details and makes it easier to move away from Dagger later if your needs change. For your case, I recommend this solution; it'll take some restructuring, but I think you'll wind up with a clearer structure.
Side note about Guice Module#configure(Binder)
It's not idiomatic to call feature.configure(binder()); please use install(feature); instead. This allows Guice to better describe where errors occur in your code, discover #Provides methods in your Modules, and to de-duplicate your module instances in case a module is installed more than once.
Is there any way to just use a list of modules returned from a
function like we are doing in Guice? If not, what would be the closest
way that would minimize rewriting the annotations and the
crazyBusinessLogic() method?
Not sure this is the answer you're looking for, but just in case you do have other options and for other community members I will describe completely different approach.
I would say that the way you used Guice until now is an abuse of DI framework, and you will be much better off leveraging this opportunity to remove this abuse instead of implementing it in Dagger.
Let me explain.
The main goal of dependency injection architectural pattern is to have construction logic segregated from functional logic.
What you basically want to achieve is standard polymorphism - provide different implementations based on a set of parameters.
If you use Modules and Components for that purpose, you will end up structuring your DI code according to business rules governing the need for these polymorphic implementations.
Not only will this approach requires much more boilerplate, but it also prevents emergence of cohesive Modules that have meaningful structure and provide insights into application's design and architecture.
In addition, I doubt you will be able to unit test these business rules "encoded" inside dependency injection logic.
There are two approaches which are much better IMHO.
First approach is still not very clean, but, at least, it doesn't compromise the large scale structure of dependency injection code:
#Provides
WifiManager wifiManager(DeviceInfoProvider deviceInfoProvider) {
if (deviceInfoProvider.isPostKitKat() ) {
if (deviceInfoProvider.isSamsung()) {
return new WifiMinagerSamsungPostKitKat();
} else {
return new WifiMinagerPostKitKat();
}
} else {
return new WifiMinagerPreKitKat();
}
}
The logic that chooses between implementation still resides in DI code, but, at least, it did not make it into the large scale structure of that part.
But the best solution in this case is to make a proper object oriented design, instead of abusing DI framework.
I'm pretty sure that the source code of all these classes is very similar. They might even inherit from one another while overriding just one single method.
In this case, the right approach is not duplication/inheritance, but composition using Strategy design pattern.
You would extract the "strategy" part into a standalone hierarchy of classes, and define a factory class that constructs them based on system's parameters. Then, you could do it like this:
#Provides
WiFiStrategyFactory wiFiStrategyFactory(DeviceInfoProvider deviceInfoProvider) {
return new WiFiStrategyFactory(deviceInfoProvider);
}
#Provides
WifiManager wifiManager(WiFiStrategyFactory wiFiStrategyFactory) {
return new WifiMinager(WiFiStrategyFactory.newWiFiStrategy());
}
Now construction logic is simple and clear. The differentiation between strategies encapsulated inside WiFiStrategyFactory and can be unit tested.
The best part of this proper approach is that when a new strategy will need to be implemented (because we all know that Android fragmentation is unpredictable), you won't need to implement new Modules and Components, or make any changes to DI structure. This new requirement will be handled by just providing yet another implementation of the strategy and adding the instantiation logic to the factory.
All that while being kept safe with unit tests.
I am a newbie in using Dagger and DI. I am trying to use AndroidInjection resolver for injecting dependencies into fragments of its activity.
Generally, I understood that, in the case of using Dagger.android, I have to create MyAppComponent and install AndroidInjectionModule in order to use AndroidInjection.inject(Activity/Fragment/etc..). In this way, I have provided Subcomponents' interfaces with Builders to make Dagger able to generate appropriate injectors.
But what if I have Subcomponent, i.e. DeviceFragmentSubcomponent that has a dependency on the module with parameterized constructor?
#Subcomponent(modules = {DeviceModule.class})
public interface DevicePageFragmentSubcomponent extends AndroidInjector<DevicePageFragment>{
#Subcomponent.Builder
public abstract class Builder extends AndroidInjector.Builder<DevicePageFragment>{
public abstract Builder setDeviceModule(DeviceModule deviceModule);
}
}
#Module
public class DeviceModule {
private Device mDevice;
public DeviceModule(Device device) {
mDevice = device;
}
#Provides
public Device provideDevice(){
return mDevice;
}
}
What should be done to set DeviceModule instance within DeviceActivity for using AndroidInjection.inject(this) in its fragments?
Is it possible to add required modules not at the moment of creation application's dependency tree, but on the arbitrary event?
The Android Injection part of Dagger can (currently) only be used along with AndroidInjection.inject(this), where it will inject the given Android Framework type with a predefined module.
As such, there is no way to pass in a parameter or module.
Your first option would be not to use the Android Injection part of Dagger. Just create your component as you see fit and inject your object.
The second option would be to not use a parameter / module. In theory, if your Activity can create a DeviceModule, so can Dagger, given that it has access to the Activity—and by using the Android Injection parts, the component injecting your type has access to it.
You did not specify what dependency Device has or why you need to pass it to the DeviceModule from your fragment.
Let's say your Device depends on DevicePageFragment.
class Device {
#Inject Device(DevicePageFragment fragment) { /**/ } // inject the fragment directly
}
You can access the fragment and do what you would do. If that's not your case, let's say you need to read the arguments Bundle. You could modify your Module to not take a device, but rather to create it iself, and getting rid of the constructor argument as well.
#Module
public class DeviceModule {
// no constructor, we create the object below
// again we take the fragment as dependency, so we have full access
#Provides
public Device provideDevice(DevicePageFragment fragment){
// read your configuration from the fragment, w/e
long id = fragment.getArguments().getLong("id")
// create the device in the module
return new Device(id);
}
}
In the end it really depends on your usecase.
What I tried to show is that you have access to the object that you are trying to inject. This means that whatever you can do within this object, you can do within Dagger. There is no need for parameterized modules, since you can extract those parameters from the target, as seen above.
For 'regular' Java project overriding the dependencies in the unit tests with mock/fake ones is easy. You have to simply build your Dagger component and give it to the 'main' class that drives you application.
For Android things are not that simple and I've searched for a long time for decent example but I was unable to find so I had to created my own implementation and I will really appreciate feedback is this a correct way to use Dagger 2 or there is a simpler/more elegant way to override the dependencies.
Here the explanation (project source can be found on github):
Given we have a simple app that uses Dagger 2 with single dagger component with single module we want to create android unit tests that use JUnit4, Mockito and Espresso:
In the MyApp Application class the component/injector is initialized like this:
public class MyApp extends Application {
private MyDaggerComponent mInjector;
public void onCreate() {
super.onCreate();
initInjector();
}
protected void initInjector() {
mInjector = DaggerMyDaggerComponent.builder().httpModule(new HttpModule(new OkHttpClient())).build();
onInjectorInitialized(mInjector);
}
private void onInjectorInitialized(MyDaggerComponent inj) {
inj.inject(this);
}
public void externalInjectorInitialization(MyDaggerComponent injector) {
mInjector = injector;
onInjectorInitialized(injector);
}
...
In the code above:
Normal application start goes trough onCreate() which calls initInjector() which creates the injector and then calls onInjectorInitialized().
The externalInjectorInitialization() method is ment to be called by the unit tests in order to set the injector from external source, i.e. a unit test.
So far, so good.
Let's see how the things on the unit tests side looks:
We need to create MyTestApp calls which extends MyApp class and overrides initInjector with empty method in order to avoid double injector creation (because we will create a new one in our unit test):
public class MyTestApp extends MyApp {
#Override
protected void initInjector() {
// empty
}
}
Then we have to somehow replace the original MyApp with MyTestApp. This is done via custom test runner:
public class MyTestRunner extends AndroidJUnitRunner {
#Override
public Application newApplication(ClassLoader cl,
String className,
Context context) throws InstantiationException,
IllegalAccessException,
ClassNotFoundException {
return super.newApplication(cl, MyTestApp.class.getName(), context);
}
}
... where in newApplication() we effectively replace the original app class with the test one.
Then we have to tell the testing framework that we have and want to use our custom test runner so in the build.gradle we add:
defaultConfig {
...
testInstrumentationRunner 'com.bolyartech.d2overrides.utils.MyTestRunner'
...
}
When a unit test is run our original MyApp is replaced with MyTestApp. Now we have to create and provide our component/injector with mocks/fakes to the app with externalInjectorInitialization(). For that purpose we extends the normal ActivityTestRule:
#Rule
public ActivityTestRule<Act_Main> mActivityRule = new ActivityTestRule<Act_Main>(
Act_Main.class) {
#Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
OkHttpClient mockHttp = create mock OkHttpClient
MyDaggerComponent injector = DaggerMyDaggerComponent.
builder().httpModule(new HttpModule(mockHttp)).build();
MyApp app = (MyApp) InstrumentationRegistry.getInstrumentation().
getTargetContext().getApplicationContext();
app.externalInjectorInitialization(injector);
}
};
and then we do our test the usual way:
#Test
public void testHttpRequest() throws IOException {
onView(withId(R.id.btn_execute)).perform(click());
onView(withId(R.id.tv_result))
.check(matches(withText(EXPECTED_RESPONSE_BODY)));
}
Above method for (module) overrides works but it requires creating one test class per each test in order to be able to provide separate rule/(mocks setup) per each test. I suspect/guess/hope that there is a easier and more elegant way. Is there?
This method is largely based on the answer of #tomrozb for this question. I just added the logic to avoid double injector creation.
1. Inject over dependencies
Two things to note:
Components can provide themselves
If you can inject it once, you can inject it again (and override the old dependencies)
What I do is just inject from my test case over the old dependencies. Since your code is clean and everything is scoped correctly nothing should go wrong—right?
The following will only work if you don't rely on Global State since changing the app component at runtime will not work if you keep references to the old one at some place. As soon as you create your next Activity it will fetch the new app component and your test dependencies will be provided.
This method depends on correct handling of scopes. Finishing and restarting an activity should recreate its dependencies. You therefore can switch app components when there is no activity running or before starting a new one.
In your testcase just create your component as you need it
// in #Test or #Before, just inject 'over' the old state
App app = (App) InstrumentationRegistry.getTargetContext().getApplicationContext();
AppComponent component = DaggerAppComponent.builder()
.appModule(new AppModule(app))
.build();
component.inject(app);
If you have an application like the following...
public class App extends Application {
#Inject
AppComponent mComponent;
#Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.builder().appModule(new AppModule(this)).build().inject(this);
}
}
...it will inject itself and any other dependencies you have defined in your Application. Any subsequent call will then get the new dependencies.
2. Use a different configuration & Application
You can chose the configuration to be used with your instrumentation test:
android {
...
testBuildType "staging"
}
Using gradle resource merging this leaves you with the option to use multiple different versions of your App for different build types.
Move your Application class from the main source folder to the debug and release folders. Gradle will compile the right source set depending on the configuration. You then can modify your debug and release version of your app to your needs.
If you do not want to have different Application classes for debug and release, you could make another buildType, used just for your instrumentation tests. The same principle applies: Duplicate the Application class to every source set folder, or you will receive compile errors. Since you would then need to have the same class in the debug and rlease directory, you can make another directory to contain your class used for both debug and release. Then add the directory used to your debug and release source sets.
There is a simpler way to do this, even the Dagger 2 docs mention it but they don't make it very obvious. Here's a snippet from the documentation.
#Provides static Pump providePump(Thermosiphon pump) {
return pump;
}
The Thermosiphon implements Pump and wherever a Pump is requested Dagger injects a Thermosiphon.
Coming back to your example. You can create a Module with a static boolean data member which allows you to dynamically switch between your real and mock test objects, like so.
#Module
public class HttpModule {
private static boolean isMockingHttp;
public HttpModule() {}
public static boolean mockHttp(boolean isMockingHttp) {
HttpModule.isMockingHttp = isMockingHttp;
}
#Provides
HttpClient providesHttpClient(OkHttpClient impl, MockHttpClient mockImpl) {
return HttpModule.isMockingHttp ? mockImpl : impl;
}
}
HttpClient can be the super class which is extended or an interface which is implemented by OkHttpClient and MockHttpClient. Dagger will automatically construct the required class and inject it's internal dependencies just like Thermosiphon.
To mock your HttpClient, just call HttpModule.mockHttp(true) before your dependencies are injected in your application code.
The benefits to this approach are:
No need to create separate test components since the mocks are injected at a module level.
Application code remains pristine.
I'm currently developing an Android MVP Application, and I'm trying to separate my dependencies in different Dagger2 Modules.
The problem I'm having is about changing a module in Unit Test Time. The scenario is the following:
LoginComponent, which uses two modules: LoginModule and HTTPModule
LoginModule in one of its methods requires an OkHttp instance, which is provided by HTTPModule.
The code is the following:
#Singleton
#Component(modules = {LoginModule.class, HTTPModule.class})
public interface LoginComponent {
}
#Module(includes = {HTTPModule.class})
public class LoginModule {
#Provides
#Singleton
public MyThing provideMyThing(OkHttpClient client) {
// Do things with it
}
}
#Module
public class HTTPModule {
#Provides
#Singleton
public OkHttpClient provideOkHttpClient(){
// Return the OkHttpClient
}
}
The thing is, at test time I would need to change the OkHttpClient that is returned (by making it accept all the certificates, as when I run it on the JVM it does not accept the LetsEncrypt certificate).
Also I would need that because I need to declare that MyTest.class can be injected with module, and as MyTest.class is under the app/src/test/ folder, it's not visible for the classes that are placed under app/src/main/. What I've done until now is to copy and paste the Component and the modules to the /test/ folder, and make the injected class declaration there. But I know there must be a proper way to achieve what I'm looking for.
Another thing I've tried is annotating the methods with custom Scopes (creating a #TestScope annotation). However this leads me to the same problem that I had commented before: I cannot make the MyTest.class visible to the component, because it's placed under the /test/ folder.
I've already checked other similar questions, such as this one and this another one, but this last one is for running tests with Robolectric, and by now I'm able to unit test most of my code with JUnit4 only (Android Studio 2-Beta 8).
If anyone could point me to the right direction, I would be more than grateful.
Thanks in advance!
You're using Dependency injection in a way that still keeps your code tightly coupled. Generally speaking you want your dependencies to be interfaces instead of actual classes. This keeps your code nice and loose, easy to read, modify and maintain.
Hide your network operations behind an interface to allow you to modify the network implementation whenever you need to. This could be done for testing - in your case, but it will also allow you to switch out the network library if you'll want to or need to in the future without changing any other code.
Try something like this:
#Module
public class HTTPModule {
#Provides
#Singleton
public NetworkProvider provideNetworkProvider(){
// Return the Network provider
}
}
The network abstraction layer:
public interface NetworkProvider {
// Methods to send requests and receive async responses
}
The OkHttp implementation:
public class OkHttpNetworkProvider implements NetworkProvider {
// Implement NetworkProvider. This is the only class that
// knows about OkHttp and its components
}
Now you can create a mock version of NetworkProvider and use it for testing, whether via a test module or directly.
I have a library project/module that is used by both Android apps and regular java apps.
In Dagger 1 this project/module has property complete = false. Within there is an #Inject field that is not satisfied by any class implementation or #Provides method. The idea is to force the "top" module(s) which has complete = true to provide system specific implementation
Just for the sake of example: In the library project I have ActLogin activity that have field #Inject #Named("app version") mAppVersion. The value of this field is used when logging in into a server. ActLogin is used by several apps that use this library. Each app's module has complete = true and provides value with #Provides #Named("app version") provideAppVersion()
Documentation for migration of Dagger 2 (http://google.github.io/dagger/dagger-1-migration.html) states:
Dagger 2 modules are all declared as complete = false and library = true
and in the same time the "main" documentation page (http://google.github.io/dagger/) states:
The Dagger annotation processor is strict and will cause a compiler error if any bindings are invalid or incomplete.
The latter is obviously the correct one because when trying to build with unsatisfied inject error is produced (error: java.lang.String cannot be provided without an #Provides- or #Produces-annotated method).
The question is: is it possible to migrate this approach (deferring providing inject) to Dagger 2 and how?
P.S. Initially I thought as a dirty workaround to provide some dummy values in the library's #Module but then again - you cannot have module overrides in Dagger 2 (which is kind of WTF(!!!). Module overrides were the most useful feature for me when creating unit tests). Probably I am missing something very basic and I hope that someone can point it out :-).
It turns out that there is dedicated construct for this but it takes some time to find it out.
If you need to have a component that have a module which contains unsatisfied inject(s) - make it #Subcomponent. As documentation clearly states:
That relationship allows the subcomponent implementation to inherit the entire binding graph from its parent when it is declared. For that reason, a subcomponent isn't evaluated for completeness until it is associated with a parent
So in my case, my library project needs to be a dagger subcomponent. When I use it in my app project, my app dagger component have to include the lib subcomponent.
In code:
The library subcomponent:
#Subcomponent(modules = Mod1.class)
public interface MyLibraryComponent {
void inject(Mod1Interface1 in);
}
The app component:
#Component(modules = Mod2.class)
#Singleton
public interface MyAppComponent {
void inject(MainActivity act);
MyLibraryComponent newMyLibraryComponent();
}
Please note the MyLibraryComponent newMyLibraryComponent(); - that is how you tell dagger that your component contains that subcomponent.
Graph instantiation:
MyAppComponent comp = DaggerMyAppComponent.builder().build();
Please note that contrary to using component composition with dependencies (#Component's property) in this case you don't have to "manually" construct your subcomponent. The component will "automatically" take care for that in case the subcomponent's modules don't need special configuration (i.e. constructor parameters). In case some subcomponent's module requires configuration you do it trough the component instantiation like this:
MyAppComponent comp = DaggerMyAppComponent.builder().
mod2(new Mod2SpecialConfiguration()).
build();
For android there is a special twist if your library project contains activities because each activity have to be separately injected "on demand" contrary to regular java application where you usually inject the whole application once at start up time.
For the sake of example let's say our library project contains login activity "ActLogin" that we use as common for several applications.
#Subcomponent(modules = Mod1.class)
public interface MyLibraryComponent {
void injectActLogin(ActLogin act);
void inject(Mod1Interface1 in);
}
The problem is that in Android we usually create our dependency graph in the Application object like this:
public class MyApplication extends Application {
private MyAppComponent mAppDependencyInjector;
#Override
public void onCreate() {
super.onCreate();
mAppDependencyInjector = DaggerMyAppComponent.builder().build();
}
public MyAppComponent getAppDependencyInjector() {
return mAppDependencyInjector;
}
}
and then in your activity you use it like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
((MyApplication) getApplication()).getAppDependencyInjector().inject(this);
// ...
}
but our ActLogin activity is part of the library project (and dagger component) that is not event aware of what application will it be used in so how are we going to inject it?
There is a nice solution but please note that I am not sure it is canonical (i.e. it is not mentioned in the documentation, it is not given as an example by the "authorities" (afaik))
Project's source can be found at github.
First you will have to extend the library dagger component in you app component:
public interface MyAppComponent extends MyLibraryComponent {
That way your app component will contain all the inject methods from the subcomponent so you will be able to inject it's activities too. After all, top component is in fact the whole object graph (more precisely the Dagger generated DaggerMyAppComponent represent the whole graph) so it is able to inject everything defined in itself + in all subcomponents.
Now we have to assure that the library project is able to access it. We create a helper class:
public class MyLibDependencyInjectionHelper {
public static MyLibraryComponent getMyLibraryComponent(Application app) {
if (app instanceof MyLibraryComponentProvider) {
return ((MyLibraryComponentProvider) app).getMyLibraryComponent();
} else {
throw new IllegalStateException("The Application is not implementing MyLibDependencyInjectionHelper.MyLibraryComponentProvider");
}
}
public interface MyLibraryComponentProvider {
MyLibraryComponent getMyLibraryComponent();
}
}
then we have to implement MyLibraryComponentProvider in our Application class:
public class MyApplication extends Application implements
MyLibDependencyInjectionHelper.MyLibraryComponentProvider {
// ...
#Override
public MyLibraryComponent getMyLibraryComponent() {
return (MyLibraryComponent) mAppDependencyInjector;
}
}
and in ActLogin we inject:
public class ActLogin extends Activity {
#Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
// ...
MyLibDependencyInjectionHelper.getMyLibraryComponent(getApplication()).
injectActLogin(this);
// ...
}
}
There is a problem with this solution: If you forget to implement the MyLibraryComponentProvider in your application you will not get an error at compile time but at runtime when you start ActLogin activity. Luckily that can be easily avoided with simple unit test.