0

I have been trying to implement Dagger2.

Problem: When I use constructor injection, it works fine but when I use field injection, it throws an Error like below:

Error:(6, 48) error: cannot find symbol class DaggerApplicationComponent
/home/moderator/Downloads/Maulik/Sample Codes/Made/Dagger2Demo/app/src/main/java/com/dagger2demo/dagger2demo/di/component/ApplicationComponent.java
Error:(18, 10) error: com.dagger2demo.dagger2demo.mvp.HomePresenter cannot be provided without an @Inject constructor or from an @Provides- or @Produces-annotated method. This type supports members injection but cannot be implicitly provided.
com.dagger2demo.dagger2demo.mvp.HomePresenter is injected at
com.dagger2demo.dagger2demo.mvp.BaseActivity.homePresenter
com.dagger2demo.dagger2demo.mvp.BaseActivity is injected at
com.dagger2demo.dagger2demo.di.component.ApplicationComponent.inject(baseActivity)
Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
> Compilation failed; see the compiler error output for details.

Dagger2 - My Understanding: You have to create a Module class where you will create methods. These methods will give you respective object of your needed class like Retrofit, ApplicationContext etc.. You will create an component interface in which you will define where to inject module class's dependencies.

I'm using: Retrofit, RxJava - RaxAndroid, Dagger2 & MVP.

Code is below:

build.gradle(app)

// Retrofit Dependency
compile 'com.squareup.retrofit2:retrofit:2.3.0'

// Gson Converter Factory Dependency
compile 'com.squareup.retrofit2:converter-gson:2.3.0'

// RxJava2 Adapter Dependency for Retrofit2
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'

// ButterKnife Dependencies
compile 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

// RxJava & RxAndroid Dependencies
compile group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.1.8'
compile group: 'io.reactivex.rxjava2', name: 'rxandroid', version: '2.0.1'

// Dagger2 Dependency
compile 'com.google.dagger:dagger:2.14.1'
annotationProcessor 'com.google.dagger:dagger-compiler:2.14.1'

Dagger2DemoApplication.java

    public class Dagger2DemoApplication extends Application {

    private ApplicationComponent mApplicationComponent;

    @Override
    public void onCreate() {
        super.onCreate();

        mApplicationComponent = DaggerApplicationComponent.builder()
                .applicationModule(new ApplicationModule())
                .build();
    }

    public ApplicationComponent getmApplicationComponent() {
        return mApplicationComponent;
    }
}

ApplicationModule.java

    @Module
public class ApplicationModule {

    @Provides
    @Singleton
    public APIEndPoints provideAPIEndPoints() {

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://reqres.in/")
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

        APIEndPoints apiEndPoints = retrofit.create(APIEndPoints.class);

        return apiEndPoints;
    }
}

ApplicationComponent.java

    @Singleton
@Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {

    void inject(BaseActivity baseActivity);
}

BaseActivity.java

    public class BaseActivity extends AppCompatActivity {

    // Variables
    public ProgressDialog mProgressDialog;

    @Inject
    HomePresenter homePresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // For Dagger2 i.e Creating instance of all provide methods defined in ApplicationModule
        ((Dagger2DemoApplication) getApplication()).getmApplicationComponent().inject(this);

        setupProgressBar();
    }

    private void setupProgressBar() {
        mProgressDialog = new ProgressDialog(this);
        mProgressDialog.setTitle(getString(R.string.str_progress_dialog_title));
        mProgressDialog.setMessage(getString(R.string.str_progress_dialog_desc));
        mProgressDialog.setCancelable(false);
    }
}

BaseView.java

    public interface BaseView extends View {

    void handleResponse(Object obj);

    void showMessage(String msg);
}

View.java

    public interface View {

}

BasePresenter.java

public interface BasePresenter {

    void attachView(View view);

    void callAPI();
}

HomePresenter.java

    public class HomePresenter implements BasePresenter {

    private BaseView mBaseView;

    @Inject
    APIEndPoints mApiEndPoints;

    /*@Inject
    public HomePresenter(APIEndPoints apiEndPoints) {
        this.mApiEndPoints = apiEndPoints;
    }*/

    @Override
    public void attachView(View view) {
        mBaseView = (BaseView) view;
    }

    @Override
    public void callAPI() {

        // Actually calling API here with observable object - Start
        Observable<Users> usersObservable = mApiEndPoints.getUsers();

        usersObservable
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::onSuccess, this::onError);
        // Actually calling API here with observable object - End
    }

    private void onSuccess(Users users) {
        mBaseView.handleResponse(users);
    }

    private void onError(Throwable throwable) {
        mBaseView.showMessage(throwable.toString());
    }
}

HomeActivity.java

    public class HomeActivity extends BaseActivity implements BaseView {

    // Widgets
    @BindView(R.id.rv_users)
    RecyclerView rv_users;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // For ButterKnife
        ButterKnife.bind(this);

        // Initializing Presenter
        homePresenter.attachView(this);
    }

    public void getDataFromServer(View view) {
        mProgressDialog.show();
        homePresenter.callAPI();
    }

    // BaseView Methods
    @Override
    public void handleResponse(Object obj) {
        Users users;
        if (obj instanceof Users) {
            users = (Users) obj;
            if (users != null) {
                mProgressDialog.dismiss();
                rv_users.setLayoutManager(new LinearLayoutManager(HomeActivity.this));
                rv_users.setAdapter(new RVAdapter(users.getData()));
            }
        }
    }

    @Override
    public void showMessage(String msg) {
        if (msg != null) {
            mProgressDialog.dismiss();
            Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
        }
    }
}

As you can see I commented Constructor Injection in HomePresenter. I'm having Field Injection there instead. But I'm not able to build the project as I'm getting error like mentioned above.

Any help will be appreciated. Let me know if any other things related to code is required.

Thanks in advance.

EDIT: PS: I know the answer but I just can't understand why Field Injection i.e @Inject APIEndPoints mApiEndPoints; is not working in HomePresenter. Please someone explain me.

Maulik Dodia
  • 1,629
  • 2
  • 21
  • 48

2 Answers2

2

As you can see I commented Constructor Injection in HomePresenter. I'm having Field Injection there instead.

If you use Constructor Injection, then Dagger will create the object for you and know all about it.

If you use field injection then you have to create the object and tell Dagger about it.

I don't see why you would prefer to use field injection in this case, but with field injection you need to add a @Provides annotated method to one of your modules to give Dagger access to your presenter.

You need to use either Construcotr injection, or a @Provides annotated methods in your module, just as the error states.

David Medenjak
  • 33,993
  • 14
  • 106
  • 134
  • I have created **provideAPIEndPoints()** method in **ApplicationModule**. I'm really confused when to use Field Injection and when to use Constructor Injection. Can you please guide me? – Maulik Dodia Jan 22 '18 at 12:21
  • @MaulikDodia There are different opinions on when to use what. I'm personally a strong supporter of constructor injection whenever possible, so you'd only use field injection with Android Framework types (where you can't add arguments to the constructor) I wrote an [introductory blog post](https://blog.davidmedenjak.com/android/2017/11/11/dagger-rules-engagement.html) a while ago if you'e interested – David Medenjak Jan 22 '18 at 12:29
  • I would definitely interested in reading that blog. As I already said in previous comment that I have created **provideAPIEndPoints()** method in **ApplicationModule**. Then why I need constructor injection? Can you please elaborate your answer?@DavidMedenjak – Maulik Dodia Jan 22 '18 at 12:34
  • @MaulikDodia `provideAPIEndPoints` does not provide a `HomePresenter` (see your error). You need to add a `@Provides provideHomePresenter()` or switch back to constructor injection for it – David Medenjak Jan 22 '18 at 12:39
  • But how come **@Inject HomePresenter homePresenter;** is working in BaseActivity? I did not define any method for HomePresenter in Module class.@DavidMedenjak – Maulik Dodia Jan 22 '18 at 13:02
  • @MaulikDodia please read the error you included in your question carefully. `HomePresenter` cannot be provided in `BaseActivity`, so it's _not_ working. For more information on how to read the error [see here](https://stackoverflow.com/q/44912080/1837367) – David Medenjak Jan 22 '18 at 13:05
  • It's working bro. When I uncomment constructor injection code and comment the field injection code in HomePresenter. Everything works for me. That means **@Inject HomePresenter homePresenter;** is working in BaseActivity. But I don't understand how?@DavidMedenjak – Maulik Dodia Jan 22 '18 at 13:33
  • @MaulikDodia As you can read in the answer above, you need to tell Dagger about every object. If you use constructor injection, Dagger will create the object and thus also know about it. With field injection you need to use `@Provides` annotated methods to add the object to Dagger. – David Medenjak Jan 22 '18 at 14:57
  • Still confusion of **Field Injection**. Just check my code. I'm doing `@Inject HomePresenter homePresenter;` in `BaseActivity`. **Your words: You need to tell Dagger about every object. If you use constructor injection, Dagger will create the object and thus also know about it. With field injection you need to use `@Provides` annotated methods to add the object to Dagger.** Neither I provided `@Provides` method nor constructor with `@Inject` annotation in `BaseActivity` for `HomePresenter`. But it is working correctly, how?@DavidMedenjak – Maulik Dodia Jan 23 '18 at 11:23
  • @MaulikDodia You said in your previous comment that it works when you uncomment the constructor injection code, so it seems to me like you're using constructor injection – David Medenjak Jan 23 '18 at 20:11
  • Why are you guys not understanding my concern? Read my above comment carefully and then check my code.@DavidMedenjak – Maulik Dodia Jan 24 '18 at 05:59
0

You're confounding dependencies producer mechanism and dependencies consumption mechanism. An annotated field is used to consume a dependency. In your case, @Inject HomePresenter homePresenter is telling Dagger "hey, I want you to inject an HomePresenter here". To do so, Dagger either needs you to define a @Provides method or to have the object constructor annotated with @Inject.

As a rule of thumb, always use @Inject annotated constructor to provide dependencies. You should only use @Provides method provider when the objects you're providing are either:

  • an interface
  • an abstract class
  • an object coming from an external library (you don't have access to the constructor)
  • an object which requires customization before being provided

In your case, you got your error because you don't have a @Provides annotated method nor an @Inject annotated constructor. You should uncomment your constructor as it is the way to go in your situation.

Benjamin
  • 7,055
  • 6
  • 40
  • 60
  • But how come `@Inject HomePresenter homePresenter;` is working in BaseActivity? I did not define any `@Provides` method for HomePresenter in Module class@Benjamin – Maulik Dodia Jan 23 '18 at 10:27
  • When you annotate a constructor with `@Inject`, Dagger knows how to inject it and takes care of it for you (no need for an `@Provides` method) – Benjamin Jan 23 '18 at 10:33
  • Please read comment carefully. I'm asking you why `@Inject HomePresenter homePresenter;` is working in BaseActivity? although I did not injected with constructor injection. Instead I injected with filed injection and also did not define `@Provides` method in module class. That is the opposite to what you mentioned in above comment@Benjamin – Maulik Dodia Jan 23 '18 at 10:39
  • The above code is compiling correctly with your `HomePresenter` constructor commented? I doubt it – Benjamin Jan 23 '18 at 10:43
  • The above code is giving me error as I mentioned in question. Just answer me below simple questions? 1. If I want to use field injection, I have to define `@Provides` method in module class? 2. If I want to use constructor injection, `@Provides` is not required. Is it right?@Benjamin – Maulik Dodia Jan 23 '18 at 10:48
  • You're mixing dependencies providing mechanism and dependencies consumption mechanism. An annotated field is use to consume a dependency. In your case, `@Inject HomePresenter homePresenter` is telling Dagger "hey, I want you to inject an HomePresenter here". To do so, Dagger either needs you to define an `@Provides`method or to have the object constructor annotated with `@Inject`. So to answer your question: 1. no, you should have an `@Provides` method OR an annotated `@Inject` constructor. 2. if your constructor is annotated, you don't need the `@Provides`. – Benjamin Jan 23 '18 at 10:54
  • Still confusion of **Field Injection**. Just check my code. I'm doing `@Inject HomePresenter homePresenter;` in `BaseActivity`. **Your words: `@Inject HomePresenter homePresenter` is telling Dagger "hey, I want you to inject an HomePresenter here". To do so, Dagger either needs you to define an `@Provides` method or to have the object constructor annotated with @Inject**. Neither I provided `@Provides` method nor constructor with @Inject annotation in `BaseActivity` for HomePresenter. But it is working correctly. Sorry for again asking clarification@Benjamin – Maulik Dodia Jan 23 '18 at 11:06
  • You did one of them... your `HomePresenter` constructor is annotated with `@Inject`... if you comment it it does not work anymore. I'm done here, I can not do much. – Benjamin Jan 23 '18 at 11:09