How to inject into a java class that doesn't h

2019-06-01 05:11发布

问题:

Android Studio 2.2.2

I have a NewsListModelImp class which is the model in the MVP.

I want to inject my retrofit service into the model. However, as NewsListModelImp doesn't contain any reference to a context or activity I cannot call getApplication(). Which is what you would do if you were in a activity or fragment. I don't want to pass any context or activity in the constructor of NewsListModeImp as that would have to come from the presenter and I want to avoid any android stuff there.

public class NewsListModelImp implements NewsListModelContract {
    @Inject
    NYTimesSearchService mNYTimesSearchService;

    public NewsListModelImp() {
        ((NYTimesSearchApplication)getApplication()).getAppComponent().inject(this);
    }
}

My Application class

public class NYTimesSearchApplication extends Application {
    private AppComponent mAppComponent;

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

        /* Setup dependency injection */
        createAppComponent();
    }

    private void createAppComponent() {
        mAppComponent = DaggerAppComponent
                .builder()
                .retrofitModule(new RetrofitModule())
                .build();
    }

    public AppComponent getAppComponent() {
        return mAppComponent;
    }
}

My provides module

@Module
public class RetrofitModule {
    private Retrofit retrofit() {
        return new Retrofit
                .Builder()
                .baseUrl(Constants.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }

    @Provides
    @Singleton
    public NYTimesSearchService providesNYTimesSearch() {
        return retrofit().create(NYTimesSearchService.class);
    }
}

My Appcomponent

@Singleton
@Component(modules = {RetrofitModule.class})
public interface AppComponent {
    void inject(NewsListModelImp target);
}

Many thanks for any suggestions,

回答1:

Dagger-2 works reccurently. So if inside Activity (or Fragment) object is injected and it's constructor is properly annotated with @Inject annotation, the constructor's parameters will be injected too.

Suppose inside the application you would like to inject:

@Inject NyTimesPresenter presenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ((NYTimesSearchApplication) getApplication()).getAppComponent().inject(this);
}

The constructor of NyTimesPresenter must be annotated with @Inject:

public class NyTimesPresenter {

    NewsListModelImp newsListModel;

    @Inject
    public NyTimesPresenter(NewsListModelImp newsListModel) {
        this.newsListModel = newsListModel;
    }
}

NewsListModelImp constructor must also be annotated with @Inject:

public class NewsListModelImp implements NewsListModelContract {

    NYTimesSearchService mNYTimesSearchService;

    @Inject
    public NewsListModelImp(NYTimesSearchService nYTimesSearchService) {
        this.mNYTimesSearchService = nYTimesSearchService;
    }
}

Then everything will be injected properly.

Why the parameters should be passed to class as constructors' parameters? Such design pattern conforms SOLID principles. Object dependencies are injected into objects and not created inside it and such code is easily testable (in tests dependencies can be replaced ie. by Mock's)

EXTRA INFO:

It is possible to inject objects implementing specific interfaces. Such technique is described here. In your case NyTimesPresenter can have NewsListModelContract as it's dependency instead of NewsListModelImp. To do this add another module to your AppComponent:

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

AppComponent method to provide concrete class implementing interface should look like:

@Singleton
@Module
public abstract class AppModule {

    @Binds
    public abstract NewsListModelContract provideNewsListModelContract(NewsListModelImp newsListModelImp);
}

The implementation of NyTimesPresenter should change (just to replace concrete class with interface it implements):

public class NyTimesPresenter {

    NewsListModelContract newsListModel;

    @Inject
    public NyTimesPresenter(NewsListModelContract newsListModel) {
        this.newsListModel = newsListModel;
    }
}