昨天建好了Dagger2的環境,今天就把目前的程式用DI的方式來改寫,會比建置的過程輕鬆很多。
RepoFragment中實例化了GithubViewModelFactory,我們將這邊改成用Inject的方式,首先將GithubViewModelFactory加入AppModule中:
@Module
class AppModule {
@Provides
@Singleton
GithubService provideGithubService() {
...
}
@Provides
@Singleton
GithubViewModelFactory provideFactory() {
return new GithubViewModelFactory();
}
}
在BuildersModule中新增RepoFragment,方式與新增Activity相同:
@Module
public abstract class BuildersModule {
@ContributesAndroidInjector
abstract MainActivity contributeMainActivity();
@ContributesAndroidInjector
abstract RepoFragment contributeRepoFragment();
}
接著就是Inject了,而要在Fragment中使用@Inject
的話需先在其Parent Activity加入HasSupportFragmentInjector:
public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {
@Inject
DispatchingAndroidInjector<Fragment> fragmentDispatchingAndroidInjector;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
...
}
@Override
public AndroidInjector<Fragment> supportFragmentInjector() {
return fragmentDispatchingAndroidInjector;
}
}
之後就可以在Fragment中使用@Inject
:
public class RepoFragment extends Fragment {
...
@Inject
GithubViewModelFactory factory;
...
@Override
public void onAttach(Context context) {
AndroidSupportInjection.inject(this);
super.onAttach(context);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
...
}
...
}
在onAttach
中呼叫AndroidSupportInjection.inject(this)
,就完成對Fragment Inject的操作了。
不過,這其中還有個不夠乾淨的地方,就是GithubViewModelFactory在內部實例化了DataModel:
public class GithubViewModelFactory implements ViewModelProvider.Factory {
private DataModel dataModel;
public GithubViewModelFactory() {
this.dataModel = new DataModel();
}
...
}
顯然這違反了IoC原則,我們應該用Inject的方式將DataModel加入GithubViewModelFactory才對。一種方式我們可以修改AppModule:
@Module
class AppModule {
...
@Provides
@Singleton
DataModel provideDataModel() {
return new DataModel();
}
@Provides
@Singleton
GithubViewModelFactory provideFactory(DataModel dataModel) {
return new GithubViewModelFactory(dataModel);
}
}
標註@Provides
的method若有parameter的話,Dagger會找出其擁有的該型態物件來使用。我們在Module內新增了DataModel將其列入Dagger的管理下,接著在provideFactory()
增加parameter變成provideFactory(DataModel dataModel)
,Dagger就會找出其管理的DataModel給provideFactory使用。
補充一下官方文件Q&A的@Provides
說明:
@Provides, the most common construct for configuring a binding, serves three functions:
- Declare which type (possibly qualified) is being provided — this is the return type
- Declare dependencies — these are the method parameters
- Provide an implementation for exactly how the instance is provided — this is the method body
除了上面的方式外,對於自訂的class還有另一種方式constructor injection可以使用,讓我們不用修改Module的內容,詳細如下說明。
當我們的自訂的class很多時,Module就會就會變得比較肥,所以對自訂的class我們可以用constructor injection的方式將其加入DI框架中並讓Module保持簡潔。
Constructor injection的用法是在class的constructor上標註@Inject
將其加入Dagger的管理下,以剛剛的情境為例,我們要將GithubViewModelFactory和DataModel這兩個自訂的class加入Dagger,首先修改DataModel:
@Singleton
public class DataModel {
...
@Inject
public DataModel() {
}
...
}
在DataModel建立constructor並標註@Inject
,這樣的概念就如同將DataModel加入AppModule一樣,DataModel已經列入Dagger的管理之下,其他class可以用@Inject
或是constructor parameter取得此DataModel。
修改GithubViewModelFactory:
@Singleton
public class GithubViewModelFactory implements ViewModelProvider.Factory {
private DataModel dataModel;
@Inject
public GithubViewModelFactory(DataModel dataModel) {
this.dataModel = dataModel;
}
...
}
在constructor上也標註@Inject
,將GithubViewModelFactory列入Dagger的管理中,就可以用parameter取得DataModel了。
用constructor injection或在Module中標註@Provides
都一樣在Dagger的管理之下,所以可以互相取用,例如我們的DataModel可以用parameter取得在AppModule內的GithubService:
@Singleton
public class DataModel {
private GithubService githubService;
@Inject
public DataModel(GithubService githubService) {
this.githubService = githubService;
}
...
}
以上就是constructor injection的用法,而怎麼決定要用constructor injection或是加入Module中用@Provides
呢?一般來說我習慣將自訂的class用constructor injection來處理,只有像Retrofit那種外部library無法取得constructor的才加入Module中,以保持Module的簡潔。
當Module比較肥的時候,可以將其內容分散到多個Module,例如建立NetworkModule管理網路相關的class:
@Module
class NetworkModule {
@Provides
@Singleton
OkHttpClient provideOkHttpClient() {
return new OkHttpClient().newBuilder()
...
}
@Provides
@Singleton
GithubService provideGithubService(OkHttpClient okHttpClient) {
return new Retrofit.Builder()
...
}
}
接著在Component中加入此Module就可以了:
@Singleton
@Component(modules = {
AndroidSupportInjectionModule.class,
...
NetworkModule.class})
public interface AppComponent {
...
}
若是Activity/Fragment比較多的話,也可以拆分BuildersModule,例如將Fragment的部分拆出來成為FragmentBuildersModule:
@Module
public abstract class FragmentBuildersModule {
@ContributesAndroidInjector
abstract RepoFragment contributeRepoFragment();
}
將原本的BuildersModule重新命名成ActivityBuildersModule:
@Module
public abstract class ActivityBuildersModule {
@ContributesAndroidInjector(modules = FragmentBuildersModule.class)
abstract MainActivity contributeMainActivity();
}
因為MainActivity中會建立RepoFragment所以要加上(modules = FragmentBuildersModule.class),若之後別的Activity沒有用到FragmentBuildersModule中的Fragment就可以不用這段。
這兩天利用Dagger建立了基本的DI框架,明天會依照GithubBrowserSample學習怎麼進一步使用Dagger讓程式更為簡潔,
GitHub source code:
https://github.com/IvanBean/ITBon2018/tree/day10-dagger-constructor-injection