Dagger - Getting Same Instance On Different Compon

2019-05-18 07:43发布

问题:

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?

回答1:

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