Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dagger2: Component cannot depend on multiple scoped components

Yes, I know this has been asked before, and yes, I know it is "by design".

But I'd like to do something like this:

@Component(modules = {RealmModule.class})
public interface RealmComponent {
    Realm realm();
}


@Component(modules = {RepositoryModule.class})
public interface RepositoryComponent {
    PersonRepository personRepository();

    ScheduleRepository schedulesRepository();
}

@Component(dependencies = {RealmComponent.class, RepositoryComponent.class})
public interface AppDataComponent
        extends RealmComponent, RepositoryComponent {
}

@ApplicationScope
@Component(dependencies = {AppContextComponent.class,
        AppDataComponent.class,
        AppDomainComponent.class,
        AppPresentationComponent.class,
        AppUtilsComponent.class})
public interface ApplicationComponent
        extends AppContextComponent, AppDataComponent, AppDomainComponent, AppUtilsComponent, AppPresentationComponent {
    void inject(CustomApplication customApplication);

    void inject(DashboardActivity dashboardActivity);
}

However, what I get is unscoped, every time I inject a JobManager or a ScheduleRepository or anything else, I get a new instance. The only way I could "fix" that was this.

@Module
public class JobManagerModule {
    private JobManager jobManager;

    @Provides
    public JobManager jobManager(Context context) {
        if(jobManager == null) {
            jobManager = new JobManager(context, new Configuration.Builder(context).networkUtil(
                    new WifiOrMobileNetworkUtil(context)).build());
        }
        return jobManager;
    }
}

Not a fan.

So, how is one meant to structure and rip apart the dependency tree, without making one big gigantic über blob component that has every single module listed and every single provision method (instead of these "subcomponent" component dependencies)?

I tried using subcomponents for this, but then you have to provide every single module for the final ApplicationComponent.

I'm not sure what to do here. I tried specifying @Singleton for every first-level component and @SubcomponentScope for every AppDataLevelComponent, I also tried making a new scope for every single subcomponent, but both of them failed with "cannot depend on multiple scoped components".

EDIT: Apparently in order to get scoped providers, marking the components with the scope is not enough - you must specify the scope for the @Provides annotated methods too.

@Module
public class RepositoryModule {
    @Provides
    @Singleton
    public PersonRepository personRepository() {
        return new PersonRepositoryImpl();
    }

    @Provides
    @Singleton
    public ScheduleRepository schedulesRepository() {
        return new SchedulesRepositoryImpl();
    }
}

In the meantime, I ended up with this übercomponent.

@Singleton
@Component(modules = {
        AppContextModule.class,
        DbMapperModule.class,
        DbTaskModule.class,
        RealmModule.class,
        RepositoryModule.class,
        InteractorModule.class,
        ServiceModule.class,
        PresenterModule.class,
        XmlPersisterModule.class
})
public interface ApplicationComponent
        extends AppContextComponent, AppDataComponent, AppDomainComponent, AppUtilsComponent, AppPresentationComponent {

Where the xyzComponent classes are just interfaces to store the provision methods...

(Please note that this structure is an anti-pattern as described by Martin Fowler, and you should organize modules based on features / activities, and make them into subscoped components using component dependencies. Component dependencies are used to subscope your superscope components, and "inherit" dependency providers.)

like image 207
EpicPandaForce Avatar asked Jul 10 '15 13:07

EpicPandaForce


1 Answers

I had same problems like you not while ago and ended using the same ubercomponent approach except I use @Subcomponents in order organize the things and not to have all modules listed in the ubercomponent (I call it "top" or "app's" component).

You may see an example here: How to migrate missing inject from module with complete = false from Dagger 1 to Dagger 2

like image 189
Ognyan Avatar answered Nov 10 '22 11:11

Ognyan