背景


一个标准的Android的MVVM架构是分层规划实现的,各层之间有清晰的依赖关系。借助Dagger2完成MVVM各层所需的依赖注入,可以让项目结构变得更清爽和更可维护。

虽然Dagger2有一定使用门槛,但是MVVM的项目结构大都相同,实现一个Dagger2+Retrofit2(+OkHttp3)+ViewModel 的最小构成实践,对其他类似项目具有一定参考意义。


构成




1. gradle


final DAGGER_VERSION = '2.25.3'    final RETROFIT_VERSION = '2.7.0'    // retrofit    implementation "com.squareup.retrofit2:adapter-rxjava2:$RETROFIT_VERSION"    implementation "com.squareup.retrofit2:converter-moshi:$RETROFIT_VERSION"    implementation "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION"    // dagger2    implementation "com.google.dagger:dagger:$DAGGER_VERSION"    implementation "com.google.dagger:dagger-android:$DAGGER_VERSION"    implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"    kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"    kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"    implementation "com.squareup.okhttp3:logging-interceptor:4.2.2"    // Android Architecture Components    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0-rc03"    implementation "androidx.lifecycle:lifecycle-livedata:2.2.0-rc03"

各种依赖库的配置:Dagger2Retrofit2AAC,以及J神推荐的Json库Moshi


2. Application


AppModule.kt

@Moduleabstract class AppModule {    @Binds    abstract fun provideContext(application: App): Context}

AppComponent.kt

@Singleton@Component(    modules = [        AndroidInjectionModule::class,        AppModule::class,        MainActivityBuilder::class])interface AppComponent : AndroidInjector<App> {    @Component.Factory    interface Factory {        fun create(@BindsInstance app: App): AppComponent    }}
  • Component使用@Component.Factory 替代 @Comonent.Builder,参考Factory与Builder比较
  • Component中添加AndroidInjectionModule的依赖,可以避免AndroidSupportInjectionModule的额外依赖,减少DispatchingAndroidInjector的依赖关系

App.kt

class App : DaggerApplication() {    override fun applicationInjector(): AndroidInjector<out DaggerApplication> {        return DaggerAppComponent.factory()            .create(this)    }}

App继承DaggerApplication,简化代码,无需再在onCreate中进行inject

3. Activity


MainActivityBuilder.kt

@Moduleabstract class MainActivityBuilder {    @ActivityScope    @ContributesAndroidInjector    abstract fun bindMainActivity(): MainActivity}

在Activity的Module中限定@ActivityScope

MainActivity.kt

class MainActivity : DaggerAppCompatActivity()

MainActivity继承DaggerAppCompatActivity,简化代码,无需再在DaggerAppCompatActivity中调用AndroidInjection.inject(this)

到此为止Activity已经被注入到AppComponent,可以在运行时提供使用

4. Fragment


MainFragmentModule.kt

@Moduleinternal abstract class MainFragmentModule {    @ContributesAndroidInjector    abstract fun provideMainFragment(): MainFragment}

MainActivityBuilder.kt

@Moduleabstract class MainActivityBuilder {    @ActivityScope    @ContributesAndroidInjector(modules = [MainFragmentModule::class]) // 追加    abstract fun bindMainActivity(): MainActivity}

MainFragment继承DaggerFragment,可以自动实现HasAndroidInjector,而且无需AndroidSupportInjection.inject(this)

MainFragment.kt

class MainFragment : DaggerFragment() {

5. Retrofit


Api.kt

interface Api

ApiModule.kt

使用Moshi进行Json解析

@Moduleclass ApiModule {    companion object {        const val API_READ_TIMEOUT: Long = 10        const val API_CONNECT_TIMEOUT: Long = 10    }    @Provides    @Singleton    fun provideOkhttpClient(): OkHttpClient {        val logInterceptor = HttpLoggingInterceptor()        logInterceptor.level = HttpLoggingInterceptor.Level.BODY        return OkHttpClient.Builder()            .addInterceptor {                val httpUrl = it.request().url                val requestBuilder = it.request().newBuilder().url(httpUrl)                it.proceed(requestBuilder.build())            }            .addInterceptor(logInterceptor)            .readTimeout(API_READ_TIMEOUT, TimeUnit.SECONDS)            .connectTimeout(API_CONNECT_TIMEOUT, TimeUnit.SECONDS)            .build()    }    @Provides    @Singleton    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {        val moshi = Moshi.Builder()            .add(KotlinJsonAdapterFactory())            .build()        return Retrofit.Builder()            .client(okHttpClient)            .baseUrl("https://api.github.com/")            .addConverterFactory(MoshiConverterFactory.create(moshi))            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())            .build()    }    @Provides    @Singleton    fun provideAPI(retrofit: Retrofit): Api {        return retrofit.create(Api::class.java)    }}

AppComponent

更新AppComponent,在其中追加ApiModule

@Singleton@Component(    modules = [        AndroidInjectionModule::class,        AppModule::class,        MainActivityBuilder::class,        ApiModule::class    // 追加    ])interface AppComponent : AndroidInjector<App> {    @Component.Factory    interface Factory {        fun create(@BindsInstance app: App,                   @BindsInstance apiModule: ApiModule): AppComponent // 追加    }}

App.kt

class App : DaggerApplication() {    override fun applicationInjector(): AndroidInjector<out DaggerApplication> {        return DaggerAppComponent.factory()            .create(this, ApiModule()) //  追加ApiModule()    }}

6. ViewModel


ViewModelFactory.kt

为了能够为ViewModel动态传入参数,我们定义一个工厂

class ViewModelFactory @Inject constructor(    private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>) : ViewModelProvider.Factory {    override fun <T: ViewModel> create(modelClass: Class<T>): T {        var creator: Provider<out ViewModel>? = creators[modelClass]        if (creator == null) {            for ((key, value) in creators) {                if (modelClass.isAssignableFrom(key)) {                    creator = value                    break                }            }        }        if (creator == null) {            throw IllegalArgumentException("unknown model class " + modelClass)        }        try {            @Suppress("UNCHECKED_CAST")            return creator.get() as T        } catch (e: Exception) {            throw RuntimeException(e)        }    }}

ViewModelModule.kt

@MustBeDocumented@Target(        AnnotationTarget.FUNCTION,        AnnotationTarget.PROPERTY_GETTER,        AnnotationTarget.PROPERTY_SETTER)@Retention(AnnotationRetention.RUNTIME)@MapKey internal annotation class ViewModelKey(val value: KClass<out ViewModel>)

ViewModelModule.kt

@Moduleinterface ViewModelModule {    @Binds    fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory}

MainViewModel.kt

创建MainFragment中需要使用的MainViewModel

class MainViewModel @Inject constructor(private val useCase: MainUseCase): ViewModel() {}

因为MainViewModel中需要使用UseCase,通过构造函数注入。

MainUseCase.kt

MainUseCase中需要注入MainRepository`

class MainUseCase @Inject constructor(private val repository: MainRepository) {}

MainRepository.kt

MainRepository需要注入Api

class MainRepository @Inject constructor(private val api: Api) {}

MainFragmentModule.kt

更新MainFragmentModule,在MainFragment的Scope中(添加@FragmentScope)追加MainViewModel,以供其运行时取用。

@Moduleinternal abstract class MainFragmentModule {    @ContributesAndroidInjector    @FragmentScope    abstract fun provideMainFragment(): MainFragment    // 追加    @Binds    @IntoMap    @ViewModelKey(MainViewModel::class)    @FragmentScope    internal abstract fun bindMainViewModel(viewModel: MainViewModel): ViewModel}

MainFragment.kt

最后,在MainFragment中注入ViewModelFactory,运行时通过其创建MainViewModel

class MainFragment : DaggerFragment() {    @Inject    lateinit var viewModelFactory: ViewModelFactory    lateinit var viewModel: MainViewModel    override fun onCreateView(        inflater: LayoutInflater, container: ViewGroup?,        savedInstanceState: Bundle?    ): View? {        // Inflate the layout for this fragment        viewModel = ViewModelProvider(this, viewModelFactory).get(MainViewModel::class.java)        return inflater.inflate(R.layout.fragment_main, container, false)    }

Build完成后,所以的Dagger代码生成,依赖注入关系成功建立。


PS1: UseCase、Repository等的接口依赖


上面例子中UseCaseRepository是直接被注入到所需的对象中,但是实际项目中它们可能是以接口形式被依赖的,例如ViewModel依赖MainUserCase接口,而非MainUseCaseImpl

interface MainUseCase {    fun hoge()}class MainUseCaseImpl @Inject constructor(private val repository: MainRepository): MainUseCase {    override fun hoge() {         // 。。。    }}

此时可以再增加一个Module,将依赖的实现和接口分离

MainModule.kt

@Moduleinternal object MainModule {    @Singleton    @Provides    @JvmStatic    fun provideMainRepository(api: Api): MainRepository =        MainRepositoryImpl(api)    @Singleton    @Provides    @JvmStatic    fun provideMainUseCase(repository: MainRepository): MainUseCase =        MainUseCaseImpl(repository)}

AppComponent.kt

@Singleton@Component(    modules = [        AndroidInjectionModule::class,        AppModule::class,        ApiModule::class,        MainActivityBuilder::class,        MainModule::class // 追加    ])

PS2:ViewModel的实例化


上面代码中使用lateinit var声明ViewModel,然后通过ViewModelProvider运行时创建ViewModel。
Android-KTX中提供了最新的功能,可以帮助我们优化这种写法

implementation 'androidx.fragment:fragment-ktx:1.2.0-rc04' 

MainFrgment.kt

可以通过下面方式使用默认的factory创建ViewModel:

private val viewModel: MainViewModel by viewModels()

当想例子中那样需要使用自定义factory时:

private val viewModel: MainViewModel by viewModels { viewModelFactory }

一行代码搞定,减少了不必要的模板代码。

最后


以上例子介绍了如何使用Dagger创建一个最小构成的MVVVM项目,具体的一些Dagger注解的使用没有详细展开,有需要可以查阅Dagger官网,官网的文档还是很详细的。

Dagger起步很难,实际项目中大多是像上面这样提供一些脚手架代码,然后后面照猫画虎的添加Module或者Component。正因为Dagger起步难,所以写了这篇文章帮大家快速上手,在项目中一旦用起来就会发现Dagger的功能确实强大,不是其他DI框架所能比拟的,建议大家勇敢尝试。

更多相关文章

  1. android上执行UI交互的junit方法
  2. Android(安卓)依赖方式的变化
  3. Android之WebView安全
  4. 献给android原生应用层开发初学者技术架构选型和整合的方案思路(
  5. Android可能出现的SQL注入以及防范
  6. Android(安卓)解决依赖冲突问题
  7. 【Android】lib依赖
  8. Androids中的System.loadLibrary对于依赖so的加载分析
  9. Android(安卓)Studio 添加arr为依赖

随机推荐

  1. Android(安卓)Studio实现本地视频播放
  2. TabLayout用法,android顶部导航栏,android
  3. Android(安卓)开发技术周报 Issue#279
  4. 使用Python开发Android应用程序:第一节 在
  5. portrait表示纵向,landscape表示横向
  6. Android高效加载大图、多图解决方案,有效
  7. 【Android市场】提交应用的一点经验分享
  8. android使用全局变量的两种方法
  9. Android实现数据存储技术
  10. Android常用三方库收集