一个标准的Android的MVVM架构是分层规划实现的,各层之间有清晰的依赖关系。借助Dagger2
完成MVVM各层所需的依赖注入,可以让项目结构变得更清爽和更可维护。
虽然Dagger2有一定使用门槛,但是MVVM的项目结构大都相同,实现一个Dagger2
+Retrofit2(+OkHttp3)
+ViewModel
的最小构成实践,对其他类似项目具有一定参考意义。
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"
各种依赖库的配置:Dagger2
、Retrofit2
、AAC
,以及J神推荐的Json库Moshi
@Module
abstract class AppModule {
@Binds
abstract fun provideContext(application: App): Context
}
@Singleton
@Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
MainActivityBuilder::class]
)
interface AppComponent : AndroidInjector<App> {
@Component.Factory
interface Factory {
fun create(@BindsInstance app: App): AppComponent
}
}
@Component.Factory
替代 @Comonent.Builder
,参考Factory与Builder比较class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.factory()
.create(this)
}
}
App继承DaggerApplication
,简化代码,无需再在onCreate
中进行inject
@Module
abstract class MainActivityBuilder {
@ActivityScope
@ContributesAndroidInjector
abstract fun bindMainActivity(): MainActivity
}
在Activity的Module中限定@ActivityScope
class MainActivity : DaggerAppCompatActivity()
MainActivity继承DaggerAppCompatActivity
,简化代码,无需再在DaggerAppCompatActivity中调用AndroidInjection.inject(this)
到此为止Activity已经被注入到AppComponent,可以在运行时提供使用
@Module
internal abstract class MainFragmentModule {
@ContributesAndroidInjector
abstract fun provideMainFragment(): MainFragment
}
@Module
abstract class MainActivityBuilder {
@ActivityScope
@ContributesAndroidInjector(modules = [MainFragmentModule::class]) // 追加
abstract fun bindMainActivity(): MainActivity
}
MainFragment继承DaggerFragment,可以自动实现HasAndroidInjector
,而且无需AndroidSupportInjection.inject(this)
。
class MainFragment : DaggerFragment() {
interface Api
使用Moshi进行Json解析
@Module
class 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
,在其中追加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 // 追加
}
}
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.factory()
.create(this, ApiModule()) // 追加ApiModule()
}
}
为了能够为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)
}
}
}
@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
@Module
interface ViewModelModule {
@Binds
fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
创建MainFragment
中需要使用的MainViewModel
class MainViewModel @Inject constructor(private val useCase: MainUseCase): ViewModel() {
}
因为MainViewModel
中需要使用UseCase
,通过构造函数注入。
MainUseCase中需要注入
MainRepository`
class MainUseCase @Inject constructor(private val repository: MainRepository) {
}
MainRepository
需要注入Api
class MainRepository @Inject constructor(private val api: Api) {
}
更新MainFragmentModule
,在MainFragment
的Scope中(添加@FragmentScope
)追加MainViewModel
,以供其运行时取用。
@Module
internal abstract class MainFragmentModule {
@ContributesAndroidInjector
@FragmentScope
abstract fun provideMainFragment(): MainFragment
// 追加
@Binds
@IntoMap
@ViewModelKey(MainViewModel::class)
@FragmentScope
internal abstract fun bindMainViewModel(viewModel: MainViewModel): ViewModel
}
最后,在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代码生成,依赖注入关系成功建立。
上面例子中UseCase
、Repository
是直接被注入到所需的对象中,但是实际项目中它们可能是以接口形式被依赖的,例如ViewModel依赖MainUserCase
接口,而非MainUseCaseImpl
interface MainUseCase {
fun hoge()
}
class MainUseCaseImpl @Inject constructor(private val repository: MainRepository): MainUseCase {
override fun hoge() {
// 。。。
}
}
此时可以再增加一个Module,将依赖的实现和接口分离
@Module
internal object MainModule {
@Singleton
@Provides
@JvmStatic
fun provideMainRepository(api: Api): MainRepository =
MainRepositoryImpl(api)
@Singleton
@Provides
@JvmStatic
fun provideMainUseCase(repository: MainRepository): MainUseCase =
MainUseCaseImpl(repository)
}
@Singleton
@Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
ApiModule::class,
MainActivityBuilder::class,
MainModule::class // 追加
]
)
上面代码中使用lateinit var
声明ViewModel,然后通过ViewModelProvider
运行时创建ViewModel。
Android-KTX中提供了最新的功能,可以帮助我们优化这种写法
implementation 'androidx.fragment:fragment-ktx:1.2.0-rc04'
可以通过下面方式使用默认的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框架所能比拟的,建议大家勇敢尝试。