DEV Community

loading...

Implementation of Hilt in Retrofit

Dev Arena
Android, Kotlin, Flutter, IOT, AI & ML enthusiast Passionate on Technology & Innovation.
Originally published at dev.to ・4 min read

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.

Project Structure

Alt Text

Package Structure

Alt Text

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.
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"

    }
}
Enter fullscreen mode Exit fullscreen mode
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.
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"
}
Enter fullscreen mode Exit fullscreen mode

Manifest

  • Internet permission(uses-permission) is required for accessing API.
  • Application class should be referred in the android:name attribute.
 <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>
Enter fullscreen mode Exit fullscreen mode

Application

Create Application class and annotate with @HiltAndroidApp.

@HiltAndroidApp annotation generates Hilt code for Application class and creates parent container.

@HiltAndroidApp
class HiltApplication : Application()
Enter fullscreen mode Exit fullscreen mode

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.

@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()
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

ViewModel

Create ViewModel class and add method for loading the country list.

@HiltViewModel is annotated to enable viewmodel injection.

@HiltViewModel
class MainViewModel @Inject constructor(private val repository: Repository) : ViewModel() {
    private val countryLiveData = MutableLiveData<List<Country>?>()

    fun getCountry() = countryLiveData

    init {
        loadCountries()
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

@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)
}
Enter fullscreen mode Exit fullscreen mode

Service

Define the Countries list API's in the Service.

interface ApiService {
    @GET("region/europe")
    suspend fun getCountries(): Response<Countries>
}
Enter fullscreen mode Exit fullscreen mode

Repository

Create an Repository class which returns the list of countries network response.

class Repository(private val apiService: ApiService) {
    suspend fun getCountries() = apiService.getCountries()
}
Enter fullscreen mode Exit fullscreen mode

Screenshot

Alt Text

Code

Project code is accessible in the GitHub Repo DevArena/HiltRetrofitApp

Happy Coding! 😀

Discussion (0)