javadagger

Dagger dependency injected individually and @IntoSet at the same time. How do I implement it?


I'm learning Dagger. I'm a former Spring user. One of the things I noticed is I have to annotate each bean with @IntoSet to inject beans of a certain type as a collection. In Spring, I'd directly declare, for example, Set<MyBeanClass> as a parameter in an autowired method or constructor, and the framework would figure all out itself (granted, "framework figuring things out for you" is not always an advantage)

However, @IntoSet sometimes breaks things. Here's an MRE:

package myown;

import dagger.Component;

import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Set;

@Component(modules = {PersonModule.class})
@Singleton
public interface PersonComponent {
    Person getJane();
}
package myown;

import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;

import javax.inject.Singleton;

@Module(includes = PetModule.class)
public class PersonModule {
    @IntoSet
    @Singleton
    @Provides
    public Person jane(Pet pet) {
        return new PetOwner("Jane", pet);
    }
}
package myown;

import dagger.Module;
import dagger.Provides;

import javax.inject.Singleton;

@Module
public class PetModule {
    @Provides
    @Singleton
    Pet getPet() {
        return new Pet("Puffy");
    }
}
package myown;

public class Person {
    private final String name;

    public Person(String name) {
        this.name = name;
    }
}
package myown;

public class PetOwner extends Person {
    private final Pet pet;

    public PetOwner(String name, Pet pet) {
        super(name);
        this.pet = pet;
    }
}
package myown;

public class Pet {
    private final String name;

    public Pet(String name) {
        this.name = name;
    }
}

Try to build it and you get:

java: [Dagger/MissingBinding] myown.Person cannot be provided without an @Inject constructor or an @Provides-annotated method.
      myown.Person is provided at
          myown.PersonComponent.getJane()

However, if you comment out @IntoSet and try to rebuild it, everything is fine

To be clear, I want jane to be able to be injected directly and as part of an injected Set of all Persons (imagine, there are jack, john, etc. as well -- also annotated with @IntoSet)

Why is it happening, and is there a solution/workaround?


Solution

  • When you provide with @IntoSet it will only be in your Set. You can not directly inject 'Jane'

    If you want to inject Jane specifically you can provide Jane with a named annotation or a subtype and then bind or provide it again with @IntoSet

    @Module(includes = PetModule.class)
    public class PersonModule {
        @Singleton
        @Provides
        @Named("Jane")
        public Person jane(Pet pet) {
            return new PetOwner("Jane", pet);
    
        @Provides
        @IntoSet
        public Person janeIntoSet(@Named("Jane") jane: Person) {
            return jane;
        }
    }
    

    There are multiple ways to achieve it. See https://dagger.dev/dev-guide/multibindings.html.

    The difference in Spring is that it figures it out at runtime while everything in dagger is compile time generated. So there is no guessing or casting or reflection going on here. It needs to be all clear at compile time.