Dependency Injection with Dagger2,Fragment

2020-11-22 21:12:30

参考地址 Dependency Injection with Dagger2: Part 2

昨天建好了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:

  1. Declare which type (possibly qualified) is being provided — this is the return type

  2. Declare dependencies — these are the method parameters

  3. 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


  • 2018-11-28 09:55:53

    android radiogroup样式(设置切换背景与文字颜色)

    RadioButton(单选按钮)在Android开发中应用的非常广泛,比如一些选择项的时候,会用到单选按钮。它是一种单选框双状态的按钮,可以选择或不选择。在RadioButton没有被选中时,用户能够按下或点击来选中它。

  • 2018-12-01 00:27:12

    批量kill mysql processlist进程

    如果大批量的操作能够通过一系列的select语句产生,那么理论上就能对这些结果批量处理。 但是mysql并没用提供eval这样的对结果集进行分析操作的功能。所以只能现将select结果保存到临时文件中,然后再执行临时文件中的指令。

  • 2018-12-02 10:54:14

    HTTP长连接、短连接究竟是什么?

    HTTP的长连接和短连接本质上是TCP长连接和短连接。HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议。 IP协议主要解决网络路由和寻址问题,TCP协议主要解决如何在IP层之上可靠地传递数据包,使得网络上接收端收到发送端所发出的所有包,并且顺序与发送顺序一致。TCP协议是可靠的、面向连接的。

  • 2018-12-04 15:30:01

    如何在Mac OS X上安装 Ruby运行环境

    ​ 对于新入门的开发者,如何安装 Ruby和Ruby Gems 的运行环境可能会是个问题,本页主要介绍如何用一条靠谱的路子快速安装 Ruby 开发环境。 此安装方法同样适用于产品环境!

  • 2018-12-04 15:31:15

    iOS--Pod install && Pod update

    许多人在最初接触CocoaPods时认为pod install只是在第一次为项目设置CocoaPods时使用,之后都应该使用pod update.看起来是这样,但也不是(But that's not the case at all.)。 这篇文章的目的就是教你啥时候用pod install,啥时候用pod update

  • 2018-12-04 15:33:19

    CocoaPods安装和使用教程

    当你开发iOS应用时,会经常使用到很多第三方开源类库,比如JSONKit,AFNetWorking等等。可能某个类库又用到其他类库,所以要使用它,必须得另外下载其他类库,而其他类库又用到其他类库,“子子孙孙无穷尽也”,这也许是比较特殊的情况。总之小编的意思就是,手动一个个去下载所需类库十分麻烦。另外一种常见情况是,你项目中用到的类库有更新,你必须得重新下载新版本,重新加入到项目中,十分麻烦。如果能有什么工具能解决这些恼人的问题,那将“善莫大焉”。所以,你需要 CocoaPods。