# Dependency Injection by Hilt ## Hilt 是基於Dagger開發的,根據官方介紹有以下優點 1. 程式碼可重複使用 2. 易於重構 3. 易於測試 #### 程式碼架構 [參考這篇](https://itnext.io/android-architecture-hilt-mvvm-kotlin-coroutines-live-data-room-and-retrofit-ft-8b746cab4a06) ,他的code有蠻多不錯的寫法,運用了蠻多的 '*泛型* ' 與'*高階函數* ' #### 不免俗的還是上一張MVVM架構圖 ![](https://i.imgur.com/s433o9a.png) ![](https://i.imgur.com/cbjgskI.png) #### 進入主題 hilt 1. gradle 引用 (注意kapt 跟hilt core版本要一樣,有些狀況編譯會錯誤) ```kotlin= // project dependencies { ext.hilt_version = '2.35' //hilt classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" } // app plugins { // hilt support id 'kotlin-kapt' id 'dagger.hilt.android.plugin' } dependencies { //hilt support implementation 'com.google.dagger:hilt-android:2.35' implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03" kapt 'com.google.dagger:hilt-android-compiler:2.35' kapt "androidx.hilt:hilt-compiler:1.0.0" implementation "androidx.hilt:hilt-navigation-fragment:1.0.0" } ``` 2. 一切源頭從@HiltAndroidApp開始 **於專案目錄下新增application class,並將路徑加入manifest** ```kotlin= @HiltAndroidApp class MainApplication : Application(){ override fun onCreate() { super.onCreate() if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree()) } } } //於manifest加入剛剛新增的application讓app啟動時載入 <application android:name=".MainApplication" ``` 3. 在需要注入的類別加上聲明 @AndroidEntryPoint 原作者在MainActivity也有聲明,因為Fragment是依附在Activity底下。 ```kotlin= @AndroidEntryPoint class CharactersFragment : Fragment(), CharactersAdapter.CharacterItemListener { } @AndroidEntryPoint class CharacterDetailFragment: Fragment() { } @AndroidEntryPoint class MainActivity : AppCompatActivity() { } ``` 4. 注入,在類別建構子使用 @Inject constructor()或者在變數前@Inject var myVar = xxxx 這個例子,發出網路請求的起點應該是從viewModel這裡開始的。在這裏聲明@hiltviewModel功能跟@AndroidEntryPoint一樣, 那@Inject constructor()是一個提供instance的概念,hilt會自動產生CharactersViewModel的instance, ```kotlin= @HiltViewModel class CharactersViewModel @Inject constructor( repository: CharacterRepository ) : ViewModel() { val characters = repository.getCharacters() } ``` 那我們繼續往下追,CharactersViewModel要產生instance需要一個參數,repository: CharacterRepository,這個參數他是一個類別 ```kotlin= class CharacterRepository @Inject constructor( private val remoteDataSource: CharacterRemoteDataSource, private val localDataSource: CharacterDao ) ``` 在繼續往下追,CharacterRepository要產生instance需要兩參數: remoteDataSource: CharacterRemoteDataSource, localDataSource: CharacterDao 我們先看第一個參數CharacterRemoteDataSource,然而要產生instance需要變數CharacterService ```kotlin= class CharacterRemoteDataSource @Inject constructor(private val characterService: CharacterService) : BaseDataSource() ``` 追到CharacterService發現他是一個interface,他無法利用建構子產生instance,到這就走不下去了,稍後提到的Module會解決此問題 ```kotlin= interface CharacterService { @GET("character") suspend fun getAllCharacters(): Response<CharacterList> @GET("character/{id}") suspend fun getCharacter(@Path("id") id: Int): Response<Character> } ``` 相同地,第二個變數CharacterDao他也是一個interface 接下來我們來談談Module吧 ================== 5. 那些無法使用@Inject提供instance的類別或者介面,hilt要去哪兒找勒? @Module 登場 AppModule這個object,來瞧瞧他寫了些什麼 --- * 我們先看CharacterService,他是一個介面,沒有建構子,在Module裏面,provideCharacterService的instance是透過Retrofit間接取得 * 而Retrofit也是無法透過建構子產生instance,一樣要在Module定義 * Gson一樣道理 ```kotlin= @Provides fun provideCharacterService(retrofit: Retrofit): CharacterService = retrofit.create(CharacterService::class.java) @Singleton @Provides fun provideRetrofit(gson: Gson): Retrofit = Retrofit.Builder() .baseUrl("https://rickandmortyapi.com/api/") .addConverterFactory(GsonConverterFactory.create(gson)) .build() @Provides fun provideGson(): Gson = GsonBuilder().create() ``` * 接下來看第二個變數CharacterDao,他也是一個interface,沒有建構子可以產生instance * 他是透過資料庫間接取得 * 在定義取得資料庫的instance ```kotlin= @Singleton @Provides fun provideDatabase(@ApplicationContext appContext: Context) = AppDatabase.getDatabase(appContext) @Singleton @Provides fun provideCharacterDao(db: AppDatabase) = db.characterDao() ``` --- ##### 所以Module主要是在定義無法使用@Inject constructor()取得instance的函數,提供給hilt注入。 ##### 經過這樣分析下來,從viewModel發出網路請求,一路上所需要的instance要從哪裡Inject與Module定義就都完成了,剩下的就是完成商業邏輯了 ##### 以上只是Hilt最基本的應用 ###### tags: `hilt` `DI` `kotlin` `Android`