這是本系列的第四篇文章,尚未看過前面三篇的讀者能夠先看看:java
【譯】使用Kotlin從零開始寫一個現代Android 項目-Part1android
【譯】使用Kotlin從零開始寫一個現代Android 項目-Part2git
【譯】使用Kotlin從零開始寫一個現代Android 項目-Part3設計模式
正文開始!app
讓咱們先看看GitRepoRepository
類:ide
class GitRepoRepository(private val netManager: NetManager) { private val localDataSource = GitRepoLocalDataSource() private val remoteDataSource = GitRepoRemoteDataSource() fun getRepositories(): Observable<ArrayList<Repository>> { ... } }
咱們能夠說GitRepoRepository
依賴三個對象,分別是netManager
、localDataSource
和remoteDataSource
。經過構造函數提供netManager
時,數據源在GitRepoRepository中被初始化。換句話說,咱們將netManager注入到GitRepoRepository
。函數
依賴注入是一個很是簡單的概念:你須要什麼,其餘人就給你提供什麼。post
讓咱們看看,咱們在哪裏構造GitRepoRepository類(Mac上用cmd + B
,Windows上用alt + B
):測試
如你所見,GitRepoRepository類在MainViewModel中被構造,NetManager也是在這兒被構造,是否也應該將它們注入ViewModel?是的。應該將GitRepoRepository實例提供給ViewModel,由於GitRepoRepository能夠在其餘ViewModel中使用。ui
另外一方面,咱們肯定整個應用程序僅應建立一個NetManager實例。讓咱們經過構造函數提供它。咱們指望有這樣的東西:
class MainViewModel(private var gitRepoRepository: GitRepoRepository) : ViewModel() { ... }
請記住,咱們沒有在MainActivity中建立MainViewModel。咱們從ViewModelProviders
來得到它:
class MainActivity : AppCompatActivity(), RepositoryRecyclerViewAdapter.OnItemClickListener { ... override fun onCreate(savedInstanceState: Bundle?) { ... val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) ... } ... }
如前所述,ViewModelProvider將建立新的ViewModel或返回現有的ViewModel。如今,咱們必須將GitRepoRepository做爲參數。該怎麼作?
咱們須要爲MainViewModel設置特殊的工廠(Factory)類,由於咱們不能使用標準的類:
class MainViewModelFactory(private val repository: GitRepoRepository) : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(MainViewModel::class.java)) { return MainViewModel(repository) as T } throw IllegalArgumentException("Unknown ViewModel Class") } }
所以,如今咱們能夠在構造它時,設置參數,
class MainActivity : AppCompatActivity(), RepositoryRecyclerViewAdapter.OnItemClickListener { .... override fun onCreate(savedInstanceState: Bundle?) { ... val repository = GitRepoRepository(NetManager(applicationContext)) val mainViewModelFactory = MainViewModelFactory(repository) val viewModel = ViewModelProviders.of(this, mainViewModelFactory) .get(MainViewModel::class.java) ... } ... }
等等!咱們仍是沒有解決問題,咱們真的應該在MainActivity中建立一個MainViewModelFactory
實例嗎?不因該的,這裏應該使用依賴注入來解決。
讓咱們建立一個Injection類,它具備將提供所需實例的方法:
object Injection { fun provideMainViewModelFactory(context: Context) : MainViewModelFactory{ val netManager = NetManager(context) val repository = GitRepoRepository(netManager) return MainViewModelFactory(repository) } }
如今,咱們能夠將其今後類注入MainActivity.kt
:
class MainActivity : AppCompatActivity(), RepositoryRecyclerViewAdapter.OnItemClickListener { private lateinit var mainViewModelFactory: MainViewModelFactory override fun onCreate(savedInstanceState: Bundle?) { ... mainViewModelFactory = Injection.provideMainViewModelFactory(applicationContext) val viewModel = ViewModelProviders.of(this, mainViewModelFactory) .get(MainViewModel::class.java) ... } ... }
所以,如今咱們的Activity不知道來自應用程序數據層的repositories
。這樣的代碼組織對咱們有很大幫助,尤爲是在測試應用程序方面。這樣,咱們將UI邏輯與業務邏輯分開。
咱們能夠在Injection.kt
中應用更多的依賴注入概念:
object Injection { private fun provideNetManager(context: Context) : NetManager { return NetManager(context) } private fun gitRepoRepository(netManager: NetManager) :GitRepoRepository { return GitRepoRepository(netManager) } fun provideMainViewModelFactory(context: Context): MainViewModelFactory { val netManager = provideNetManager(context) val repository = gitRepoRepository(netManager) return MainViewModelFactory(repository) } }
如今,每一個類都有獲取它們實例的方法了,若是你仔細看,你會發現,全部的這些方法在咱們調用它們時,都會返回一個新的實例,真的應該這樣?每當咱們某個Repository類中須要時,都要建立NetManager的新實例?固然不是,每一個應用程序只須要一個NetManager實例。能夠說NetManager應該是單例。
在軟件工程中,單例模式是一種將類的實例化限制爲一個對象的軟件設計模式。
讓咱們實現它:
object Injection { private var NET_MANAGER: NetManager? = null private fun provideNetManager(context: Context): NetManager { if (NET_MANAGER == null) { NET_MANAGER = NetManager(context) } return NET_MANAGER!! } private fun gitRepoRepository(netManager: NetManager): GitRepoRepository { return GitRepoRepository(netManager) } fun provideMainViewModelFactory(context: Context): MainViewModelFactory { val netManager = provideNetManager(context) val repository = gitRepoRepository(netManager) return MainViewModelFactory(repository) } }
這樣,咱們確保每一個應用程序只有一個實例。換句話說,咱們能夠說NetManager實例具備Application一樣的生命週期範圍。
讓咱們看看依賴圖:
若是看一下上面的注入,您會發現,若是圖中有不少依賴項,那麼咱們將須要作大量工做。 Dagger幫助咱們以簡單的方式管理依賴項及其範圍。
讓咱們先引入dagger
:
... dependencies { ... implementation "com.google.dagger:dagger:2.14.1" implementation "com.google.dagger:dagger-android:2.14.1" implementation "com.google.dagger:dagger-android-support:2.14.1" kapt "com.google.dagger:dagger-compiler:2.14.1" kapt "com.google.dagger:dagger-android-processor:2.14.1" ... }
要使用dragger,咱們須要新建一個Application繼承自DaggerApplication
類,咱們建立一個DaggerApplication
:
class ModernApplication : DaggerApplication(){ override fun applicationInjector(): AndroidInjector<out DaggerApplication> { TODO("not implemented") } }
在繼承DaggerApplication()
時,它須要實現applicationInjector()
方法,該方法應返回AndroidInjector的實現。稍後我將介紹AndroidInjector。
不要忘了在AndroidManifest.xml
註冊application:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="me.mladenrakonjac.modernandroidapp"> ... <application android:name=".ModernApplication" ...> ... </application> </manifest>
首先,建立AppModule
,Modules是具備@Provides
註解功能的類。咱們說這些方法是提供者,由於它們提供了實例。要將某個類做爲模塊,咱們須要使用@Module
註解對該類進行註解。這些註解可幫助Dagger製做和驗證圖形。咱們的AppModule將僅具備提供應用程序上下文的函數:
@Module class AppModule{ @Provides fun providesContext(application: ModernApplication): Context { return application.applicationContext } }
如今,咱們建立一個component
:
@Singleton @Component( modules = [AndroidSupportInjectionModule::class, AppModule::class]) interface AppComponent : AndroidInjector<ModernApplication> { @Component.Builder abstract class Builder : AndroidInjector.Builder<ModernApplication>() }
Component是一個接口,咱們在其中指定應從哪些模塊中將實例注入哪些類中。這個例子中,咱們指定AppModule
和AndroidSupportInjectionModule
。
AndroidSupportInjectionModule
是可幫助咱們將實例注入Android生態系統類的模塊,這些類包括 Activity
,Fragment
,Service
,BroadcastReceivers
或ContentProviders
。
由於咱們要使用咱們的組件來注入這些類,所以AppComponent
必須繼承AndroidInjector <T>
。對於T
,咱們使用ModernApplication
類。若是打開AndroidInjector
接口,則能夠看到:
abstract class Builder<T> implements AndroidInjector.Factory<T> { @Override public final AndroidInjector<T> create(T instance) { ... } public abstract void seedInstance(T instance); ... } }
Builder
有兩個方法:create(T instance)
用於建立AndroidInjector,而seedInsance(T instance)
方法則用於提供實例。
在咱們的例子中,咱們將建立具備ModernApplication實例的AndroidInjector,並將在須要的地方提供該實例。
@Component.Builder abstract class Builder : AndroidInjector.Builder<ModernApplication>()
關於咱們的AppComponent
,總結一下:
@Provides
方法提供ModernApplication的實例。例如,將向AppModule中的providerContext(application:ModernApplication)
方法提供ModernApplication的實例。如今,咱們編譯一下項目
當構建結束,Dragger將自動生成一些新的類,對於AppComponent
,Dragger將會生成一個DaggerAppComponent
類。
讓咱們回到ModernApplication並建立應用程序的主要組件。建立的組件應在applicationInjector()
方法中返回。
class ModernApplication : DaggerApplication(){ override fun applicationInjector(): AndroidInjector<out DaggerApplication> { return DaggerAppComponent.builder().create(this) }
如今,咱們完成了Dagger所需的標準配置。
當咱們想將實例注入MainActivity類時,咱們須要建立MainActivityModule
。
@Module internal abstract class MainActivityModule { @ContributesAndroidInjector() internal abstract fun mainActivity(): MainActivity }
@ContributesAndroidInjector
註解可幫助Dagger鏈接所需的內容,以便咱們能夠將實例注入指定的Activity中。
若是返回到咱們的Activity,能夠看到咱們使用Injection類注入了MainViewModelProvider
。所以,咱們須要在MainActivityModule
中提供provider
方法,該方法將提供MainViewModelProvider
:
@Module internal abstract class MainActivityModule { @Module companion object { @JvmStatic @Provides internal fun providesMainViewModelFactory(gitRepoRepository: GitRepoRepository) : MainViewModelFactory { return MainViewModelFactory(gitRepoRepository) } } @ContributesAndroidInjector() internal abstract fun mainActivity(): MainActivity }
可是,誰將提供GitRepoRepository給providesMainViewModelFactoty
方法呢?
有兩個選擇:咱們能夠爲其建立provider
方法並返回新實例,或者可使用@Inject
註解它的構造函數
。
讓咱們回到咱們的GitRepoRepository並使用@Inject
註解來標註其構造函數:
class GitRepoRepository @Inject constructor(var netManager: NetManager) { ... }
由於GitRepoRepository
須要NetManager
,所以,一樣標註NetManager
的構造函數
@Singleton class NetManager @Inject constructor(var applicationContext: Context) { ... }
咱們使用@Singleton
註解設置NetManager爲單例。另外,NetManager須要applicationContext
。 AppModule中有一個方法來提供它。
不要忘記將MainActivityModule
添加到AppComponent.kt
中的模塊列表中:
@Component( modules = [AndroidSupportInjectionModule::class, AppModule::class, MainActivityModule::class]) interface AppComponent : AndroidInjector<ModernApplication> { @Component.Builder abstract class Builder : AndroidInjector.Builder<ModernApplication>() }
最後,咱們須要將其注入到咱們的MainActivity中。爲了使Dagger在那裏工做,咱們的MainActivity須要繼承DaggerAppCompatActivity
。
class MainActivity : DaggerAppCompatActivity(), RepositoryRecyclerViewAdapter.OnItemClickListener { ... @Inject lateinit var mainViewModelFactory: MainViewModelFactory override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val viewModel = ViewModelProviders.of(this, mainViewModelFactory) .get(MainViewModel::class.java) ... } ... }
要注入MainViewModelFactory
實例,咱們須要使用@Inject
註解。
重要說明: mainViewModelFactory
變量必須是公共的。
到這兒就完成了!
再也不須要從「注入」類進行注入:
mainViewModelFactory = Injection.provideMainViewModelFactory(applicationContext)
實際上,咱們能夠刪除Injection
類了,由於咱們如今正在使用Dagger了。
MainViewModelFactory
注入MainActiivty@Inject
註解對mainViewModelFactory
進行標註@ContributesAndroidInjector
註解的方法的模塊,該方法返回MainActivity。@Inject
註解的構造函數。provideMainViewModelFactory()
返回實例,可是爲了建立它,須要GitRepoRepository實例@Inject
帶註解的構造函數,該構造函數返回GitRepoRepository實例。@Inject
註解的構造函數。可是該構造函數須要NetManager實例@Inject
註釋的構造函數。就是這樣!
問題:對於每一個具備參數的ViewModel,咱們都須要建立ViewModelFactory類。在Chris Banes的Tivi
應用程序源代碼中,我發現了一種很是好的自動方法。
建立ViewModelKey.kt :
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) @Retention(AnnotationRetention.RUNTIME) @MapKey annotation class ViewModelKey(val value: KClass<out ViewModel>)
而後添加一個DaggerAwareViewModelFactory
類:
class DaggerAwareViewModelFactory @Inject constructor( private val creators: @JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>> ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { var creator: Provider<out ViewModel>? = creators[modelClass] if (creator == null) { for ((key, value) in creators) { if (modelClass.isAssignableFrom(key)) { creator = value break } } } if (creator == null) { throw IllegalArgumentException("unknown model class " + modelClass) } try { @Suppress("UNCHECKED_CAST") return creator.get() as T } catch (e: Exception) { throw RuntimeException(e) } } }
建立ViewModelBuilder
module:
@Module internal abstract class ViewModelBuilder { @Binds internal abstract fun bindViewModelFactory(factory: DaggerAwareViewModelFactory): ViewModelProvider.Factory }
添加ViewModelBuilder
到AppComponent
:
@Singleton @Component( modules = [AndroidSupportInjectionModule::class, AppModule::class, ViewModelBuilder::class, MainActivityModule::class]) interface AppComponent : AndroidInjector<ModernApplication> { @Component.Builder abstract class Builder : AndroidInjector.Builder<ModernApplication>() }
MainViewModel類添加@Injec
:
class MainViewModel @Inject constructor(var gitRepoRepository: GitRepoRepository) : ViewModel() { ... }
從如今開始,咱們只須要將其綁定到Activity模塊便可:
@Module internal abstract class MainActivityModule { @ContributesAndroidInjector internal abstract fun mainActivity(): MainActivity @Binds @IntoMap @ViewModelKey(MainViewModel::class) abstract fun bindMainViewModel(viewModel: MainViewModel): ViewModel }
不須要MainViewModelFactory
provider 。實際上,根本不須要MainViewModelFactory.kt
,所以能夠將其刪除。
最後,在MainActivity.kt
中對其進行更改,以便咱們使用ViewModel.Factory
類型而不是MainViewModelFactory
:
class MainActivity : DaggerAppCompatActivity(), RepositoryRecyclerViewAdapter.OnItemClickListener { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory override fun onCreate(savedInstanceState: Bundle?) { ... val viewModel = ViewModelProviders.of(this, viewModelFactory) .get(MainViewModel::class.java) ... } ... }
感謝Chris Banes
這個神奇的解決方案!
譯者注:原本,這個系列還有一篇文章,講Retrofit + Room的運用,不過好像原做者斷更了😂😂😂,所以本篇就將是最後一篇了,本系列總共
4
篇,建議你們看完,你會有收穫的!
以上就是本文的所有內容,感謝你的閱讀!