【譯】使用Kotlin從零開始寫一個現代Android 項目-Part4

這是本系列的第四篇文章,尚未看過前面三篇的讀者能夠先看看: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依賴三個對象,分別是netManagerlocalDataSourceremoteDataSource。經過構造函數提供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幫助咱們以簡單的方式管理依賴項及其範圍。

讓咱們先引入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是一個接口,咱們在其中指定應從哪些模塊中將實例注入哪些類中。這個例子中,咱們指定AppModuleAndroidSupportInjectionModule

AndroidSupportInjectionModule是可幫助咱們將實例注入Android生態系統類的模塊,這些類包括ActivityFragmentServiceBroadcastReceiversContentProviders

由於咱們要使用咱們的組件來注入這些類,所以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,總結一下:

  • 咱們擁有AppComponent,它是繼承與AndroidInjector的應用程序的主要組件
  • 當咱們要構建Component時,咱們將須要使用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
  • 爲了使Dragger能在MainActivity中正常工做,MainActivity須要繼承自DaggerAppCompatActivity
  • 咱們須要使用@Inject註解對mainViewModelFactory進行標註
  • Dagger搜索帶有@ContributesAndroidInjector註解的方法的模塊,該方法返回MainActivity。
  • Dagger搜索返回MainViewModelFactory實例的provider,或帶@Inject註解的構造函數。
  • provideMainViewModelFactory() 返回實例,可是爲了建立它,須要GitRepoRepository實例
  • Dagger搜索provider或@Inject帶註解的構造函數,該構造函數返回GitRepoRepository實例。
  • GitRepoRepository類具備帶@Inject註解的構造函數。可是該構造函數須要NetManager實例
  • Dagger搜索返回NetManager實例的provider或帶@Inject註釋的構造函數。
  • Dagger搜索返回Application Context實例的provider。
  • AppModule具備返回application context 的provider方法。可是該構造函數須要ModernApplication實例。
  • AndroidInjector具備provider。

就是這樣!

有一種更好的自動化方法來提供ViewModelFactory

問題:對於每一個具備參數的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
}
複製代碼

添加ViewModelBuilderAppComponent:

@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篇,建議你們看完,你會有收穫的!

以上就是本文的所有內容,感謝你的閱讀!

相關文章
相關標籤/搜索