I'm trying to create a simple app with retrofit 2, dagger 2 and MVP, but I struggle with dependencies, actualy, this is the error I get after i try to rebuild the project Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
java.lang.StackOverflowError
and also in App class where I provide AppComponent: can not resolve symbol 'DaggerAppComponent'
I'll try to show you what my project looks like so someone can see the problem, First one is my AppModule which includes PresentationModule.class
#Module(includes = PresentationModule.class)
public class AppModule {
private App app;
public AppModule(App app) {
this.app = app;
}
#Provides
#Singleton
public App provideApp() {
return app;
}
}
Presentation Module looks like this:
#Module(includes = InteractorModule.class)
public class PresentationModule {
#Provides
JokePresenter providePresenter(JokeInteractor jokeInteractor) {
return new JokePresenterImpl(jokeInteractor);
}
}
And InteractorModule:
#Module(includes = {NetworkModule.class, PresentationModule.class})
public class InteractorModule {
#Provides
JokeInteractor provideJokeInteractor(RetrofitService service, JokePresenter presenter) {
return new JokeInteractorImpl(service, presenter);
}
}
This is JokePresenter that has a reference to view and interactor:
public class JokePresenterImpl implements JokePresenter {
private JokeView mJokeView;
private List<String> mData = new ArrayList<>();
private String mJoke;
private JokeInteractor jokeInteractor;
public JokePresenterImpl(JokeInteractor jokeInteractor) {
this.jokeInteractor = jokeInteractor;
}
#Override
public void setView(JokeView view) {
this.mJokeView = view;
}
#Override
public void getRandomJoke() {
mJokeView.showProgress();
jokeInteractor.getRandomJoke();
}
}
And JokeInteractor that has a RetrofitService and JokePresenter references:
public class JokeInteractorImpl implements JokeInteractor {
private RetrofitService retrofitService;
private JokePresenter presenter;
public JokeInteractorImpl(RetrofitService service, JokePresenter presenter) {
this.retrofitService = service;
this.presenter = presenter;
}
#Override
public void getRandomJoke() {
retrofitService.getRandomJoke()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<RandomJokeResponse>() {
#Override
public void onCompleted() {
presenter.onRandomJokeCompleted();
}
#Override
public void onError(Throwable e) {
presenter.onError(e.getMessage());
}
#Override
public void onNext(RandomJokeResponse randomJokeResponse) {
presenter.onNextRandomJoke(randomJokeResponse);
}
});
}
Gradle dependencies:
apt 'com.google.dagger:dagger-compiler:2.7'
compile 'com.google.dagger:dagger:2.7'
provided 'javax.annotation:jsr250-api:1.0'
//retrofit
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.okhttp:okhttp:2.5.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
//rx java
compile 'io.reactivex:rxjava:1.1.6'
compile 'io.reactivex:rxandroid:1.2.1'
//dagger2
compile 'com.google.dagger:dagger:2.0'
provided 'org.glassfish:javax.annotation:10.0-b28'
Can someone see the problem here?
Look at the implementation of:
#Module(includes = InteractorModule.class)
public class PresentationModule
and
#Module(includes = {NetworkModule.class, PresentationModule.class})
public class InteractorModule
You have a cyclic dependency. You need to rethink your design. I propose to decouple Interactor from Presenter.
A simple solution:
Change getRandomJoke() implementation to return Observable and subscribe to it inside Presenter. And remove presenter reference from Interactor.
Interactor:
public class JokeInteractorImpl implements JokeInteractor {
private RetrofitService retrofitService;
public JokeInteractorImpl(RetrofitService service) {
this.retrofitService = service;
}
#Override
public Observable getRandomJoke() {
return retrofitService.getRandomJoke()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
Presenter:
#Override
public void getRandomJoke() {
mJokeView.showProgress();
jokeInteractor.getRandomJoke()
.subscribe(new Observer<RandomJokeResponse>() {
#Override
public void onCompleted() {
onRandomJokeCompleted();
}
#Override
public void onError(Throwable e) {
onError(e.getMessage());
}
#Override
public void onNext(RandomJokeResponse randomJokeResponse) {
onNextRandomJoke(randomJokeResponse);
}
});
}
Make sure that dependencies added in your gradle
implementation 'com.google.dagger:dagger-android:2.15'
implementation 'com.google.dagger:dagger-android-support:2.13'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.16'
Make sure adding AndroidSupportInjectionModule::class inside your modules.
Like this:
#Component(
modules = [
AndroidSupportInjectionModule::class,
....//Other module classes
]
)
This is what worked for me (Solution) :-
If you have any other build issues related to Dagger apart from DaggerAppComponent import issue, then make sure to fix them first, if not we will just end up wasting time.
If you fix the other Dagger issues, & then build your project.
The DaggerAppComponent will get generated, & then all you have to do is import it.
Brief info regarding the issue that I faced :-
The mistake that I did was that, I had another Dagger build issue, but I ignored it & spent my time trying to fix 'DaggerAppComponent' import problem.
Wont be relevant to you but in my case, the DI module class annotated with #Module had a single abstract method, & hence the module class had to be made abstract as well. But later accidentally I added 2 other non-abstract methods within this same abstract class. So when I fixed this & re-build the project. DaggerAppComponent was available for import.
This is all what I have in my app level build.gradle file in relation to dagger :-
Within plugins block :
id 'kotlin-kapt'
Within dependencies block :
implementation "com.google.dagger:dagger:2.43.2"
kapt "com.google.dagger:dagger-compiler:2.43.2"
(Please note that "2.43.2" was the latest available version at the time of posting this comment)
My failed attempts (These answers didn't work for me) :-
Clean > Re-build > Invalidates caches & restart.
Switching to Project view, deleting build folder & re-building the project.
Trying out various dagger versions & extra dagger dependencies that I didnt require.
Related
I'm trying out the new CameraX, have followed the instructions mentioned here https://developer.android.com/jetpack/androidx/releases/camera#camera-camera2-1.0.0-alpha07. And I get the following error,
e: Supertypes of the following classes cannot be resolved. Please make sure you have the required dependencies in the classpath:
class com.sample.SampleApplication, unresolved supertypes: androidx.camera.core.CameraXConfig.Provider
Can someone help?
Edit: Another thing, I extend "MultiDexApplication" class.
your Application should look similar to this:
public class AppController extends Application implements CameraXConfig.Provider {
and the implemented method like this:
#NonNull
#Override
public CameraXConfig getCameraXConfig() {
return Camera2Config.defaultConfig(this);
}
works for me with the same setup you posted in the google group
ie
def camerax_version = "1.0.0-alpha07"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-view:1.0.0-alpha04"
in the Activity all I do is:
bind cameraView and bind it to the activity:
#BindView(R.id.cameraView)
CameraView cameraView;
....
if (checkCameraAndStoragePermissions()) {
initializeView();
} else {
askCameraAndStoragePermissions(new SingleObserver<Boolean>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Boolean response) {
if (response) {
initializeView();
}
}
#Override
public void onError(Throwable e) {
}
});
}
#SuppressLint("MissingPermission")
private void initializeView() {
...
cameraView.bindToLifecycle(this);
...
}
api "androidx.camera:camera-core:${camerax_version}" fixes the problem
I am using Dagger2 in my android application, I have two component in my application. First is global for entire application and second is specific to activity instance.
NetComponent.java
#Singleton
#Component(modules = {AppModule.class, NetModule.class})
public interface NetComponent {
void inject(AuthenticationActivity authenticationActivity);
void inject(PaymentActivity paymentActivity);
}
ValidationComponent.java
#Singleton
#Component(modules = {ValidatorModule.class})
public interface ValidationComponent {
void inject(Activity activity);
}
AppModule.java
#Module
public class AppModule {
private Application application;
public AppModule(Application application) {
this.application = application;
}
#Provides
#Singleton
Application providesApplication() {
return application;
}
}
NetModule.java
#Module
public class NetModule {
#Provides
#Singleton
SharedPreferences providesSharedPreferences(Application application) {
return PreferenceManager.getDefaultSharedPreferences(application);
}
#Provides
#Singleton
Cache provideOkHttpCache(Application application) {
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(application.getCacheDir(), cacheSize);
return cache;
}
#Provides
#Singleton
Gson provideGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
return gsonBuilder.create();
}
#Provides
#Singleton
OkHttpClient provideOkHttpClient(Cache cache) {
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.newBuilder()
//.addNetworkInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR)
.cache(cache)
.build();
return okHttpClient;
}
#Provides
#Singleton
#Named("authRetrofit")
Retrofit provideAuthRetrofit(Gson gson, OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(PAYMENT_SERVICE)
.client(okHttpClient)
.build();
return retrofit;
}
#Provides
#Singleton
#Named("paymentRetrofit")
Retrofit providePaymentRetrofit(Gson gson, OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(LOGIN_SERVICE)
.client(okHttpClient)
.build();
return retrofit;
}
}
ValidatorModule.java
#Module
public class ValidatorModule {
private final Activity activity;
public ValidatorModule(Activity activity) {
this.activity = activity;
}
#Provides
com.mobsandgeeks.saripaar.Validator providesValidator() {
return new com.mobsandgeeks.saripaar.Validator(activity);
}
}
AppApplication.java
public class AppApplication extends Application {
private NetComponent mNetComponent;
#Override
public void onCreate() {
super.onCreate();
mNetComponent = DaggerNetComponent.builder()
.appModule(new AppModule(this))
.build();
}
public NetComponent getmNetComponent() {
return mNetComponent;
}
}
AuthenticationActivity.java
public class AuthenticationActivity extends BaseActivity implements View.OnClickListener, Validator.ValidationListener {
#Inject
Validator validator;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
....
((AppApplication) getApplication()).getmNetComponent().inject(this);
ValidationComponent validationComponent = DaggerValidationComponent.builder()
.validatorModule(getValidatorModule())
.build();
validationComponent.inject(this);
...
// apply click and validation listeners
validator.setValidationListener(this);
}
}
protected ValidatorModule getValidatorModule() {
return new ValidatorModule(this);
}
It is generating ValidationComponent but not DaggerNetComponent
Above are my module, components of my application. When I am compiling and building a application then I am getting below errors -
Error
Information:Gradle tasks [clean, :app:generateDebugSources, :app:mockableAndroidJar, :app:prepareDebugUnitTestDependencies, :app:generateDebugAndroidTestSources, :app:compileDebugSources, :app:compileDebugUnitTestSources, :app:compileDebugAndroidTestSources]
C:\Users\xyz\AndroidStudioProjects\ICICIAppathon\app\src\main\java\com\icici\iciciappathon\AppApplication.java
Error:(24, 48) error: cannot find symbol class DaggerNetComponent
C:\Users\xyz\AndroidStudioProjects\ICICIAppathon\app\src\main\java\com\icici\iciciappathon\login\AuthenticationActivity.java
Error:(35, 48) error: cannot find symbol class DaggerValidationComponent
Error:(39, 43) error: package com.icici.iciciappathon.databinding does not exist
C:\Users\xyz\AndroidStudioProjects\ICICIAppathon\app\src\main\java\com\icici\iciciappathon\checkout\PaymentActivity.java
Error:(28, 43) error: package com.icici.iciciappathon.databinding does not exist
C:\Users\xyz\AndroidStudioProjects\ICICIAppathon\app\src\main\java\com\icici\iciciappathon\dashboard\DashboardActivity.java
Error:(32, 43) error: package com.icici.iciciappathon.databinding does not exist
C:\Users\xyz\AndroidStudioProjects\ICICIAppathon\app\src\main\java\com\icici\iciciappathon\shopping\ScanBarcodeActivity.java
Error:(41, 43) error: package com.icici.iciciappathon.databinding does not exist
C:\Users\xyz\AndroidStudioProjects\ICICIAppathon\app\src\main\java\com\icici\iciciappathon\ui\GetStartedActivity.java
Error:(27, 43) error: package com.icici.iciciappathon.databinding does not exist
C:\Users\xyz\AndroidStudioProjects\ICICIAppathon\app\src\main\java\com\icici\iciciappathon\dagger\component\NetComponent.java
Error:(34, 10) error: com.mobsandgeeks.saripaar.Validator cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method.
com.icici.iciciappathon.login.AuthenticationActivity.validator
[injected field of type: com.mobsandgeeks.saripaar.Validator validator]
Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
> Compilation failed; see the compiler error output for details.
Information:BUILD FAILED
Information:Total time: 11.373 secs
Information:9 errors
Information:0 warnings
Information:See complete output in console
The general statement here is that Dagger doesn't generate classes when it encounters an error, usually when you're doing something impossible in your binding graph. That's the case here.
C:\Users\xyz\AndroidStudioProjects\ICICIAppathon\app\src\main\java\com\icici\iciciappathon\dagger\component\NetComponent.java
Error:(34, 10) error: com.mobsandgeeks.saripaar.Validator cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method.
com.icici.iciciappathon.login.AuthenticationActivity.validator
[injected field of type: com.mobsandgeeks.saripaar.Validator validator]
I agree with azizbekian that the problem is that you are trying to inject the Validator from within AuthenticationActivity, and telling NetComponent to try to do so without instructing it how to do so (in ValidationModule).
In the code you've posted, there isn't a reason for NetComponent to inject AuthenticationActivity; there's nothing for it to provide. So you can delete these two lines and be done:
// In NetComponent
void inject(AuthenticationActivity authenticationActivity);
// In AuthenticationActivity
((AppApplication) getApplication()).getmNetComponent().inject(this);
However, it's also possible that you've simply not shown us the NetComponent dependencies you need in AuthenticationActivity. In that case, you're going to need to combine the object graphs, either through subcomponents or component dependencies. You'll still need to delete the two lines listed above, because under no circumstance does NetComponent have the dependencies it needs to inject AuthenticationActivity.
Subcomponents
To do so with subcomponents, simply make a builder on NetComponent through which you get your ValidationActivity. You're welcome to provide a scope, but you don't necessarily need one, and I think it might be confusing to add that into the mix. At that point, your ValidationComponent will have access to all the bindings in your NetComponent AND all of the bindings listed in ValidationComponent. This is effectively identical to azizbekian's answer, though notably this is through subcomponents, not component dependencies. I'll talk about component dependencies in a moment.
// After removing the inject(AuthenticationActivity) call, add this to NetComponent
ValidatorComponent validatorComponent(ValidatorModule validatorModule);
// And call the implementation from AuthenticationActivity
((AppApplication) getApplication()).getmNetComponent()
.validatorModule(new ValidatorModule(this))
.inject();
You'll also need to switch ValidationComponent from #Component to #Subcomponent, and remove the #Singleton instance (which makes sense because you're creating a new one for every AuthenticationActivity instance Android creates.
Effectively, this turns NetComponent into a factory for ValidationComponent instances. Dagger generates the code for both at once, so ValidationComponent can access anything it needs from NetComponent automatically. However, this way it is impossible to generate code for each one separately, or to get a ValidationComponent without a NetComponent instance (except through including it in another component).
Component dependencies
Component dependencies work a little bit differently. They're freestanding components, so they can be generated separately and independently. Instead, you can pass in an interface (often but not necessarily another Dagger component like NetComponent), and every single no-argument method becomes an automatic provider into the other graph. This means, for example, that you could list specific dependencies available to ValidationComponent from NetComponent, add a dependencies={NetComponent.class} to your ValidationComponent definition, and then change your DaggerValidationComponent builder to call .netComponent(((AppApplication getApplication()).getmNetComponent()) to hook the exact instances up. (Similarly, you could do so with any implementation of that interface, whether Dagger generates it or not; this can help with testing.) You'll also need to remove the use of #Singleton, because Dagger would be very confused to have two separate components both claim to exist in singleton scopeāone has to be created first, after all.
You haven't listed out the exact dependencies you're consuming out of NetComponent, so I don't have example changes I can show. However, for cases like this, I would recommend using subcomponents anyway; the bindings are more automatic, you likely won't benefit from the loose coupling you'd get from component dependencies, and you're closer to the design of Dagger's official Android support package so it's easier to migrate to that later.
In your onCreate() you have following piece of code:
((AppApplication) getApplication()).getmNetComponent().inject(this);
ValidationComponent validationComponent = DaggerValidationComponent.builder()
.validatorModule(getValidatorModule())
.build();
validationComponent.inject(this);
And you have declared that you are willing to #Inject a Validator instance.
Hence when getmNetComponent().inject(this) is reached, Dagger will try to inject that dependency, but it cannot find in that component, because it will be provided from ValidatorComponent (which will be executed later).
This is a situation, when you have to make use of subcomponents. Basically, your ValidatorComponent needs to see all dependencies provided by NetworkComponent and also add some of its own.
Following changes should be made:
NetComponent.java
#Singleton
#Component(modules = {AppModule.class, NetModule.class})
public interface NetComponent {
void inject(AuthenticationActivity authenticationActivity);
void inject(PaymentActivity paymentActivity);
ValidatorComponent validatorComponent(ValidatorModule validatorModule);
}
ValidationComponent.java
#YourCustomScope
#Subcomponent(modules = {ValidatorModule.class})
public interface ValidationComponent {
void inject(Activity activity);
}
AuthenticationActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((AppApplication) getApplication()).getmNetComponent()
.validatorModule(new ValidatorModule(this))
.inject();
}
YourCustomScope.java
#Scope
#Documented
#Retention(RUNTIME)
public #interface YourCustomScope {}
if you're using kotlin you have to add
apply plugin: 'kotlin-kapt'
in your build.gradle file
and alose use kapt instead of annotationProcessor in your dependencies
chage this :
implementation 'com.google.dagger:dagger:2.22'
annotationProcessor 'com.google.dagger:dagger-compiler:2.22'
implementation 'com.google.dagger:dagger-android:2.22'
implementation 'com.google.dagger:dagger-android-support:2.22'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.22'
to this:
kapt 'com.google.dagger:dagger-compiler:2.22'
implementation 'com.google.dagger:dagger-android:2.22'
implementation 'com.google.dagger:dagger-android-support:2.22'
kapt 'com.google.dagger:dagger-android-processor:2.22'
Context
Recenty I started investigating about dependency injection and Dagger 2. It looks a pretty good library but it seems a bit confusing to me. There are some situations in which I don't know exactly how to proceed.
What have I tried
I have created a simple Android app that creates a Client and its Dependency and do some (dummy) work. These are the classes:
Client.java
public class Client {
private Dependency dep;
#Inject
public Client(Dependency dep) {
this.dep = dep;
}
public void work() {
System.out.println("Client working");
dep.doWork();
}
}
Dependency.java
public class Dependency {
#Inject
public Dependency() {
}
public void doWork() {
System.out.println("Dependency working");
}
}
Following some tutorials I created a couple of Module classes:
DependencyModule.java
#Module
public class DependencyModule {
#Provides
Dependency provideDependency() {
return new Dependency();
}
}
ClientModule.java
#Module
public class ClientModule {
#Provides
Client provideClient(Dependency dep) {
return new Client(dep);
}
}
And also the Component interface:
#Component(modules = {ClientModule.class})
public interface ClientComponent {
Client provideClient();
}
This works fine. From my activity I can do the following and it works:
ClientComponent clientComp = DaggerClientComponent
.builder()
.clientModule(new ClientModule())
.build();
Client client = clientComp.provideClient();
client.work();
Problem
I understand how to inject dependencies in a client (at least I think so). But how I add parameters into the constructor of a client/dependency?
I mean, what if I would wanted to add some int parameters to my objects? Something as simple as this:
Client.java
public class Client {
int id;
Dependency dep;
#Inject
public Client(int id, Dependency dep) {
this.id = id;
this.dep = dep;
}
public void work() {
System.out.println("id: " + id + " Client working");
dep.doWork();
}
}
Dependency.java
public class Dependency {
private int id;
#Inject
public Dependency(int id) {
this.id = id;
}
public void doWork() {
System.out.println("id: " + id + " Dependency working");
}
}
NOTE:
The following code is what I've tried. So I'm not sure about its correctness.
So, as the objects has new parameters in their constructor the Modules have to change:
DependencyModule.class
public class DependencyModule {
#Provides
Dependency provideDependency() {
return new Dependency(id);
}
}
ClientModule.class
#Module
public class ClientModule {
#Provides
Client provideClient(int id, Dependency dep) {
return new Client(id, dep);
}
}
Question
How do I use that new Modules? I haven't found a way to pass the id to that methods. The only way I get it to work is by passing it in the Module constructor and removing it from the provide method. This way:
#Module
public class ClientModule {
private int id;
public ClientModule(int id) {
this.id = id;
}
#Provides
Client provideClient(Dependency dep) {
return new Client(id, dep);
}
}
Same approach in the DependencyModule.java.
This way, adding the DependencyModule.class in the ClientComponent interface I can do something like:
ClientComponent clientComp = DaggerClientComponent
.builder()
.clientModule(new ClientModule(clientId))
.dependencyModule(new DependencyModule(dependencyId))
.build();
Client client = clientComp.provideClient();
client.work();
Is that the correct way of doing that?
Is there a better way of getting the same effect?
Am I committing crimes against DI principle?
There are two basic ways to get Dagger to provide an instance of a class:
Add #Inject to a constructor, and put the class's dependencies in as constructor arguments.
Add a #Provides-annotated method to a #Module-annotated class, and install that module into your #Component.
You only need to use one method for each class. So in your first example, Client and Dependency are fine as is; you don't also need ClientModule and DependencyModule.
Once you add the int dependency, now you do need a module, because there's no class to #Inject. The module just needs to provide that int, so something like this would work:
#Module
public class ClientIdModule {
private final clientId;
public ClientIdModule(int clientId) {
this.clientId = clientId;
}
#Provides
static int clientId() {
return clientId;
}
}
Now if you install ClientIdModule into your component, you'll be able to get a Client which has the right ID, and its Dependency will as well.
I have a scoped dependency in my Activity and I want to test that activity with some mocks. I have read about different approach that suggest to replace Application component with a test component during the test, but what I want is to replace the Activity component.
For example, I want to test the Activity against mock presenter in my MVP setup.
I believe that replacing component by calling setComponent() on Activity will not work, because Activity dependencies already injected via field injection, so during the test, real object will be used.
How can I resolve this issue? What about Dagger1? Is it has the same issue?
Injecting the Component
First, you create a static class to act as a factory for your Activity. Mine looks a little like this:
public class ActivityComponentFactory {
private static ActivityComponentFactory sInstance;
public static ActivityComponentFactory getInstance() {
if(sInstance == null) sInstance = new ActivityComponentFactory();
return sInstance;
}
#VisibleForTesting
public static void setInstance(ActivityComponentFactory instance) {
sInstance = instance;
}
private ActivityComponentFactory() {
// Singleton
}
public ActivityComponent createActivityComponent() {
return DaggerActivityComponent.create();
}
}
Then just do ActivityComponentFactory.getInstance().createActivityComponent().inject(this); inside your Activities.
For testing, you can replace the factory in your method, before the Activity is created.
Providing mocks
As #EpicPandaForce's answer makes clear, doing this the officially-supported way currently involves a lot of boilerplate and copy/pasted code. The Dagger 2 team need to provide a simpler way of partially overriding Modules.
Until they do though, here's my unnoficial way: Just extend the module.
Let's say you want to replace your ListViewPresenter with a mock. Say you have a PresenterModule which looks like this:
#Module #ActivityScope
public class PresenterModule {
#ActivityScope
public ListViewPresenter provideListViewPresenter() {
return new ListViewPresenter();
}
#ActivityScope
public SomeOtherPresenter provideSomeOtherPresenter() {
return new SomeOtherPresenter();
}
}
You can just do this in your test setup:
ActivityComponentFactory.setInstance(new ActivityComponentFactory() {
#Override
public ActivityComponent createActivityComponent() {
return DaggerActivityComponent.builder()
.presenterModule(new PresenterModule() {
#Override
public ListViewPresenter provideListViewPresenter() {
// Note you don't have to use Mockito, it's just what I use
return Mockito.mock(ListViewPresenter.class);
}
})
.build();
}
});
...and it just works!
Note that you don't have to include the #Provides annotation on the #Override method. In fact, if you do then the Dagger 2 code generation will fail.
This works because the Modules are just simple factories - the generated Component classes take care of caching instances of scoped instances. The #Scope annotations are used by the code generator, but are irrelevant at runtime.
You cannot override modules in Dagger2 [EDIT: you can, just don't specify the #Provides annotation on the mock), which would obviously be the proper solution: just use the builder().somethingModule(new MockSomethingModule()).build() and be done with it!
If you thought mocking is not possible, then I would have seen two possible solutions to this problem. You can either use the modules to contain a pluggable "provider" that can have its implementation changed (I don't favor this because it's just too verbose!)
public interface SomethingProvider {
Something something(Context context);
}
#Module
public class SomethingModule {
private SomethingProvider somethingProvider;
public SomethingModule(SomethingProvider somethingProvider) {
this.somethingProvider = somethingProvider;
}
#Provides
#Singleton
public Something something(Context context) {
return somethingProvider.something(context);
}
}
public class ProdSomethingProvider implements SomethingProvider {
public Something something(Context context) {
return new SomethingImpl(context);
}
}
public class TestSomethingProvider implements SomethingProvider {
public Something something(Context context) {
return new MockSomethingImpl(context);
}
}
SomethingComponent somethingComponent = DaggerSomethingComponent.builder()
.somethingModule(new SomethingModule(new ProdSomethingProvider()))
.build();
Or you can bring the provided classes and injection targets out into their own "metacomponent" interface, which your ApplicationComponent and your TestApplicationComponent extend from.
public interface MetaApplicationComponent {
Something something();
void inject(MainActivity mainActivity);
}
#Component(modules={SomethingModule.class})
#Singleton
public interface ApplicationComponent extends MetaApplicationComponent {
}
#Component(modules={MockSomethingModule.class})
#Singleton
public interface MockApplicationComponent extends MetaApplicationComponent {
}
The third solution is to just extend the modules like in #vaughandroid 's answer. Refer to that, that is the proper way of doing it.
As for activity scoped components... same thing as I mentioned here, it's just a different scope, really.
I've found the following post that solves the problem:
http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html
You need first to allow to modify the component of the activity:
#Override public void onCreate() {
super.onCreate();
if (component == null) {
component = DaggerDemoApplication_ApplicationComponent
.builder()
.clockModule(new ClockModule())
.build();
}
}
public void setComponent(DemoComponent component) {
this.component = component;
}
public DemoComponent component() {
return component;
}
And modify it in the test case
#Before
public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
DemoApplication app
= (DemoApplication) instrumentation.getTargetContext().getApplicationContext();
TestComponent component = DaggerMainActivityTest_TestComponent.builder()
.mockClockModule(new MockClockModule())
.build();
app.setComponent(component);
component.inject(this);
}
I want to use Dagger injections in my robolectric tests but I have trouble with set it up. Where is error in my code sample. How can I make this work?
My main module
#Module(
includes = DatabaseModule.class,
injects = {
MainActivity.class,
}
)
public class MainModule {
private final MyApplication application;
public MainModule(MyApplication application) {
this.application = application;
}
My test module
#Module(
overrides = true,
includes = MainModule.class,
injects = {
TestMyApplication.class,
MyApplication.class
}
)
public class TestModule {
}
My production main class
public class MyApplication extends Application {
#Inject
public MyApplication() {
super();
}
#Override
public void onCreate() {
super.onCreate();
graph = ObjectGraph.create(getModules().toArray());
inject(this);
}
...
}
My robolectric test application class
public class TestMyApplication extends MyApplication {
#Override
protected List<Object> getModules() {
List<Object> modules = super.getModules();
return modules;
}
public void injectMocks(Object object) {
((TestMyApplication) Robolectric.application).inject(object);
}
Error:
java.lang.RuntimeException: java.lang.IllegalArgumentException: No inject registered for members/info.korzeniowski.MyApplication.TestMyApplication. You must explicitly add it to the 'injects' option in one of your modules.
When I change in My robolectric application class method to this:
#Override
protected List<Object> getModules() {
List<Object> modules = super.getModules();
modules.add(new TestModule());
return modules;
}
Result:
java.lang.RuntimeException: java.lang.IllegalStateException: Module adapter for class info.korzeniowski.walletplus.test.TestModule could not be loaded. Please ensure that code generation was run for this module.
Update
gradle.build:
compile 'com.jakewharton:butterknife:5.1.2'
def daggerVersion = '1.2.+'
apt "com.squareup.dagger:dagger-compiler:$daggerVersion"
compile "com.squareup.dagger:dagger:$daggerVersion"
compile "com.squareup:javawriter:2.2.1"
You should provide the TestModule and you are doing it right (TestModule must be in the list of modules returned from getModules). You can find solution for the second error here: Dagger example built through eclipse fails with 'Please ensure that code generation was run for this module.'