user1341300 user1341300 - 3 months ago 20
Android Question

Presenter injection with Dagger 2

I just started using Dagger 2 and I found online thousands guides each one with a different implementation and I'm a bit confused now.
So basically this is what I wrote at the moment:

AppModule.java:

@Module
public class AppModule {

Application mApplication;

public AppModule(Application application) {
mApplication = application;
}

@Provides
@Singleton
Application providesApplication() {
return mApplication;
}
}


DataModule.java:

@Module
public class DataModule {

private static final String BASE_URL = "http://beta.fridgewizard.com:9001/api/";

@Provides
@Singleton
NetworkService provideNetworkService() {
return new NetworkService(BASE_URL);
}

@Provides
@Singleton
SharedPreferences provideSharedPreferences(Application app) {
return PreferenceManager.getDefaultSharedPreferences(app);
}
}


PrefsModel.java:

@Module(includes = DataModule.class)
public class PrefsModel {

@Provides
@Singleton
QueryPreferences provideQuery(SharedPreferences prefs) {
return new QueryPreferences(prefs);
}
}


AppComponent.java (I'm exposing QueryPreferences object since I need it in a presenter, hopefully is correct in this way):

@Singleton
@Component(modules = {AppModule.class, DataModule.class, PrefsModel.class})
public interface AppComponent {

void inject(HomeFragment homeFragment);

QueryPreferences preferences();
NetworkService networkService();
}


Then I have the FwApplication.java:

public class FwApplication extends Application {

private static final String TAG = "FwApplication";

private NetworkService mNetworkService;

private AppComponent mDataComponent;

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

buildComponentAndInject();
}

public static AppComponent component(Context context) {
return ((FwApplication) context.getApplicationContext()).mDataComponent;
}

public void buildComponentAndInject() {
mDataComponent = DaggerComponentInitializer.init(this);
}

public static final class DaggerComponentInitializer {
public static AppComponent init(FwApplication app) {
return DaggerAppComponent.builder()
.appModule(new AppModule(app))
.dataModule(new DataModule())
.build();
}
}
}


Finally I added another module for the presenters:

@Module
public class PresenterModule {

@Provides
Presenter<FwView> provideHomePresenter(NetworkService networkService) {
return new HomePresenterImpl(networkService);
}

@Provides
Presenter<FwView> provideSearchPresenter(NetworkService networkService) {
return new SearchPresenterImpl(networkService);
}

}


And the following component (which returns error because I cannot add a scoped dependencies here):

@Component(dependencies = AppComponent.class, modules = PresenterModule.class)
public interface PresenterComponent {

void inject(HomePresenterImpl presenter);
}


So, I have few questions that are not clear for me reading the documentation online:


  • How can I fix the error in the presenter component since it depends on NetworkService which is a singleton defined in the AppComponent?

  • I have an HomeFragment which should implement the HomePresenter with "new HomePresenter(networkService)" but now I don't know how to use the DI defined



EDIT - FIX:

HomeFragment.java:

public class HomeFragment extends Fragment {

private static final String TAG = "FW.HomeFragment";


@Inject
HomePresenterImpl mHomePresenter;

public static HomeFragment newInstance() {
return new HomeFragment();
}

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

FwApplication.component(getActivity()).inject(this);
}


Then I modified the presenter constructor in this way:

@Inject
public HomePresenterImpl(NetworkService networkService) {
mNetworkService = networkService;
mInteractor = new InteractorImpl(mNetworkService);
}


Then NetworkService is injected automatically.

I was wondering if it is correct in this way since I have to call for every fragment I have that needs a presenter constructed in the same way as the one above the following code:

FwApplication.component(getActivity()).inject(this);

Answer

You are mixing thing up. To provide your presenter, you should switch to something like the following:

Use constructor injection if possible. It will make things much easier

public class HomePresenterImpl {

    @Inject
    public HomePresenterImpl(NetworkService networkService) {
        // ...
    }

}

To provide the interface use this constructor injecetion and depend on the implementation

Presenter<FwView> provideHomePresenter(HomePresenterImpl homePresenter) {
    return homePresenter;
}

This way you don't have to call any constructors yourself. And to actually inject the presenter...

public class MyFragment extends Fragment {

    @Inject
    Presenter<FwView> mHomePresenter;

    public void onCreate(Bundle xxx) {
        // simplified. Add your modules / Singleton component
        PresenterComponent component = DaggerPresenterComponent.create().inject(this);
    }
}

This way you will inject the things. Please read this carefully and try to understand it. This will fix your major problems, you still can not provide 2 presenters of the same type from the same module (in the same scope)

// DON'T
@Provides
Presenter<FwView> provideHomePresenter(NetworkService networkService) { /**/ }

@Provides
Presenter<FwView> provideSearchPresenter(NetworkService networkService) { /**/ }

This will not work. You can not provide 2 objects of the same kind. They are indistinguishable. Have a look at @Qualifiers like @Named if you are sure this is the way you want to go.

Comments