Skip to content

Instantly share code, notes, and snippets.

@mitchtabian
Last active September 12, 2023 18:43
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mitchtabian/d06ee1d6445265adf00b087fc56708d8 to your computer and use it in GitHub Desktop.
Save mitchtabian/d06ee1d6445265adf00b087fc56708d8 to your computer and use it in GitHub Desktop.
Basics #6: Hilt Modules, Binds and Provides
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.Fragment
import com.google.gson.Gson
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.components.ApplicationComponent
import javax.inject.Inject
import javax.inject.Singleton
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var someClass: SomeClass
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println(someClass.doAThing())
}
}
class SomeClass
@Inject
constructor(
private val someInterfaceImpl: SomeInterface,
private val gson: Gson
){
fun doAThing(): String{
return "Look I got: ${someInterfaceImpl.getAThing()}"
}
}
class SomeInterfaceImpl
@Inject
constructor(): SomeInterface {
override fun getAThing() : String{
return "A Thing"
}
}
interface SomeInterface{
fun getAThing(): String
}
// ***REMEMBER***
// Notice this doesn't compile because dagger doesn't know how to build the Gson object, even though we added it to the module.
@InstallIn(ApplicationComponent::class)
@Module
abstract class MyModule{
@Singleton
@Binds
abstract fun bindSomeDependency(
someImpl: SomeInterfaceImpl
): SomeInterface
// note this is from the dependency: "com.squareup.retrofit2:converter-gson:2.6.0"
@Singleton
@Binds
abstract fun bindGson(
gson: Gson
): Gson
}
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.Fragment
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.components.ApplicationComponent
import javax.inject.Inject
import javax.inject.Singleton
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var someClass: SomeClass
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println(someClass.doAThing())
}
}
class SomeClass
@Inject
constructor(
private val someInterfaceImpl: SomeInterface
){
fun doAThing(): String{
return "Look I got: ${someInterfaceImpl.getAThing()}"
}
}
class SomeInterfaceImpl
@Inject
constructor(): SomeInterface {
override fun getAThing() : String{
return "A Thing"
}
}
interface SomeInterface{
fun getAThing(): String
}
// ***REMEMBER***
// This method can't be used in all situations. AND it's more complex in my opinion. So I never use it.
@InstallIn(ApplicationComponent::class)
@Module
abstract class MyModule{
@Singleton
@Binds
abstract fun bindSomeDependency(
someImpl: SomeInterfaceImpl
): SomeInterface
}

From the Hilt-Android docs:

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.

Source: Hilt Modules

Modules are not new. Dagger used modules. Modules are the solution to the problem I outlined in Basics #5.

The purpose of a Module is to tell dagger how to instantiate an implementation of an interface or a class that you do not own.

Like saying: "Hey Dagger, here's how you build that thing I want."

The confusing part

Hilt gives you two ways to do this. And as far as I know there is no performance difference between the two. But one is significantly more complex (IN MY OPINION). And this is the kicker:

  1. The SIMPLE one can be used for ALL scenarios
  2. The COMPLEX one can be used for MOST scenarios

Anyway, I will explain this to you with examples and I'm sure (like me) you will choose the simpler way that can be used for all scenarios.

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.Fragment
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.components.ApplicationComponent
import javax.inject.Inject
import javax.inject.Singleton
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var someClass: SomeClass
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println(someClass.doAThing())
}
}
class SomeClass
@Inject
constructor(
private val someInterfaceImpl: SomeInterface,
private val gson: Gson
){
fun doAThing(): String{
return "Look I got: ${someInterfaceImpl.getAThing()}"
}
}
class SomeInterfaceImpl
@Inject
constructor(): SomeInterface {
override fun getAThing() : String{
return "A Thing"
}
}
interface SomeInterface{
fun getAThing(): String
}
// This is the best way and works ALWAYS
@InstallIn(ApplicationComponent::class)
@Module
class MyModule{
@Singleton
@Provides
fun provideSomeInterface(): SomeInterface{
return SomeInterfaceImpl()
}
@Singleton
@Provides
fun provideGson(): Gson {
return GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment