Dependency Injection using Hilt
This story explains basics of Dependency Injection using Hilt framework with a sample example in Kotlin
Introduction:
What is Dependency Injection ?
Dependency Injection Comes into picture when there are 2 classes that are dependent on each other.
Second class makes use of first class. Then 2nd class is dependent on first class.
Official definition of Dependency Injection:
Dependency Injection is based on concept called “Inversion of Control”. This concept means a class should get its dependencies from external class rather than instantiating them in the class.
Dependency injection (DI) is a technique widely used in programming and well suited to Android development.
Hilt Dependency Injection Framework:
Hilt is a dependency injection library for Android that reduces the boilerplate of doing manual dependency injection in your project.
Hilt is built on top of the popular DI library Dagger to benefit from the compile-time correctness, runtime performance, scalability, and Android Studio support that Dagger provides.
Hilt reduces lot of boiler plate that is generally used when implementing Dependency Injection using Dagger framework.
Hilt is released as a part of Jet pack libraries is now the recommended way by Google to use it.
Steps to Integrate Hilt in project:
Step 1 : Hilt Application class
All apps that use Hilt must contain an Application
class that is annotated with @HiltAndroidApp
.
This generated Hilt component is attached to the Application
object's lifecycle and provides dependencies to it. Additionally, it is the parent component of the app, which means that other components can access the dependencies that it provides.
Example:
@HiltAndroidApp
class HiltApplication : Application() {
}
Step 2: Inject dependencies to Android classes
Once Hilt is set up in your Application
class and an application-level component is available, Hilt can provide dependencies to other Android classes that have the @AndroidEntryPoint
annotation.
Hilt supports different Android classes like Activity,Fragment,Service,Broadcast Receiver,View
If you annotate an Android class with @AndroidEntryPoint
, then you also must annotate Android classes that depend on it. For example, if you annotate a fragment, then you must also annotate any activities where you use that fragment.
Example:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_layout_main)
replaceFragmentWithNoHistory(EmployeeFragment(), R.id.container_fragment)
}
}
corresponding Fragment:
@AndroidEntryPoint
class EmployeeFragment : Fragment() {
private val mainViewModel : EmployeeViewModel by viewModels()
private var userListView : View? = null
var mContainerId:Int = -1
private var postListAdapter : PostAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
Step 3: Hilt modules
A Hilt module is a class that is annotated with @Module
. Like a Dagger module, it informs Hilt how to provide instances of certain types. Unlike Dagger modules, you must annotate Hilt modules with @InstallIn
to tell Hilt which Android class each module will be used or installed in.
Example:
@InstallIn(ApplicationComponent::class)
@Module
object NetworkModule {
@Provides
fun provideNetworkURL():String = NetworkUtil.NETWORK_BASE_URL
@Provides
fun provideHttpLogger():HttpLoggingInterceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
@Provides
fun provideOKHttp(logger: HttpLoggingInterceptor) : OkHttpClient{
val okHttpClient = OkHttpClient.Builder()
with(okHttpClient){
addInterceptor(logger)
callTimeout(NetworkUtil.REQUEST_TIMEOUT,TimeUnit.SECONDS)
readTimeout(NetworkUtil.REQUEST_TIMEOUT,TimeUnit.SECONDS)
writeTimeout(NetworkUtil.REQUEST_TIMEOUT,TimeUnit.SECONDS)
connectTimeout(NetworkUtil.REQUEST_TIMEOUT,TimeUnit.SECONDS)
}
return okHttpClient.build()
}
@Provides
fun provideRetrofit(baseUrl : String,okHttpClient: OkHttpClient) = Retrofit.Builder().baseUrl(baseUrl).client(okHttpClient).addConverterFactory(GsonConverterFactory.create()).build()
@Provides
fun provideAPIService(retrofit: Retrofit) = retrofit.create(NetworkAPIService::class.java)
}
Note: If you want a dependency to be available at an activity level , use the below annotation for the module.
In the below example , you want Repository to be available at an activity level not at application level, we use @InstallIn(ActivityRetainedComponent::class) annotation
Example:
@Module
@InstallIn(ActivityRetainedComponent::class)
object NetworkRepositoryModule {
@Provides
fun provideRepository(apiService: NetworkAPIService) = NetworkRepository(apiService)
}
Step 4 : Hilt and Jetpack integrations
ViewModels are not directly supported by Hilt and to work with Hilt in ViewModel we use Jetpack Extensions.
Hilt includes extensions for providing classes from other Jetpack libraries. Hilt currently supports the following Jetpack components:
ViewModel
WorkManager
You must add the Hilt dependencies to take advantage of these integrations.
Once dependencies are added we can directly pass dependent classes to View model as below:
Example:
class EmployeeViewModel @ViewModelInject constructor(private val networkRepository: NetworkRepository) :
ViewModel(),LifecycleObserver {}
Dependencies used as part of this Hilt Integration:
//Hilt
implementation "com.google.dagger:hilt-android:2.28-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
// When using Kotlin.
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
The example shared in Github uses Kotlin Coroutines to fetch data from Network and display as list using Hilt Dependency Injection.
Please find below link for GitHub Source Code: