google/dagger

Optional bindings in a parent component can sometimes use bindings from child components

goj opened this issue · 2 comments

goj commented

Like #2085, but for optional bindings.

It's possible for a child component to satisfy an optional binding, so that it will be injected into a parent component's class.

Example: https://gist.github.com/goj/defb14a6232064a0a4beb7d77913e8c3

Is it a bug or a feature?

It's a bug. Adding a scoping annotations (e.g. making JudgmentalCoffee a @Singleton in the example above) changes what gets injected. For a big projects and long injection chains this may lead to a very surprising results.

Hi @goj,

This particular case is actually working as intended.

Note that the feature only works for bindings that depend on optional bindings and/or multibindings. In particular, Dagger will re-resolve a binding in a component if there is a contribution to the optional binding or multibinding in that component.

For example, with multibindings you can have something like this:

@Module
interface ParentModule {
  @Multibinds
  Set<Bar> setOfBar();

  @Provides
  Foo provideFoo(Set<Bar> setOfBar) { ... }
}

@Module
interface ChildModule {
  @Provides
  @IntoSet
  Bar provideBarIntoSet() { ... }
}

public void main() {
  // The set is empty in this Foo
  Foo parentFoo = parentComponent.getFoo();

  // The set has one element in this Foo
  Foo childFoo = childComponent.getFoo();
}

I do agree that the behavior is a bit confusing since the contents of the optional or multibinding can change depending on which component you request it from, but that's how they were designed and also their main benefit.

Also note that #2085 is different and that actually is a bug. In that case the issue was that we were re-resolving for non-optional/multibindings.

It's a bug. Adding a scoping annotations (e.g. making JudgmentalCoffee a @Singleton in the example above) changes what gets injected. For a big projects and long injection chains this may lead to a very surprising results

Sorry, forgot to comment on this in my previous post.

Yes, scoping JudgmentalCoffee with @Singleton means that it will be locked into the ParentComponent and so it would no longer pick of the Sugar you've provided in your ChildComponent (i.e. the Optional<Sugar> it injects will be empty).

This complexity is part of the trade-off of using optional/multibindings, and I would advise to use them as little as possible and only when absolutely needed.