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


  • 2020-02-20 18:35:21

    Vue加载组件、动态加载组件的几种方式

    组件是Vue.js最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码。在较高层面上,组件是自定义的元素,Vue.js的编译器为它添加特殊功能。在有些情况下,组件也可以是原生HTML元素的形式,以is特性扩展。

  • 2020-02-22 13:58:50

    深入理解 requestAnimationFrame

    在Web应用中,实现动画效果的方法比较多,Javascript 中可以通过定时器 setTimeout 来实现,css3 可以使用 transition 和 animation 来实现,html5 中的 canvas 也可以实现。除此之外,html5 还提供一个专门用于请求动画的API,那就是 requestAnimationFrame,顾名思义就是请求动画帧。 为了深入理解 requestAnimationFrame 背后的原理,我们首先需要了解一下与之相关的几个概念:

  • 2020-02-22 17:35:14

    使用tweenjs写一个发牌的demo

    移动端h5的三大抽奖方式:转盘,老虎机,翻纸牌。 前两样demo比较多,用户也审美疲劳了。这次微信公众号要抽奖,于是调研了下翻纸牌。

  • 2020-02-22 17:45:43

    tweenjs简单快速学习

    tweenjs 是使用 JavaScript 中的一个简单的补间动画库,支持数字、对象的属性和 CSS 样式属性的赋值。 tweenjs 以平滑的方式修改元素的属性值,需要传递给 tween 要修改的值、动画结束时的最终值和动画花费时间(duration),之后 tween 引擎就可以计算从开始动画点到结束动画点之间值,从而产生平滑的动画效果。

  • 2020-02-22 17:47:12

    easeJs入门学习教程

    createjs中包含以下四个部分: EaselJS:用于 Sprites、动画、向量和位图的绘制,创建 HTML5 Canvas 上的交互体验(包含多点触控) TweenJS:用于做动画效果 SoundJS:音频播放引擎 PreloadJS:网站资源预加载

  • 2020-02-22 17:54:59

    最新FullCalendar中文文档

    Fullcalendar是一款用来管理日程安排、工作计划的日历工具,它功能非常强大而且非常实用。而目前网络上没有很好的中文文档,为了更好的和大家分享Fullcalendar的相关使用,即日起计划将Fullcalendar的英文文档翻译成中文版。