Implementation of Hilt in Retrofit
Tags: android, kotlin, hilt, retrofit
Published: 2021-06-03 08:15:27

In this article let's explore how easy to implement the Hilt DI and annotate the Network module to invoke the Countries API.

###Components:
Components used in the Project.
* Hilt
* Coroutine
* Retrofit
* Gson
* ViewBinding
* Timber
* ConstraintLayout
* RecyclerView

###API
**Rest Countries** - *Get information about countries via a RESTful API.*


| Method  |  URL |
|---|---|
| **GET**  | https://restcountries.eu/rest/v2/region/europe |


###Project Structure
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1bzauaaaepvkr9hk5w3c.png)

###Package Structure
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jiqafir9o44sk4vxzwkt.png)

###Implementation
Follow the steps to implement the Hilt DI.

####Add Dependencies
######Project Level
* Add the `hilt-android-gradle-plugin` into the classpath of the project level `build.gradle`.

```kotlin
buildscript {
    ext.kotlin_version = "1.5.20-M1"
    ext.gradle_version = "4.2.1"
    ext.hilt_version = '2.35'
    ext.core_version = '1.5.0'
    ext.appcompat_version = '1.3.0'
    ext.material_version = '1.3.0'
    ext.constraint_version = '2.0.4'
    ext.coroutines_version = '1.5.0'
    ext.lifecycle_version = '2.3.1'
    ext.activity_version = '1.2.3'
    ext.retrofit_version = '2.6.0'
    ext.httplogging_version = '3.12.0'
    ext.json_version = '2.8.6'
    ext.junit_version = '4.13.2'
    ext.extjunit_version = '1.1.2'
    ext.espressocore_version = '3.3.0'
    ext.timber_version = '4.7.1'

    dependencies {
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"

    }
}
```

######App Level
* Add the `dagger.hilt.android.plugin` plugin into the app level `build.gradle`.
* Enable the support for Java 8 features by adding `compileOptions` in the `build.gradle`.
* Add the `dagger:hilt-android` implementation and `dagger:hilt-compiler` kapt into the `build.gradle`.

```kotlin
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'kotlin-android-extensions'
    id 'dagger.hilt.android.plugin'
}
android {
  compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        viewBinding true
    }
}
dependencies {
    //  Kotlin
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

    // Core
    implementation "androidx.core:core-ktx:$core_version"
    implementation "androidx.appcompat:appcompat:$appcompat_version"
    implementation "androidx.activity:activity-ktx:$activity_version"

    // UI
    implementation "com.google.android.material:material:$material_version"
    implementation "androidx.constraintlayout:constraintlayout:$constraint_version"

    //  Lifecycle Components
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"

    //  Logging
    implementation "com.jakewharton.timber:timber:$timber_version"

    // Hilt DI
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-compiler:$hilt_version"

    //  Coroutines
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"

    //Retrofit
    implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
    implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
    implementation "com.squareup.okhttp3:logging-interceptor:$httplogging_version"
    implementation "com.google.code.gson:gson:$json_version"

    // Test
    testImplementation "junit:junit:$junit_version"
    androidTestImplementation "androidx.test.ext:junit:$extjunit_version"
    androidTestImplementation "androidx.test.espresso:espresso-core:$espressocore_version"
}
```

####Manifest
* Internet permission(`uses-permission`) is required for accessing API.
* Application class should be referred in the `android:name` attribute.

```xml
 <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:name=".core.HiltApplication"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyHiltApplication">
        <activity android:name=".main.view.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
```

####Application
Create Application class and annotate with @HiltAndroidApp. 
>`@HiltAndroidApp` annotation generates Hilt code for Application class and creates parent container.

```kotlin
@HiltAndroidApp
class HiltApplication : Application()
```

####Activity
Create an Activity and setup the UI & livedata observers. Here, viewbinding is used for inflating the UI. And viewmodel is initialized using the `by viewModels()` ktx.
>`@AndroidEntryPoint` annotation generates Hilt components for Android classes like Activity, Fragment etc., based on the lifecycle of the respective component. 

```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private val mainViewModel: MainViewModel by viewModels()

    @Inject
    lateinit var countryAdapter: CountryAdapter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        setUpUI()
        setUpObservers()
    }
}
```

####Adapter
Create the Adapter class for recyclerview with constructor injection.
> `@Inject` annotation used in constructor, field and method injection where dependency is requested. Field injected cannot be private.

```kotlin
class CountryAdapter @Inject constructor() : RecyclerView.Adapter<CountryAdapter.ViewHolder>() {
    var countries: List<Country> = emptyList()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
        ViewHolder(
            CountryItemBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )

    override fun onBindViewHolder(holder: ViewHolder, position: Int) =
        holder.bind(countries[position])

    override fun getItemCount(): Int = countries.size

    inner class ViewHolder(private val binding: CountryItemBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(country: Country) {
            binding.apply {
                country.also { (name, capital) ->
                    nameTextview.text = name
                    capitalTextview.text = capital
                }
            }
        }
    }
}
```

####ViewModel
Create ViewModel class and add method for loading the country list.
>`@HiltViewModel` is annotated to enable viewmodel injection.
```kotlin
@HiltViewModel
class MainViewModel @Inject constructor(private val repository: Repository) : ViewModel() {
    private val countryLiveData = MutableLiveData<List<Country>?>()

    fun getCountry() = countryLiveData

    init {
        loadCountries()
    }
}
```

####Module
Create object class with various annotations like @Module, @InstallIn, @Singleton and @Provides which provides dependencies. The Module class supplies the necessary dependent methods for the Network module.

>`@Module` annotated class provides required instances as dependency for various classes.

>Hilt Module annotated with `@InstallIn` specify the scope of the Module. Here, `SingletonComponent::class` generates singleton container for the class.

>`@Provides` is annotated in the method and provides objects to inject when required.

>`@Singleton` is annotated to create singleton instance of the dependency object and use it throughout the app.

```kotlin
@Module
@InstallIn(SingletonComponent::class)
object ApiModule {
    private const val BASE_URL = "https://restcountries.eu/rest/v2/"

    @Singleton
    @Provides
    fun providesHttpLoggingInterceptor() = HttpLoggingInterceptor()
        .apply {
            level = HttpLoggingInterceptor.Level.BODY
        }

    @Singleton
    @Provides
    fun providesOkHttpClient(httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient =
        OkHttpClient
            .Builder()
            .addInterceptor(httpLoggingInterceptor)
            .build()

    @Singleton
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl(BASE_URL)
        .client(okHttpClient)
        .build()

    @Singleton
    @Provides
    fun provideApiService(retrofit: Retrofit): ApiService = retrofit.create(ApiService::class.java)

    @Singleton
    @Provides
    fun providesRepository(apiService: ApiService) = Repository(apiService)
}
```

####Service 
Define the Countries list API's in the Service.
```kotlin
interface ApiService {
    @GET("region/europe")
    suspend fun getCountries(): Response<Countries>
}
```

####Repository
Create an Repository class which returns the list of countries network response.
```kotlin
class Repository(private val apiService: ApiService) {
    suspend fun getCountries() = apiService.getCountries()
}
```

###Screenshot
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4beb3h2t53tquy6y28l.png)

####Code
Project code is accessible in the GitHub Repo [DevArena/HiltRetrofitApp](https://github.com/krisrajkumar/DevArena/tree/development/HiltRetrofitApp)

Happy Coding! :grinning: