Tmarsh2
Tmarsh2

Reputation: 417

Dagger2 Constructor Injection not working

I am trying to inject a presenter for my Fragment via Constructor injection (so that I do not need to create a @Provides method for the presenter).

I believe that I am doing it correctly, but I am still receiving the following error:

MainFragmentPresenter cannot be provided without a @Provides- or @Produces-annotation method.

Here is my setup:

ApplicationComponent.java

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

    NetworkService networkService();
}

MainFragmentComponent.java

@PerFragment
@Component(
        dependencies = ApplicationComponent.class,
        modules = MainFragmentPresenterModule.class
)
public interface MainFragmentComponent {

    MainFragmentView view();

    void inject(MainFragment mainFragment);
}

MainFragmentPresenterModule.java

@Module
public class MainFragmentPresenterModule {

    private final MainFragmentView _view;

    public MainFragmentPresenterModule(MainFragmentView view) {
        _view = view;
    }

    @Provides
    @PerFragment
    MainFragmentView provideMainFragmentView() {
        return _view;
    }
}

NetworkModule.java

@Module
public class NetworkModule {

    @Provides
    @Singleton
    OkHttpClient provide_OkHttpClient() {
        return new OkHttpClient();
    }

    @Provides
    @Singleton
    OpenWeatherEndpoints provide_OpenWeatherAPI(OkHttpClient client) {

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://api.openweathermap.org/data/2.5/")
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(client)
                .build();

        return retrofit.create(OpenWeatherEndpoints.class);

    }

    @Provides
    @Singleton
    YoutubeEndpoints provide_YoutubeEndpoints(OkHttpClient client) {

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://www.googleapis.com/youtube/v3/")
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(client)
                .build();

        return retrofit.create(YoutubeEndpoints.class);

    }

    @Provides
    @Singleton
    NetworkService provide_NetworkService(OpenWeatherEndpoints openWeatherEndpoints, YoutubeEndpoints youtubeEndpoints) {
        return new NetworkService(openWeatherEndpoints, youtubeEndpoints);
    }
}

PerFragment.java

@Scope
@Retention(RUNTIME)
@interface PerFragment {
}

MainFragmentPresenterImpl.java

public class MainFragmentPresenterImpl implements MainFragmentPresenter {
    private final MainFragmentView _view;
    private NetworkService _service;

    @Inject MainFragmentPresenterImpl(MainFragmentView view, NetworkService networkService) {
        _view = view;
        _service = networkService;
    }
}

And when I try to inject the presenter in my fragment...

public class MainFragment extends Fragment implements MainFragmentView {

    public static final String TAG = "MainFragment";

    @Inject
    MainFragmentPresenter _presenter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DaggerMainFragmentComponent.builder()
                .applicationComponent(WeatherForecastApp.get(getContext()).getAppComponent())
                .mainFragmentPresenterModule(new MainFragmentPresenterModule(this))
                .build()
                .inject(this);
   }
}

All help is greatly appreciated! thanks.

Upvotes: 1

Views: 725

Answers (1)

aball
aball

Reputation: 56

You need to add a binding from MainFragmentPresenterImpl to MainFragmentPresenter. Dagger does not cast between types, so you need to do that explicitly with a @Binds method. @Binds is equivalent to a @Provides method that just returns its argument, but it's implemented more efficiently. Just change your module like this:

@Module
public abstract class MainFragmentPresenterModule {
    ...
    @Binds
    abstract MainFragmentPresenter(MainFragmentPresenterImpl impl);
}

Also, you can get rid of the @Provides method, _view field, and constructor in the module by using a @BindsInstance.

public interface MainFragmentComponent {
    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder mainFragmentView(MainFragmentView mainFragmentView);
    }
    void inject(MainFragment mainFragment);
}

Now, since your your module doesn't have a constructor, Dagger doesn't need to be provided an instance, so you can construct the component like this:

DaggerMainFragmentComponent.builder()
    .applicationComponent(WeatherForecastApp.get(getContext()).getAppComponent())
    .mainFragmentView(this)
    .build()
    .inject(this);

Upvotes: 1

Related Questions