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
Package Structure
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 levelbuild.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"
}
}
App Level
- Add the
dagger.hilt.android.plugin
plugin into the app levelbuild.gradle
. - Enable the support for Java 8 features by adding
compileOptions
in thebuild.gradle
. - Add the
dagger:hilt-android
implementation anddagger:hilt-compiler
kapt into thebuild.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"
}
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>
Application
Create Application class and annotate with @HiltAndroidApp.
@HiltAndroidApp
annotation generates Hilt code for Application class and creates parent container.
@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.
@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.
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.
@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.
@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.
interface ApiService {
@GET("region/europe")
suspend fun getCountries(): Response<Countries>
}
Repository
Create an Repository class which returns the list of countries network response.
class Repository(private val apiService: ApiService) {
suspend fun getCountries() = apiService.getCountries()
}
Screenshot
Code
Project code is accessible in the GitHub Repo DevArena/HiltRetrofitApp
Happy Coding! 😀
Top comments (1)
Great tutorial!
How do I add a second API to the project, I've tried adding it to the same module, but it doesn't work, I get a duplicate bindings error