使用 dagger-android 注入 ViewModel

获取 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 <T : ViewModel?> create(modelClass: Class<T>): 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 <reified T: ViewModel> 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<Class<out ViewModel>,@JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): 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<Class<out ViewModel, Provider<ViewModel>>类型的实例
  • 该实例会通过MainActivityModule中通过@IntoMap标注的方法来提供
    • 这里涉及到multibindings来提供,不太懂的同学可以先去学习一下
    • 包含该ModuleComponent会提供以下两种Map类型的集合以供使用
      • Map<Class, ViewModel>
      • Map<Class, Provider<ViewModel>>
  • Dagger会通过@IntoMap创建的 Map<Class, Provider<ViewModel>>类型的实例来创建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<Class<out ViewModel>, Provider<ViewModel>>这样一个集合
    • 这个集合会由MainActivityModule@IntoMap标注的方法来提供

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

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

    @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 <reified T: ViewModel> 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<out ViewModel>)
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容