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


  • 2019-06-13 10:09:51

    java(Android)跨Module调用对应类方法需求解决方案

    在开发组件化项目中,遇到一个这样的问题,两个不同的Module相互之间没有任何直接依赖关系,现在需求是需要在Module_A中调用Module_B中的某个类的方法,以下为解决此问题的方法;

  • 2019-06-13 11:31:32

    Android Studio接入ARouter以及简单使用

    你可能会说如果我的A module依赖了 B module,那么只要在B里面配置是不是就可以了?绝对不行!无论module之间关系如何,必须要同样配置!包括主项目和library项目!

  • 2019-06-13 11:34:24

    Android组件化方案

    1为什么要项目组件化 2如何组件化 3组件化实施流程

  • 2019-06-23 09:22:33

    Android 拍摄(横\竖屏)视频的懒人之路

    一般用在多媒体录制上面,当然如果你只是简单的想录制音频,用它最合适不过,不过如果你想更多样化的录制这里推荐《Android MP3录制,波形显示,音频权限兼容与播放》。今天的主题是录制视频,用的还是老式通用的Camera,不是新的camera2(这就尴尬了.....((/- -)/),反正个人秉承能用是王道的做法(懒)。之前也尝试过FFMPEG的录制合成音频,大小和效果也不错,只是有时候的兼容性确实有些问题,最主要还是资料不多,不好改啊 ̄へ ̄(懒)。

  • 2019-06-23 09:24:59

    仿抖音系列

    最近公司在做个短视频的项目,其中借鉴了很多抖音的设计,其中就有抖音的上下滑切换视频。