Koin Dependency Injection Framework
repository·main·Indexed 27 days ago
https://github.com/insertkoinio/koinA pragmatic and lightweight dependency injection framework for Kotlin and Kotlin Multiplatform (KMP). Koin provides a powerful DSL and an annotation-driven approach, both supported by a dedicated compiler plugin that ensures compile-time safety for dependency graphs. It is compatible with JVM, Android, Compose, iOS, Desktop, Web (JS/Wasm), and Ktor. The framework supports dynamic module loading and offers an official IDE plugin for IntelliJ IDEA and Android Studio.
What's inside Koin
- Koin is a pragmatic, lightweight dependency injection framework designed for Kotlin and Kotlin Multiplatform (KMP) developers. It provides a simple and powerful DSL for managing dependencies across various targets.
Compare Koin with Hilt/Dagger
mainKoin offers a choice between a DSL and Annotations, both of which are powered by the same Compiler Plugin for compile-safety. Unlike Hilt/Dagger, which is strictly static, Koin provides runtime flexibility, allowing for dynamic module loading, unloading, and lazy background loading.Understand Android Scope Lifecycles
mainKoin provides Android-specific scopes to align dependency lifecycles with Android components. This prevents memory leaks and ensures proper resource management.
Scope Types
Scope Type Lifetime Survives Rotation DSL Annotation Application Entire app ✅ Yes single { }@SingletonActivity Activity lifecycle ❌ No activityScope { }@ActivityScopeActivity Retained Until finish()✅ Yes activityRetainedScope { }@ActivityRetainedScopeFragment Fragment lifecycle ❌ No fragmentScope { }@FragmentScopeViewModel ViewModel lifecycle ✅ Yes viewModelScope { }@ViewModelScopeScope Hierarchy
Child scopes can access parent scope definitions, but parents cannot access child scopes:
Application Scope$\rightarrow$Activity Retained Scope$\rightarrow$Activity Scope$\rightarrow$Fragment Scope.Note:
ViewModel Scopecan access parent scopes but cannot accessActivityorFragmentscopes to prevent memory leaks.Understand Koin Compile-Time Safety levels
mainThe Koin Compiler Plugin validates your dependency graph at three distinct levels during compilation to catch missing dependencies, qualifier mismatches, and broken call sites before runtime.
- A2 — Per-Module (Early Feedback): Validates a module's definitions against its own definitions, explicitly included modules (via
@Module(includes = [...])), and sibling modules sharing the same@Configurationlabel. - A3 — Full Graph (Complete Guarantee): Validates the entire assembled graph at
startKoin<T>(), including cross-module dependencies and definitions from external JARs. - A4 — Call-Site Validation: Intercepts every
koinViewModel<T>(),get<T>(), andinject<T>()call to ensure the requested typeTexists in the assembled graph, providing exact file, line, and column numbers on failure.
- A2 — Per-Module (Early Feedback): Validates a module's definitions against its own definitions, explicitly included modules (via
Choose a Koin dependency injection style
mainKoin offers two primary ways to define dependencies, both of which are first-class citizens and fully supported by the Koin Compiler Plugin for compile-time safety.
DSL Style
Use the Kotlin DSL for a pure Kotlin approach. This is useful for developers who prefer explicit module definitions.
Annotation Style
Use annotations for a more familiar pattern (similar to Hilt or Dagger) with less ceremony. This style is ideal for automatic component scanning and module discovery.
// DSL Style val appModule = module { single<Database>() single<ApiClient>() single<UserRepository>() viewModel<UserViewModel>() } // Annotation Style @Singleton class Database @Singleton class ApiClient @Singleton class UserRepository( private val database: Database, private val apiClient: ApiClient ) @KoinViewModel class UserViewModel(private val repository: UserRepository) : ViewModel()Summary of Instrumented Testing Strategies
mainKey takeaways for implementing instrumented tests with Koin in Android:
- Configuration: Use a Custom Test Application or Test Rules to manage Koin configuration.
- Module Overriding: Use
override = trueor provide test-specific modules to replace production dependencies. - Test Doubles: Prefer Fakes over Mocks for better performance in instrumented environments.
- Isolation: Ensure strict test isolation by cleaning up dependencies between tests.
- Compose Integration:
KoinContextallows Koin to work seamlessly with Jetpack Compose testing. - Verification: Use the Koin Compiler Plugin (compile-time) or
verify()(runtime) to catch configuration errors early.
Choose a Koin approach: DSL or Annotations
mainKoin provides two primary ways to define dependencies. You can choose based on your preference for a Kotlin DSL or a more annotation-driven style:
- Koin Compiler Plugin (Recommended): Provides compile-time safety for both DSL and Annotations.
- DSL: Uses functions like
single<T>(),factory<T>(), andviewModel<T>(). - Annotations: Uses
@Singleton,@Factory, and@KoinViewModel. It auto-detects dependencies and provides compile-time safety.
- DSL: Uses functions like
- Classic DSL: Uses syntax like
singleOf(::MyService)orsingle { MyService(get()) }. This is fully supported and works with any Kotlin version.
Note: The KSP Processor (
koin-ksp-compiler) is deprecated. You should migrate to the Koin Compiler Plugin.- Koin Compiler Plugin (Recommended): Provides compile-time safety for both DSL and Annotations.
Understand Dependency Injection (DI) patterns
mainDependency Injection is a design pattern where objects receive their dependencies from external sources rather than creating them internally. This promotes loose coupling, better testability, and cleaner architecture.
Three ways to provide dependencies:
- Constructor Injection (Recommended): Dependencies are passed through the constructor. This is the preferred approach in Koin because it makes dependencies explicit, immutable, and allows for testing without requiring Koin in unit tests.
- Field Injection: Dependencies are injected into class properties. This is useful for Android framework classes (like
Activity,Fragment, orService) where you do not control the constructor. - Method Injection: Dependencies are passed through methods. This is typically used for optional dependencies or dependencies that change during an object's lifetime.
R8 / ProGuard compatibility with Koin
mainKoin's core dependency resolution is R8-safe. Functions like
get<T>(),inject<T>(), and the*Ofbuilders (e.g.,singleOf,factoryOf,viewModelOf) resolve dependencies at compile time using reified types. On Android/JVM, Koin keys the registry byClass.getName(), which is stable under R8.Because Koin does not use runtime reflection over your constructors, you do not need to add ProGuard
-keeprules for your Koin definitions, ViewModels, or their constructors on Koin's behalf. Koin automatically shipsconsumer-rules.proin its Android AARs to handle its own internals.Explore Koin Compiler Plugin features and references
mainThe Koin Compiler Plugin offers several ways to interact with the dependency injection framework:
- DSL Usage: Use the Koin DSL for defining modules. Refer to the DSL Reference for complete documentation.
- Annotations Usage: Use Koin Annotations for a more declarative approach. Refer to the Annotations Reference for complete documentation.
- Migration: If you are upgrading from KSP to the Compiler Plugin, follow the Migration Guide.
Understand Koin Definition Types
mainKoin uses different definition types to manage the lifecycle and creation of dependencies. Choose the type based on your use case:
Type DSL Annotation Lifecycle Use Case Singleton single()@SingletonOne instance for app lifetime Services, repositories, databases Factory factory()@FactoryNew instance each time Presenters, use cases, stateful objects Scoped scoped()@ScopedOne instance per scope Activity-bound, session-bound objects ViewModel viewModel()@KoinViewModelAndroid ViewModel lifecycle ViewModels Choose a Koin starting method
mainSelect a starting method based on your application type:
startKoin { }: Use for standard applications. This registers Koin in theGlobalContext.koinApplication { }: Use for testing or building SDKs where you need an isolated Koin instance.koinConfiguration { }: Use for configuration within Compose or Ktor.startKoin<T>(): Use for typed startup when using the Koin Compiler Plugin.