Ahmad Fadli Ahmad Fadli -4 years ago 296
Android Question

Dagger - Getting Same Instance On Different Component

I'm having a similar problem like the one in this question.
While the accepted answer does help, but I'm missing final piece to solve the problem.

I have 2 android library modules:

common
and
exp
which depends on
common
.

Everything under
common
:

@Module
public class CommonModule {
@Singleton
@Provides
public Repository providesRepository() {
return new Repository();
}
}

@Singleton
@Component(modules={CommonModule.class})
public interface CommonComponent {
void inject(CommonClass commonClass);
/**
CommonClass needs instance of Repository
**/
}

public class CommonDIHolder {
public static CommonComponent sComponent;

public static void init() {
sComponent = DaggerCommonComponent.builder().build();
}
}


Everything under
exp
:

@Module(includes={CommonModule.class})
public class ExpModule {
@Singleton
@Provides
public ExpResource provideExpResource() {
return new ExpResource();
}
}

@Singleton
@Component(modules={ExpModule.class}, dependencies={CommonComponent.class})
public interface ExpComponent {
void inject(ExpClass expClass);
/**
ExpClass needs instance of Repository and ExpResource
**/
}

public class ExpDIHolder {
public static ExpComponent sComponent;

public static void init() {
sComponent = DaggerExpComponent.builder()
.commonComponent(CommonDIHolder.sComponent)
.build();
}
}


I need both
CommonClass
and
ExpClass
receive the same instance of
Repository
.

The problem with this approach is that
@Singleton
can't depends on
@Singleton
. So I have to change the scope of
ExpComponent
into self-defined scope called
@ExpScope
. Then I changed the
provideExpResource
into
@ExpScope
as well.

Then I encountered an error saying that
ExpComponent
may not reference bindings with different scopes. It refers to the
provideRepository
which has different scope (
@Singleton
) on it. If I changed the scope into
ExpScope
then the
CommonComponent
will have different scope with
provideRepository
.

If I changed all
@Singleton
into
@ExpScope
then I receive this error message:
depends on scoped components in a non-hierarchical scope ordering


What should I do? Or I'm doing the wrong approach here?

Answer Source

Use one and only one @Singleton scoped component

You should have one and only one @Singleton scoped component like this:

@Singleton
@Component(modules={CommonModule.class, ExpModule.class})
public interface CommonComponent {

}

Only specify Activities, Fragments, and Services as explicit injection targets for Components

In an Android app, you should only list Activities, Fragments and Services as injection sites. You should configure Dagger 2 to inject the rest of your dependencies without having to resort to calling component.inject(this) inside them.

For example, if your CommonClass looks like this:

public class CommonClass {
    @Inject Repository repository;

    public class CommonClass() {
         CommonComponentHolder.get().inject(this);
    }
}

Refactor it like this:

public class CommonClass {
    private final Repository repository;

    @Inject
    public class CommonClass(Repository repository) {
         this.repository = repository;
    }
}

Now when you have an Activity or Fragment that needs CommonClass and you are injecting with CommonComponent or one of its sub-components or dependent components, they can obtain instances of CommonClass wired with the correct dependencies:

public class MyActivity extends AppCompatActivity {

    @Inject CommonClass commonClass;

    public void onCreate(Bundle savedInstanceState) {
        CommonComponentHolder.getComponent().inject(this);
    }
}

Use subcomponents or dependent components to specify the injection targets

Now you have a @Singleton scoped component, you'll probably want to create a component for a narrower scope for your Activity or Fragment. You'll have to connect it to your CommonComponent, so use dependent components or subcomponents (subcomponents are preferred as of Dagger 2.10). Since you say you have already tried defining a @ExpScope, I think the missing piece is to make subcomponent or dependent component with the @ExpScope that injects your Activity or Fragment.

Something like the following for the top-level singleton component:

@Singleton
@Component(modules={CommonModule.class, ExpModule.class})
public interface CommonComponent {
    ExpComponent.Builder exComponent();
}

And then for the subcomponent:

@ExpScope
@Subcomponent(modules = {NarrowerScopedModule.class})
public interface ExpComponent {
    @Subcomponent.Builder
    public interface Builder {
        Builder narrowerScopedModule(NarrowerScopedModule narrowerScopedModule);
        ExpComponent build();
    }
}

There are good working examples of Android projects in the Google Android Architecture Blueprints Github repo

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download