web-dev-qa-db-fra.com

Erreur de poignard 2: la dépendance "ne peut pas être fournie sans un constructeur @Inject" alors qu'elle est annotée avec @Inject

J'ai commencé à utiliser Dagger 2 et j'ai été confronté à un problème étrange qui ressemble à un bug pour moi.

J'ai 3 modules, qui sont composés en un seul sous-composant, qui à son tour étend/augmente le composant de niveau supérieur.

Le sous-composant est assez simple: juste une combinaison de modules et un seul point d’injection:

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


    NavigationDrawerFragment inject(NavigationDrawerFragment object);

}

Les premiers modules ressemblent à ceci: ils fournissent des dépendances générales au niveau des fragments:

@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;
    }
}

Le deuxième module ressemble à ceci: il fournit le présentateur/contrôleur et ses dépendances pour un sous-ensemble d’UI à l’écran:

@Module
public class SwitcherModule {

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

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

}

Troisième module - un autre présentateur/contrôleur pour un sous-ensemble d’UI:

@Module
public class NavigationListModule {

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

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

Partie pertinente du fragment en cours d’injection:

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

NavigationListControllerImpl implémente le constructeur suivant:

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

L'erreur que je reçois du compilateur Dagger 2 est la suivante:

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 se plaint de l'absence du constructeur @ Inject-annotated, mais il existe! Si je remplace la création implicite de l'instance NavigationListControllerImpl (en passant par le paramètre de méthode @Provides) Par explicitement (avec new), le poignard commence à se plaindre de la même erreur, mais maintenant pour l'objet de présentateur, qui est la deuxième entrée dans le même module, et ainsi de suite.

Toute cette situation semble très étrange et j'aimerais entendre les commentaires d'utilisateurs plus expérimentés de Dagger 2 (et de développeurs?).

Merci d'avance!

43
dominus

On dirait que j'ai compris ce qui n'allait pas avec ma configuration de Dagger 2. Il n'est pas possible d'utiliser la même portée dans les composants et les sous-composants. Il est obligatoire de définir une nouvelle portée pour le sous-composant. Dans mon cas, j'ai fini par créer la portée @Screen pour mon sous-composant.

Je dirais que c'est un défaut mineur mais très gênant dans Dagger 2. Apparemment, Dagger-Compiler rapporte une erreur compréhensible et agréable concernant les mêmes portées dans un composant parent et un composant enfant si le composant enfant est étendu avec un composant parent comme dépendance. Mais une erreur complètement trompeuse est signalée par le compilateur si le composant parent et le sous-composant enfant partagent la même portée.

Merci @lukas de m'avoir donné un indice ici https://stackoverflow.com/a/30383088/80831 qui a conduit à une résolution du problème.

15
dominus

J'ai eu la même erreur parce que j'ai oublié d'exposer les objets fournis par les modules du composant parent aux autres composants qui en dépendent.

Exemple de composant parent:

@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*/
}

Exemple de composant qui rend le composant ci-dessus comme dépendance

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

Mise à jour:

AppModule:

...
    @Provides
    @Singleton
    AppPref provideAppPref() {
        return appPref;
    }
...
48
s-hunter

Le composant global et le sous-composant NavigationDrawerComponent doivent avoir des portées différentes. Utilisez @Singleton pour votre composant global et une autre portée pour le sous-composant.

Sinon, si vous appliquez la même étendue au composant global et au sous-composant, vous devez également déclarer les modules de votre sous-composant dans votre composant global:

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

Pour votre cas d'utilisation, vous pouvez également utiliser des dépendances de composants. Par exemple:

@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);
}
17
lukas

Entré à travers cette question aujourd'hui aussi. Pour moi, il y avait un problème avec le traitement des annotations (sur Android Studio 2.2 avec gradle 2.x).

Au lieu de ~~ apt ~~ j'ai utilisé annotationProcessor j'ai utilisé

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

et maintenant ça marche.

4
Tobias

Nous avons rencontré le même problème en essayant de créer des sous-composants, mais il semble être résolu dans Dagger 2.0.1.

2
Kavi

On dirait que c'est le même genre de dague d'erreur qui rapporte beaucoup d'erreurs. Dans mon cas, mon injection cible s’attendait à une classe concrète (Presenter) où le module qui fournit le présentateur ne renvoie que l’interface (DemoContract.Presenter).

donc changé de

@Inject
public Presenter mDemoPresenter;  

to

@Inject
public DemoContract.Presenter mDemoPresenter;

et le module qui fournit le présentateur ressemble à ceci:

@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);
    }
}
1
cgr