使用 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>)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357

推荐阅读更多精彩内容