這是本系列的第四篇文章,尚未看過前面三篇的讀者能夠先看看: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
篇,建議你們看完,你會有收穫的!
以上就是本文的所有內容,感謝你的閱讀!