使用 dagger-android 注入 ViewModel

  • 以下內(nèi)容均為The Google I/O 2019 Android App中學(xué)習(xí)所得鼓拧,你可以直接跳轉(zhuǎn)到該鏈接進(jìn)行學(xué)習(xí)

獲取 ViewModel實(shí)例的一般做法

  • 首先會創(chuàng)建一個 MainViewModel 類,然后在類中定義一個繼承自ViewModelProvider.Factory接口的類,實(shí)現(xiàn) create接口,直接通過MainViewModel的構(gòu)造方法創(chuàng)建了一個實(shí)例
  • 然后在 Activity或者Fragment 中, 通過ViewModelProviders.of(this, MainViewModel.Factory()).get(MainViewModel::class.java)來獲取viewModel 的實(shí)例對象
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 類的實(shí)現(xiàn)都是相同的
  • 如果項(xiàng)目十分龐大的話吨岭,必然會產(chǎn)生巨量的樣板代碼(Boilerplate code),這給我們的維護(hù)會造成困難

使用 dagger-android 來減少 boilerplate

  • 首先可以先看一下使用Dagger如何減少樣板代碼的實(shí)現(xiàn)
  • 可以先從具體需要注入ViewModelMainActivity地方來看
    • 僅僅注入了一個ViewModelProvider.Factory接口的一個實(shí)例峦树,然后就可以通過相應(yīng)的方法來獲取 viewModel 的實(shí)例對象
    • 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的實(shí)例來作為依賴的提供者辣辫,而AppViewModelFactory的實(shí)例會通過其構(gòu)造方法來創(chuàng)建
  • 至此ViewModelProvider.Factory這個接口的注入已經(jīng)完善
  • 下面會詳細(xì)的描述AppViewModelFactory實(shí)例創(chuàng)建時,其構(gòu)造方法中所需依賴獲取的具體流程
@Module
@Suppress("UNUSED")
abstract class ViewModelModule {

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

  • 首先來看一下 ViewModelProvider.Factory實(shí)現(xiàn)類
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>>類型的實(shí)例
  • 該實(shí)例會通過MainActivityModule中通過@IntoMap標(biāo)注的方法來提供
    • 這里涉及到multibindings來提供魁巩,不太懂的同學(xué)可以先去學(xué)習(xí)一下
    • 包含該ModuleComponent會提供以下兩種Map類型的集合以供使用
      • Map<Class, ViewModel>
      • Map<Class, Provider<ViewModel>>
  • Dagger會通過@IntoMap創(chuàng)建的 Map<Class, Provider<ViewModel>>類型的實(shí)例來創(chuàng)建AppViewModelFactory實(shí)例急灭,來作為ViewModelProvider.Factory接口的依賴進(jìn)行注入
@Module
abstract class MainActivityModule {

    @Binds
    @IntoMap
    @ViewModelKey(MainViewModel::class)
    abstract fun viewModel(viewModel: MainViewModel): ViewModel
}
  • 以上的流程梳理:
    • 當(dāng)Activity或者Fragment需要一個ViewModelProvider.Factory實(shí)例的時候,根據(jù)ViewModelModule中定義的方法谷遂,會去尋找AppViewModelFactory實(shí)例作為返回值
    • AppViewModelFactory的創(chuàng)建需要依賴Map<Class<out ViewModel>, Provider<ViewModel>>這樣一個集合
    • 這個集合會由MainActivityModule@IntoMap標(biāo)注的方法來提供

如果你想在項(xiàng)目中集成可能需要用到的代碼

  • AppComonent 相關(guān)的類葬馋,只需要寫一次
@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
}
  • 每當(dāng)你創(chuàng)建一個新的 Activity需要注入ViewModel的時候(Fragment類似)
  • viewModelProvider是一個頂級函數(shù),可以抽到一個工具類中
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>)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市肾扰,隨后出現(xiàn)的幾起案子畴嘶,更是在濱河造成了極大的恐慌,老刑警劉巖集晚,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窗悯,死亡現(xiàn)場離奇詭異,居然都是意外死亡偷拔,警方通過查閱死者的電腦和手機(jī)蟀瞧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來条摸,“玉大人,你說我怎么就攤上這事铸屉《て眩” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵彻坛,是天一觀的道長顷啼。 經(jīng)常有香客問我,道長昌屉,這世上最難降的妖魔是什么钙蒙? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮间驮,結(jié)果婚禮上躬厌,老公的妹妹穿的比我還像新娘。我一直安慰自己竞帽,他們只是感情好扛施,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布鸿捧。 她就那樣靜靜地躺著,像睡著了一般疙渣。 火紅的嫁衣襯著肌膚如雪匙奴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天妄荔,我揣著相機(jī)與錄音泼菌,去河邊找鬼。 笑死啦租,一個胖子當(dāng)著我的面吹牛哗伯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播刷钢,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼笋颤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了内地?” 一聲冷哼從身側(cè)響起伴澄,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎阱缓,沒想到半個月后非凌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荆针,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年敞嗡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片航背。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡喉悴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出玖媚,到底是詐尸還是另有隱情箕肃,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布今魔,位于F島的核電站勺像,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏错森。R本人自食惡果不足惜吟宦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涩维。 院中可真熱鬧殃姓,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至宛篇,卻和暖如春娃磺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叫倍。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工偷卧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吆倦。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓听诸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蚕泽。 傳聞我的和親對象是個殘疾皇子晌梨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容