Reputation: 6188
I am using Dagger 2 to inject dependencies in my new Android app. I want to inject a dao into a service.
Module:
@Module
public class DenkoStationModule {
@Provides
@Singleton
public DenkoStationDao provideDenkoStationDao() {
DaoMaster daoMaster = new DaoMaster(DenkoApplication.getDatabase());
DaoSession daoSession = daoMaster.newSession();
Log.d("dao", "station dao created");
return daoSession.getDenkoStationDao();
}
@Provides
@Singleton
public DenkoStationService provideDenkoStationService() {
Log.d("service", "station service created");
return new DenkoStationService();
}
}
Service:
public class DenkoStationService {
private DenkoStationDao denkoStationDao;
@Inject
public void setDenkoStationDao(DenkoStationDao denkoStationDao) {
this.denkoStationDao = denkoStationDao;
}
public List<DenkoStation> fetchAllDenkoStations() {
Log.d("service", "loading all");
return denkoStationDao.loadAll();
}
}
Component:
@Singleton
@Component(modules = {DenkoStationModule.class})
public interface DenkoStationComponent {
DenkoStationService provideDenkoStationService();
}
I use it from my activity like this:
DenkoStationComponent denkoStationComponent = Dagger_DenkoStationComponent.builder().denkoStationModule(new DenkoStationModule()).build();
DenkoStationService denkoStationService = denkoStationComponent.provideDenkoStationService();
List<DenkoStation> denkoStations = denkoStationService.fetchAllDenkoStations();
Looks like the dao does not get injected.
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.util.List org.bitbucket.infovillafoundation.denko.dao.DenkoStationDao.loadAll()' on a null object reference
at org.bitbucket.infovillafoundation.denko.service.DenkoStationService.fetchAllDenkoStations(DenkoStationService.java:23)
at org.bitbucket.infovillafoundation.denko.activity.HomeActivity.onCreate(HomeActivity.java:29)
at android.app.Activity.performCreate(Activity.java:5933)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2251)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360)
at android.app.ActivityThread.access$800(ActivityThread.java:144)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1278)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5221)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
When you use a dependency injection solution, you'd expect the framework to inject dependencies of dependencies as well. Is it a framework problem or have I done something wrong?
Upvotes: 2
Views: 1920
Reputation: 315
I have been struggling with the same issue in recent days, attempting to migrate my existing Dagger 1 code to work with Dagger 2.
After a fair amount of trial and error, I have stumbled across a solution - supply a MembersInjector<> parameter to your @Provides method, and call its injectMembers() method after creating the object. In your case, you would modify the provideDenkoStationService() as shown below.
@Provides
@Singleton
public DenkoStationService provideDenkoStationService(MembersInjector<DenkoStationService> injector) {
Log.d("service", "station service created");
DenkoStationService denkoStationService = new DenkoStationService();
injector.injectMembers(denkoStationService);
return denkoStationService;
}
Unfortunately, injectMembers() returns void so you can't condense this into one line, but a convenient utility method should be able to do that for you.
I should stress that this is my own discovery, and I have not seen this behaviour documented anywhere! It works perfectly for me though.
I agree that based on other DI frameworks I've worked with, and my understanding of DI in general, this kind of functionality should be offered for free or at least through annotations of some sort. Espcecially since it did work in Dagger 1 (again, not in the most obvious way, but the solution was simpler and more intuitive)
Using constructor injection as suggested in another answer is another solution, obviously, but for numerous objects with multiple dependencies, requires the writing of a large amount of boilerplate code, defeating the purpose of Dagger in general. Also, I couldn't get it to inject Provider objects, which I needed.
Upvotes: 0
Reputation: 4010
You can solve your problem by doing the below. Note that the DenkoStationService
does not have any @Inject
annotation as the service is being created in the provider method. The argument to the provideDenkoStationService()
gets passed by Dagger.
@Module
public class DenkoStationModule {
@Provides
@Singleton
public DenkoStationDao provideDenkoStationDao() {
DaoMaster daoMaster = new DaoMaster(DenkoApplication.getDatabase());
DaoSession daoSession = daoMaster.newSession();
Log.d("dao", "station dao created");
return daoSession.getDenkoStationDao();
}
@Provides
@Singleton
public DenkoStationService provideDenkoStationService(DenkoStationDao denkoStationDao) {
Log.d("service", "station service created");
return new DenkoStationService(denkoStationDao);
}
}
public class DenkoStationService {
private DenkoStationDao denkoStationDao;
public DenkoStationService(DenkoStationDao denkoStationDao) {
this.denkoStationDao = denkoStationDao;
}
public List<DenkoStation> fetchAllDenkoStations() {
Log.d("service", "loading all");
return denkoStationDao.loadAll();
}
}
Upvotes: 1
Reputation: 12365
Dagger 2 uses Annotation Processor If you want to Inject something in your object you have to call method which is responsible for inject before you use instance. Is there any place where you initialise Component
? If there is not any place you have to do this.
Create Initializer inside of your Component as is shown below:
@Singleton
@Component(modules = {DenkoStationModule.class})
public interface DenkoStationComponent {
public final static class Initializer {
public static HierarchyViewerComponent init() {
return Dagger_DenkoStationComponent.builder()
.denkoStationModule(new DenkoStationModule()) //it is not necessary because DenkoStationModule doesn't have params
.build();
}
void inject(DenkoStationService deviceInfoProvider);
}
}
After that you have to initialise your Component by method:
DenkoStationComponent.Initializer.init();
Application object is a good place to do this. And you have to keep instance of this (it can be static).
Now you can inject your dao object as is shown below:
public class DenkoStationService {
@Inject
DenkoStationDao denkoStationDao;
@Override
public void onCreate() {
super.onCreate();
//lines below are responsible for inject your object
//use line below if you keep component instance in some object
//ObjectWhichContainsComponent.methodToGetComponent().inject(this);
//or
DenkoStationComponent.Initializer.init().inject(this);
//if you didn't initialise component before
}
public List<DenkoStation> fetchAllDenkoStations() {
Log.d("service", "loading all");
return denkoStationDao.loadAll();
}
}
If you have any question, ask in comment.
Here you can find more information.
Upvotes: 2