昨天建好了Dagger2的環境,今天就把目前的程式用DI的方式來改寫,會比建置的過程輕鬆很多。
Inject to Fragment
RepoFragment中實例化了GithubViewModelFactory,我們將這邊改成用Inject的方式,首先將GithubViewModelFactory加入AppModule中:
@Moduleclass AppModule { @Provides @Singleton GithubService provideGithubService() { ... } @Provides @Singleton GithubViewModelFactory provideFactory() { return new GithubViewModelFactory(); } }
在BuildersModule中新增RepoFragment,方式與新增Activity相同:
@Modulepublic 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:
@Moduleclass 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的內容,詳細如下說明。
Constructor Injection
當我們的自訂的class很多時,Module就會就會變得比較肥,所以對自訂的class我們可以用constructor injection的方式將其加入DI框架中並讓Module保持簡潔。
Constructor injection的用法是在class的constructor上標註@Inject
將其加入Dagger的管理下,以剛剛的情境為例,我們要將GithubViewModelFactory和DataModel這兩個自訂的class加入Dagger,首先修改DataModel:
@Singletonpublic class DataModel { ... @Inject public DataModel() { } ... }
在DataModel建立constructor並標註@Inject
,這樣的概念就如同將DataModel加入AppModule一樣,DataModel已經列入Dagger的管理之下,其他class可以用@Inject
或是constructor parameter取得此DataModel。
修改GithubViewModelFactory:
@Singletonpublic 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:
@Singletonpublic 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的簡潔。
Multiple Module
當Module比較肥的時候,可以將其內容分散到多個Module,例如建立NetworkModule管理網路相關的class:
@Moduleclass 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:
@Modulepublic abstract class FragmentBuildersModule { @ContributesAndroidInjector abstract RepoFragment contributeRepoFragment(); }
將原本的BuildersModule重新命名成ActivityBuildersModule:
@Modulepublic 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