Why is Dagger2 asking me to provide something that the component is not supposed to provide?

I'm playing around trying to learn Dagger2. Just when I thought I was getting it, I seem to have gotten stuck. My application has two components, ApplicationComponent (singleton) and StripeComponent (1:1 activity) , which inherit an empty interface for the sake of readability.

Then it has two modules, ApplicationModule and StripeModule.

@Singleton @Component(modules = ApplicationModule.class)
public interface ApplicationComponent extends AbstractComponent ...

@PerActivity @Component(modules = { StripeModule.class }) public interface StripeComponent
extends AbstractComponent ...

@Module public class ApplicationModule
@Module public class StripeModule

One of the objects ApplicationModule provides is a Navigator, and I'm fairly sure than the way it does it is fairly correct:

@Provides @Singleton Navigator provideNavigator() {
  return new Navigator();
}

This is a very simple class with pretty much nothing in it yet:

@Singleton public class Navigator

Then when I generate the code, an extra provision factory is generated from StripeModule - StripeModule_ProvideNavigatorFactory. And then the compiler whines that I'm not providing it - which is true, and intentional. It should be provided by the application component only. The question is, why is this factory being generated then? Why doesn't Dagger2 understand that StripeModule is not supposed to provide a navigator?

Upvotes: 0

Views: 200

Answers (2)

netdpb
netdpb

Reputation: 411

You haven't included the exact error, or the code that actually depends on the Navigator.

But I'll assume the class that depends on Navigator is provided from within StripeModule or another module installed into StripeComponent.

StripeComponent and ApplicationComponent have to be related in some way in order for bindings in StripeComponent to use bindings provided by ApplicationComponent.

You can relate them either using component dependencies or subcomponents.

If you use subcomponents, you'd make StripeComponent a subcomponent of ApplicationComponent, which would mean that it can use any of the bindings in modules installed in ApplicationComponent, including the one for Navigator.

If you want to use component dependencies, you'd make StripeComponent depend on ApplicationComponent, and you'd add a method Navigator navigator() to ApplicationComponent. Bindings in StripeComponent can depend on any type returned by a method on a component it depends on.

Upvotes: 0

EpicPandaForce
EpicPandaForce

Reputation: 81539

Assuming you have a StripeActivity class that uses StripeComponent to inject itself, then you might end up with a scenario like this one

public class StripeActivity extends AppCompatActivity {
    @Inject
    Navigator navigator;

    @Override
    public void onCreate(Bundle saveInstanceState) {
        super.onCreate(saveInstanceState);
        CustomApplication application = (CustomApplication)getApplicationContext();
        StripeComponent stripeComponent = createComponent(application);
        stripeComponent.inject(this);
    }

    protected StripeComponent createComponent(CustomApplication application) {
        return DaggerStripeComponent.builder()
                .applicationComponent(application.getApplicationComponent())
                .build();
    }
}

public class CustomApplication extends Application {
    ApplicationComponent applicationComponent;

    @Override
    protected void onCreate() {
        super.onCreate();
        applicationComponent = createApplicationComponent();
    }

    protected ApplicationComponent createApplicationComponent() {
        return DaggerApplicationComponent.create();
    }

    public ApplicationComponent getApplicationComponent() {
        return applicationComponent;
    }
}

@Component(modules={ApplicationModule.class})
@Singleton
public interface ApplicationComponent {
    Navigator navigator();
}

@Component(dependencies={ApplicationComponent.class}, modules={StripeModule.class})
@PerActivity
public interface StripeComponent extends ApplicationComponent {
    void inject(StripeActivity stripeActivity);
}

@Module
public class ApplicationModule {
    @Provides
    @Singleton
    Navigator navigator() {
        return new Navigator();
    }
}

@Module
public class StripeModule {
    //@Provides
    //@PerActivity
    //...
}

@Scope
@Retention(RUNTIME)
public @interface PerActivity {
}

EDIT: For base class injection, you need to inject both the superclass and the subclass manually, and you need to specify both the superclass and the subclass in your component. In this case, it would work like this.

public abstract class BaseActivity extends AppCompatActivity {
    @Inject
    Navigator navigator;

    @Override
    public void onCreate(Bundle saveInstanceState) {
        super.onCreate(saveInstanceState);
        CustomApplication application = (CustomApplication)getApplicationContext();
        ApplicationComponent component = createComponentAndInjectSelf(application);
        component.inject(this);
    }

    protected abstract ApplicationComponent createComponentAndInjectSelf(CustomApplication application);
}

public class StripeActivity extends BaseActivity {
    @Override
    public void onCreate(Bundle saveInstanceState) {
        super.onCreate(saveInstanceState);
    }

    @Override
    public StripeComponent createComponentAndInjectSelf(CustomApplication application) {
        StripeComponent stripeComponent = DaggerStripeComponent.builder()
            .applicationComponent(application.getApplicationComponent())
            .build();
        stripeComponent.inject(this);
        return stripeComponent;
    }
}

@Component(modules={ApplicationModule.class})
@Singleton
public interface ApplicationComponent {
    Navigator navigator();

    void inject(BaseActivity baseActivity);
}

@Component(dependencies={ApplicationComponent.class}, modules={StripeModule.class})
@PerActivity
public interface StripeComponent extends ApplicationComponent {
    void inject(StripeActivity stripeActivity);
}

Upvotes: 3

Related Questions