使用 dagger-android 注入 ViewModel

作者: 追风筝的boy | 来源:发表于2019-10-31 16:18 被阅读0次

    获取 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>)
    

    相关文章

      网友评论

        本文标题:使用 dagger-android 注入 ViewModel

        本文链接:https://www.haomeiwen.com/subject/dmytbctx.html