Skip to content

Instantly share code, notes, and snippets.

@limkhashing
Last active January 30, 2025 08:51
Show Gist options
  • Save limkhashing/f319cde8fb4640839ef2c1fe836ad3d9 to your computer and use it in GitHub Desktop.
Save limkhashing/f319cde8fb4640839ef2c1fe836ad3d9 to your computer and use it in GitHub Desktop.
Dagger-Hilt vs. Koin vs. Manual Dependency Injection
// Hilt
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): MyAppDatabase {
return MyAppDatabase(context)
}
}
// Hilt
@HiltAndroidApp
class HiltApplication : Application()
// Koin
class KoinApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@KoinApplication)
modules(
appModule,
viewModelModule
)
}
}
}
// Koin
val appModule = module {
single { MyAppDatabase(androidContext()) }
}
val viewModelModule = module {
factoryOf(::MyRepository)
viewModelOf(::MyViewModel)
}
class MainActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
setContent {
val hiltViewModel hiltViewModel<MyViewModel>()
val koinViewModel koinViewModel<MyViewModel>()
val manualViewModel = viewModel<MyViewModel>(
factory = viewModelFactory {
MyViewModel(
database = ManualApplication.appModule.database,
repository ManualApplication.appModule.repository
)
}
)
val state manualViewModel.state
...
Theme {
Scaffold(....) {
....
}
}
}
}
}
class ManualApplication: Application() {
companion object {
lateinit var appModule: AppModule
}
override fun onCreate() {
super.onCreate()
appModule = AppModuleImpl(this)
}
}
// Manual Dependency Injection approach
interface ManualAppModule {
val database: MyAppDatabase
val repository: MyRepository
}
class ManualAppModuleImpl(
private val appContext: Context
): AppModule {
override val database: MyAppDatabase by lazy {
MyAppDatabase(appContext)
}
override val authApi: AuthApi by lazy {
Retrofit.Builder()
.baseUrl("https://my-url.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create()
}
override val authRepository: AuthRepository by lazy {
AuthRepositoryImpl(authApi)
}
}
@HiltViewModel // Use this annoication only for Hilt
class MyViewModel @Inject constructor(
private val database: MyAppDatabase,
private val repository: MyRepository
): ViewModel() {
var state by mutableStateOf(value: "database not synced")
private set
fun syncDatabase() {
val data repository.fetchData()
database.addData(data)
state = "database synced!"
}
}
// Manual Dependency Injection approach
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
fun <VM: ViewModel> viewModelFactory(initializer: () -> VM): ViewModelProvider.Factory {
return object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return initializer() as T
}
}
}
@limkhashing
Copy link
Author

limkhashing commented Jan 30, 2025

https://www.youtube.com/watch?v=_qb87PN7jlI

DI is not a tool, its a concept

  • DI is not just using dagger hilt, we can create manually.
  • its not dagger hilt that makes testing easier, its DI that makes testing easier (I'm sticking to hilt because it makes testing easier, because its good for well scaling project)
  • I agree about dependency injection library that there are just tools. They help us to reduce boilerplate code against to Manual DI. So, it's just easier to replace dependencies during testing.
  • Dagger hilt or koin are just a layer on top of that, a wrapper around DI
  • Makes certain things easier, no need to create ViewModelFactory, no need to invoke Constructor all around

Hilt

  • Generate factory class behind the scene
  • Hilt compiles time might give errors that you hard to debug. The feedback was not clear
  • Dagger (or Hilt) still shines for large and complex projects. It not only ensures compile-time safety but also prevents common DI pitfalls like missing or cyclic dependencies.
  • Hilt is a little faster, but I 100% agree with you. Koin wins on so many other topics, like simplicity and multi modules, Koin is clearly a no-brainer.
  • About using Koin or Dagger Hilt, I think the main reason why some developers prefer Hilt over Koin is just for compile time checks, that all dependencies are provided. So, you can find earlier any issues, especially in large projects it could be beneficial. But of course with any tool you can make a mess

Koin

  • Koin was not strictly a DI, its a service locator, like global map
  • Its run-time dependency injection
  • Can support KMP
  • Going forward Koin will win

Manual DI

  • Manual DI comes with boiler-plate code, but you are completely in control.
  • If your goal is to minimize build times and maintain full control, Manual DI is a valid choice. But if you're working on a complex, multi-module project, Hilt (or even Koin) is still a great tool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment