使用 dagger-android 注入 ViewModel

  • 以下内容均为The Google I/O 2019 Android App中学习所得,你可以直接跳转到该链接进行学习

获取 ViewModel实例的一般做法

  • 首先会创建一个 MainViewModel 类,然后在类中定义一个继承自ViewModelProvider.Factory接口的类,实现 create接口,直接通过MainViewModel的构造方法创建了一个实例
  • 然后在 Activity或者Fragment 中, 通过ViewModelProviders.of(this, MainViewModel.Factory()).get(MainViewModel::class.java)来获取viewModel 的实例对象
class MainViewModel : ViewModel() {

    // 这里的代码,在所有的 viewModel 中都需要在写一遍,就是所谓的`Boilerplate code`
    class Factory : ViewModelProvider.Factory {
        override fun  create(modelClass: Class): T {
            @Suppress("UNCHECKED_CAST")
            return MainViewModel() as T
        }
    }
}

class MainActivity : AppCompatActivity() {

    private lateinit var viewModel: MainViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel = ViewModelProviders.of(this, MainViewModel.Factory()).get(MainViewModel::class.java)
        setContentView(R.layout.activity_main)
    }
}
  • ViewModel.Factory 类的实现都是相同的
  • 如果项目十分庞大的话,必然会产生巨量的样板代码(Boilerplate code),这给我们的维护会造成困难

使用 dagger-android 来减少 boilerplate

  • 首先可以先看一下使用Dagger如何减少样板代码的实现
  • 可以先从具体需要注入ViewModelMainActivity地方来看
    • 仅仅注入了一个ViewModelProvider.Factory接口的一个实例,然后就可以通过相应的方法来获取 viewModel 的实例对象
    • MainViewModel中去掉了原来的Factory样板代码
class MainActivity : DaggerAppCompatActivity() {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory
    private lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = viewModelProvider(viewModelFactory)
        setContentView(R.layout.activity_main)
    }
}

inline fun  AppCompatActivity.viewModelProvider(provider: ViewModelProvider.Factory): T {
    return ViewModelProviders.of(this, provider).get(T::class.java)
}

class MainViewModel @Inject constructor(): ViewModel()
  • ViewModelProvider.Factory接口的依赖是由下面定义的Module来提供
  • 然后Dagger会去寻找AppViewModelFactory的实例来作为依赖的提供者,而AppViewModelFactory的实例会通过其构造方法来创建
  • 至此ViewModelProvider.Factory这个接口的注入已经完善
  • 下面会详细的描述AppViewModelFactory实例创建时,其构造方法中所需依赖获取的具体流程
@Module
@Suppress("UNUSED")
abstract class ViewModelModule {

    @Binds
    internal abstract fun bindViewModelFactory(factory: AppViewModelFactory): ViewModelProvider.Factory
}

  • 首先来看一下 ViewModelProvider.Factory实现类
class AppViewModelFactory @Inject constructor(
    private val creators:  Map,@JvmSuppressWildcards Provider>
) : ViewModelProvider.Factory {

    override fun  create(modelClass: Class): T {
        val find = creators.entries.find { modelClass.isAssignableFrom(it.key) }
        val creator = find?.value ?: throw IllegalArgumentException("unknown modelClass class $modelClass")
        return try {
            @Suppress("UNCHECKED_CAST")
            creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}
  • 该类需要一个Map>类型的实例
  • 该实例会通过MainActivityModule中通过@IntoMap标注的方法来提供
    • 这里涉及到multibindings来提供,不太懂的同学可以先去学习一下
    • 包含该ModuleComponent会提供以下两种Map类型的集合以供使用
      • Map
      • Map>
  • Dagger会通过@IntoMap创建的 Map>类型的实例来创建AppViewModelFactory实例,来作为ViewModelProvider.Factory接口的依赖进行注入
@Module
abstract class MainActivityModule {

    @Binds
    @IntoMap
    @ViewModelKey(MainViewModel::class)
    abstract fun viewModel(viewModel: MainViewModel): ViewModel
}
  • 以上的流程梳理:
    • Activity或者Fragment需要一个ViewModelProvider.Factory实例的时候,根据ViewModelModule中定义的方法,会去寻找AppViewModelFactory实例作为返回值
    • AppViewModelFactory的创建需要依赖Map, Provider>这样一个集合
    • 这个集合会由MainActivityModule@IntoMap标注的方法来提供

如果你想在项目中集成可能需要用到的代码

  • AppComonent 相关的类,只需要写一次
@Singleton
@Component(
    modules = [
        AndroidInjectionModule::class,
        AppModule::class,
        ActivityBindingModule::class,
        ViewModelModule::class
    ]
)
interface AppComponent : AndroidInjector {

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance application: MainApplication): AppComponent
    }
}

@Module
abstract class AppModule

@Module
abstract class ActivityBindingModule {

    @ActivityScope
    @ContributesAndroidInjector(modules = [MainActivityModule::class])
    internal abstract fun mainActivity(): MainActivity
}

@Module
@Suppress("UNUSED")
abstract class ViewModelModule {

    @Binds
    internal abstract fun bindViewModelFactory(factory: AppViewModelFactory): ViewModelProvider.Factory
}
  • 每当你创建一个新的 Activity需要注入ViewModel的时候(Fragment类似)
  • viewModelProvider是一个顶级函数,可以抽到一个工具类中
inline fun  AppCompatActivity.viewModelProvider(provider: ViewModelProvider.Factory): T {
    return ViewModelProviders.of(this, provider).get(T::class.java)
}
@Module
abstract class NewActivityModule {

    @Binds
    @IntoMap
    @ViewModelKey(NewViewModel::class)
    abstract fun viewModel(viewModel: NewViewModel): ViewModel
}

class NewActivity() {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory
    private lateinit var viewModel: MainViewModel
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = viewModelProvider(viewModelFactory)
        ...
    }
}

class NewViewModel : ViewModel()

  • 注解
@Target(
    AnnotationTarget.FUNCTION,
    AnnotationTarget.PROPERTY_GETTER,
    AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass)

你可能感兴趣的:(使用 dagger-android 注入 ViewModel)