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


  • 2021-02-11 15:53:08

    node缓存框架memory-cache

    无论是在 desktop, mobile or web哪一方面,Cache都常被我们用来提升程序性能。当处理web应用程序的时候,虽然可以使用当前所有浏览器都支持的响应头来进行客户端缓存,从而提升页面加载效率。但当一个内容非常繁杂的页面需要2s来进行HTML输出的时候,即使启用客户端缓存该页面,服务器仍然需要针对每一个来访用户进行页面渲染。想想一个大型的新闻门户网站首页,难道他们要针对每一个用户一遍又一遍地处理HTML吗?

  • 2021-02-19 16:46:35

    window安装composer

    Composer 是 PHP 的一个依赖管理工具。它允许你申明项目所依赖的代码库,它会在你的项目中为你安装他们。

  • 2021-02-21 22:52:12

    php去除字符串中的HTML标签

    php自带的函数可以去除/删除字符串中的HTML标签/代码。 strip_tags(string,allow):函数剥去 HTML、XML 以及 PHP 的标签。 参数:string,必填,规定要检查的字符串;allow,选填,规定允许存在的标签,这些标签不会被删除。

  • 2021-03-02 16:00:30

    git pull时的filename too long的错误

    这是因为git在windowa下的文件名长度最大是260,(git在Linux下最大支持4096长度的文件名),可以通过输入以下命令解决:

  • 2021-03-05 13:18:03

    mjml教程详解

    mjml如何快速编写响应式电子邮件

  • 2021-03-15 10:34:55

    Sass函数:Sass Maps的函数-map-get($map,$key)

    map-get($map,$key) 函数的作用是根据 $key 参数,返回 $key 在 $map 中对应的 value 值。如果 $key 不存在 $map中,将返回 null 值。此函数包括两个参数: