2

I am new in Dagger 2. I created a module which has a provider method that takes one String parameter:

@Module
public class MyModule {

  @Provides
  Student provideStudent(String name) {
    return new Student(name); 
  }
}

How should I pass the parameter name when injecting student? @Inject Student student? But where/How can I pass the name in?

David Medenjak
  • 33,993
  • 14
  • 106
  • 134
Leem.fin
  • 40,781
  • 83
  • 202
  • 354
  • *How should I pass the parameter in consumer (Activity/Fragment) where I would like to inject student?* Could you elaborate a little bit more ? – Blackbelt Oct 02 '17 at 09:46
  • where does name come from ? – Blackbelt Oct 02 '17 at 09:47
  • From anywhere I would like to create a student. I mean my project can create a student instance whenever needed, and hardcode the name. – Leem.fin Oct 02 '17 at 09:48
  • but in the end the activity/fragment will get in a bundle ? – Blackbelt Oct 02 '17 at 09:49
  • My question is how to inject a student instance with a name can be hardcoded during injecting. – Leem.fin Oct 02 '17 at 09:49
  • 1
    Unrelated to your question, you should have a look at _Constructor Injection_, because with Dagger you should not be calling `new` yourself anymore. That whole provides method could be replaced with a single `@Inject`, e.g. see here: https://stackoverflow.com/a/45422229/1837367 – David Medenjak Oct 06 '17 at 22:00
  • Does this answer your question? [Dagger 2: Injecting user inputted parameter into object](https://stackoverflow.com/questions/37516736/dagger-2-injecting-user-inputted-parameter-into-object) – Max Jan 16 '21 at 10:55

3 Answers3

4

You want to supply an object (String name) as a parameter to your component so that it can be used by other parts. You basically have 2 options to do so.

Module Constructor Parameters

Using module arguments is the "classic" approach that you would use before they introduced the Component.Builder.

All you do is add what you need to your module constructor, and provide it from the module.

@Module
public class MyModule {

  private String name;

  MyModule(String name) {
    this.name = name;
  }

  // -----------------------------------------------
  // you could obviously just use the field directly
  @Provides
  Student provideStudent() {
    return new Student(name); 
  }


  // ---------------------------------------------------------------------
  // alternatively you'd add a Qualifier and add the argument to the graph
  @Provides
  @Named("name")
  String provideName() {
    return name; 
  }

  // then you can use it...
  @Provides
  Student provideStudent(@Named("name") String name) {
    return new Student(name); 
  }

}

The first option that directly uses the name means that it's only available within that module. If you want to make use of constructor injection or use the qualified String you have to add it to the graph as shown second.

With either approach you'd have to manually add the module to the component, since the module does no longer use a default constructor. This is where you'd also supply your argument now.

DaggerMyComponent.builder()
        .myModule(new MyModule("some name"))
        .build();

Component Builder

The same concept but a different approach would be to use the component builder to achieve the same thing—bind something to the graph.

@Component(modules = MyModule.class)
interface MyComponent {

    @Component.Builder
    interface Builder {

        @BindsInstance Builder name(@Named("name") String name);

        MyComponent build();
    }
}

@Module
class MyModule {

    @Provides
    Student provideStudent(@Named("name") String name) {
        return new Student(name);
    }

}

Here you also just add an object to the graph that can be used. Here, too, you'd have to add it to the component builder

DaggerMyComponent.builder()
        .name("some name")
        .build();
David Medenjak
  • 33,993
  • 14
  • 106
  • 134
1

My question is how to inject a student instance with a name can be hardcoded.

name is a dependency of Student, and this dependency as to be resolved. To do so you need an annotated @Provide method that returns that String. Since the type is usually inferred by the return type, in case of String you will have to use also the annotation @Named

@Provides
@Named("Name")
public String provideName() {
    return "My name";
}

and change your method like

@Provides
Student provideStudent(@Named("Name") String name) {
   return new Student(name); 
}

this way dagger2 knows ho to resolve that String to build Student

Blackbelt
  • 156,034
  • 29
  • 297
  • 305
  • THanks, but it is not a enough answer for my question, because I am asking how to pass the name string during @Inject phase, that's during consuming phase not providing phase. You only answered how to do it in provider (providing phase) – Leem.fin Oct 02 '17 at 10:25
  • to `MyModule` you mean ? – Blackbelt Oct 02 '17 at 10:31
  • e.g. in My Activity, I need a instance of student, how to inject such instance? My question has mentioned `@Inject Student student` but how can I define `name` there? I want to `name` to be defined during consuming phase not providing phase. In general, my Activity/Fragment want to inject a student instance with name be anything Activity/Fragment prefer to use. – Leem.fin Oct 02 '17 at 10:37
  • every time you start your activity/fragment, you have a different student name, and you want dagger2 to build Student for you? Sorry but I am having trouble understanding what exactly you are trying to achieve – Blackbelt Oct 02 '17 at 11:00
  • Without dagger 2, in Activity or any class which needs student instance, I do `Student student = new Student("Whatever name I want")` , with dagger 2, I do `@Inject Student student`, but how to pass "Whatever name I want" here? That's my question. I don't want to define name in module because I want those class which needs student define the name. – Leem.fin Oct 02 '17 at 11:36
  • I think I got it now. As far as I know, it is not possible to achieve what you want without the Module having knowledge of Name. The easiest, as you can image, is to provide a setter to `Student`. Up to a certain extent and with a slightly more complex setup, a smaller scope might allow you to get close to it (I can provide a gist with some code if you want) – Blackbelt Oct 02 '17 at 11:58
  • thanks, I'll wait for a day to see if other people have good solutions, otherwise I will accept your answer. – Leem.fin Oct 02 '17 at 13:18
  • ping me if you want see the "slightly more complex setup" solution – Blackbelt Oct 02 '17 at 13:19
  • Sure, I would like to see :) – Leem.fin Oct 02 '17 at 13:22
1

Instead of injecting the Student directly, you could create a Student factory and inject it instead:

public class Student {
    public Student(String name, Knowledge knowledge) {}

    public static class Factory {
        private Knowledge knowledge;

        @Inject
        public Factory(Knowledge knowledge) {
            this.knowledge = knownledge;
        }

        public Student create(String name) {
            return new Student(name, knowledge);
        }
    }
}

Or the same code in Kotlin:

class Student(name: String, knowledge: Knowledge) {
    class Factory @Inject constructor(private val knowledge: Knowledge) {
        fun create(name: String) = Student(name, knowledge)
    }
}

This allows passing the name parameter on Student instantiation, like this:

public class MyClass {
    @Inject
    public Student.Factory studentFactory;
    private Student student;

    public void setupStudent() {
        this.student = studentFactory.create("Student's name");
    }
}
Max
  • 739
  • 2
  • 8
  • 23