50

I've started using Dagger 2 and faced strange issue that looks like a bug to me.

I have 3 modules, that are composed into one subcomponent, which in turn extends/pluses higher level component.

Subcomponent is pretty simple: just combination of modules and a single injection point:

@Singleton
@Subcomponent(
        modules = {
                NavigationDrawerModule.class,
                NavigationListModule.class,
                SwitcherModule.class
        }
)
public interface NavigationDrawerComponent {


    NavigationDrawerFragment inject(NavigationDrawerFragment object);

}

First modules looks like this - it provides general fragment-level dependencies:

@Module
public class NavigationDrawerModule {

    private final Activity activity;
    private final View rootView;
    private final LoaderManager loaderManager;

    public NavigationDrawerModule(Activity activity, View rootView, LoaderManager loaderManager) {
        this.activity = activity;
        this.rootView = rootView;
        this.loaderManager = loaderManager;
    }

    @Provides @Singleton EventBus provideLocalBus() {
        return EventBus.builder().build();
    }

    @Provides @Singleton View provideViewRoot() {
        return rootView;
    }

    @Provides @Singleton LoaderManager provideLoaderManager() {
        return loaderManager;
    }

    @Provides @Singleton Context provideContext() {
        return activity;
    }
}

Second module looks like this - it provides presenter/controller and their dependencies for a subset of UI on screen:

@Module
public class SwitcherModule {

    @Provides SwitchController provideSwitcherController(SwitchControllerImpl impl) {
        return impl;
    }

    @Provides SwitcherView provideSwitcherView(SwitcherViewImpl impl) {
        return impl;
    }

}

Third module - another presenter/controller for a subset of UI:

@Module
public class NavigationListModule {

    @Provides @Singleton NavigationListController provideNavigationListController(NavigationListControllerImpl impl) {
        return impl;
    }

    @Provides @Singleton NavigationListView provideNavigationListView(NavigationListViewImpl impl) {
        return impl;
    }
}

Relevant part of the fragment that is being injected:

@Inject SwitchController identitySwitchController;
@Inject SwitcherView identitySwitcherView;
@Inject NavigationListController navigationListController;
@Inject NavigationListView navigationListView;

NavigationListControllerImpl implements the following constructor:

@Inject
public NavigationListControllerImpl(Context ctx, EventBus bus) {
    this.ctx = ctx;
    this.bus = bus;
}

Error I'm getting from the Dagger 2 compiler is the following:

error: ...sidenavigation.navigationlist.NavigationListControllerImpl cannot be provided without an @Inject constructor or from an @Provides-annotated method.
...sidenavigation.NavigationDrawerFragment.navigationListController
[injected field of type: ...sidenavigation.navigationlist.NavigationListController navigationListController]
...sidenavigation.navigationlist.NavigationListModule.provideNavigationListController(...sidenavigation.navigationlist.NavigationListControllerImpl impl)
[parameter: ...sidenavigation.navigationlist.NavigationListControllerImpl impl]

Error complains about missing @Inject-annotated constructor, but it exists! If I replace implicit NavigationListControllerImpl instance creation (passing via @Provides-method parameter) with explicit (with new), dagger starts complaining about the same error but now for the presenter object which is the second entry in the same module, and so on.

All this situation looks very strange, and I'd like to hear some input from more experienced Dagger 2 users (and developers?).

Thank you in advance!

dominus
  • 1,082
  • 1
  • 11
  • 14
  • If you haven't resolved this yet, can you confirm that the @Inject annotation is javax.inject.Inject? – Jeff Bowman May 21 '15 at 16:51
  • This issue is still bugging me. And yes, I confirm that @Inject is actually javax.inject.Inject. The global component worked fine until I added subcomponent - then I get the error. – dominus May 21 '15 at 17:32
  • 2
    I've posted issue on the Dagger 2 github page. It includes more details https://github.com/google/dagger/issues/188 – dominus May 21 '15 at 17:36

6 Answers6

58

I got this same error because I forgot to expose the objects provided by the modules in the parent component to the other components that are depend on it.

Parent component example:

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
    AppPref exposeAppPref(); /* my issue was caused by forgot this line,
the method name doesn't matter, what matters is the object type AppPref provided in the AppModule 
that you want it to be available in the component that declares this component as one of its dependencies*/
}

Sample component that makes the above component as a dependency

@UserScope
@Component (dependencies = {AppComponent.class})
public interface ActivityComponent {
    void inject(MainActivity activity);
}

Update:

AppModule:

...
    @Provides
    @Singleton
    AppPref provideAppPref() {
        return appPref;
    }
...
Dr.jacky
  • 3,341
  • 6
  • 53
  • 91
s-hunter
  • 24,172
  • 16
  • 88
  • 130
20

The GlobalComponent and the subcomponent NavigationDrawerComponent must have different scopes. Use @Singleton for your GlobalComponent and some another scope for the subcomponent.

Otherwise, if you apply the same scope to the GlobalComponent and to the subcomponent, you must declare the modules of your subcomponent in your global component as well:

@Component(
        // modules from subcomponent must be declared here also
        modules = {NavigationListModule.class, 
                  SwitcherModule.class, 
                  NavigationDrawerModule.class,
                  ...}
)
@Singleton
public interface GlobalComponent {
   NavigationDrawerComponent plus(NavigationDrawerModule module);
}

For your use case, you can also use component dependencies. For instance:

@Component(
        dependencies = GlobalComponent.class,
        modules = {NavigationListModule.class, 
                  SwitcherModule.class, 
                  NavigationDrawerModule.class}
)
@YourOtherDaggerScope // @Singleton scope won't work here, it must be a different scope
public interface NavigationDrawerComponent extends GlobalComponent { // extend the parent component if you wish to get access to parent dependencies

   NavigationDrawerFragment inject(NavigationDrawerFragment object);
}
lukasz
  • 3,121
  • 1
  • 21
  • 20
  • 2
    Could you pleased point me to the excerpt in documentation related to this? The thing is that such fix would defeat any point to have subcomponent - any dependency would be available globally and it's possible accidentally depend on some local thing from short-lived screen. – dominus May 21 '15 at 20:15
  • I added an edit in order to show that I would rather use component dependencies here. Regarding the subcomponent and its modules declaration, the documentation does not say things clearly, but this is how I understand it regarding the thigh coupling of component and subcomponent together. – lukasz May 21 '15 at 20:48
  • thank you for a hint in your sample code. The key issue was that I've used the same `@Singleton` scope in both parent component and child @Subcomponent. – dominus May 22 '15 at 12:01
  • You're right! I edited my answer to reflect your solution, for better clarity for other people that might read it. – lukasz May 23 '15 at 23:39
  • This is wrong. You said correcrly, but you did wrong; "_The GlobalComponent and the subcomponent NavigationDrawerComponent must have different scopes_" - If you use twice the same modules (like in your code sample) in two Components of differents scopes dagger compiler will throw error – murt Aug 10 '16 at 11:23
17

Seems like I've figured out what was wrong with my Dagger 2 setup. It's not possible to use the same scope in both component and subcomponents. It's required to define a new scope for subcomponent. In my case I've ended up creating @Screen scope for me subcomponent.

I'd say that this is a small but very annoying defect in Dagger 2. Apparently dagger-compiler reports nice and understandable error about the same scopes in a parent component and child component if child component is extended with a parent component as dependency. But completely misleading error is reported by the compiler if parent component and child subcomponent share the same scope.

Thank you, @lukas, for giving me a hint here https://stackoverflow.com/a/30383088/808313 that led to a problem resolution.

Community
  • 1
  • 1
dominus
  • 1,082
  • 1
  • 11
  • 14
5

Came accross this issue today too. For me there was a problem with the Annotation processing (on Android Studio 2.2 with gradle 2.x).

Instead of ~~apt~~ I used annotationProcessor I used

annotationProcessor 'com.google.dagger:dagger-compiler:2.6'

and now It's working.

Tobias
  • 7,282
  • 6
  • 63
  • 85
  • This FINALLY worked for me! If you are using the latest version of Android Studio, i.e. 2.2.2, you need to use `annotationProcessor` as per Lord Flash's answer! – Subby Nov 08 '16 at 14:04
  • When i use annotationProcessor then i am getting this error - Error:(53, 30) error: cannot find symbol variable DaggerActivityComponent And when i use apt the i am getting this error - cannot be provided without an @Inject constructor or from an Please let me know what to do now? – Shoeb Siddique Jul 27 '17 at 05:58
2

Came across the same issue while trying to create Subcomponents, but it seems to be fixed in Dagger 2.0.1.

Kavi
  • 3,880
  • 2
  • 26
  • 23
2

Seems it is the same kinda error dagger reports for many mistakes. In my case, my target injection was expecting concrete class (Presenter) where as the module that provides presenter was returning only the interface (DemoContract.Presenter)

So changed from

@Inject
public Presenter mDemoPresenter;  

to

@Inject
public DemoContract.Presenter mDemoPresenter;

and module that provides presenter looks like this:

@Module
public class DiDemoPresenterModule {
    private final DemoContract.View mView;

    DiDemoPresenterModule(MainActivity mView) {
        this.mView = mView;
    }

    @Provides
    public DemoContract.Presenter providesDemoPresenter(Repository repository) {
        return new DemoPresenter(repository, mView);
    }
}
Satan Pandeya
  • 3,747
  • 4
  • 27
  • 53
cgr
  • 4,578
  • 2
  • 28
  • 52