Koin es un framework de DI para Kotlin que funciona sin generación de código, reflexión ni anotaciones. La biblioteca utiliza DSL para describir módulos e inyecta dependencias a través de un contenedor ligero con soporte para Android, Ktor y Multiplatform. Según la documentación oficial de Koin, el framework proporciona módulos, alcances y soporte integrado para Jetpack Compose con un mínimo de boilerplate.
Puntos clave
Koin es un framework de DI para Kotlin escrito en lenguaje puro sin usar reflexión, anotaciones ni generación de código. A diferencia de Dagger Hilt, que requiere un procesador de anotaciones y generación de código en tiempo de compilación, Koin funciona completamente en tiempo de ejecución utilizando un DSL ligero para describir módulos.
La idea principal de Koin es proporcionar una API simple para registrar y resolver dependencias sin necesidad de aprender conceptos complejos de grafos de dependencias y árboles de componentes. El desarrollador describe qué clases están disponibles para el contenedor, y Koin las inyecta automáticamente a través del constructor o delegados perezosos by inject. El framework es totalmente compatible con Kotlin Multiplatform, lo que permite un enfoque DI único en Android, iOS y el lado del servidor.
Según la encuesta de la Comunidad de Desarrolladores Kotlin (2025), Koin se utiliza en el 31% de los proyectos Android comerciales, solo superado por Hilt (47%). La razón principal para elegirlo es la facilidad de configuración y la ausencia de necesidad de generación de código, lo que acelera la compilación del proyecto.
Elija Koin para proyectos medianos y grandes donde sea importante un inicio rápido del desarrollo, o para soluciones Kotlin Multiplatform donde Hilt no esté disponible por razones arquitectónicas.
Koin no utiliza reflexión ni generación de código — todos los registros se basan en funciones inline con tipos reified que sustituyen el tipo concreto en el cuerpo de la función en tiempo de compilación. Esto convierte a Koin en uno de los frameworks DI más ligeros en términos de tamaño final del APK: agregar Koin aumenta el tamaño de la aplicación solo entre 100 y 150 KB, mientras que Dagger Hilt añade unos 500 KB debido al código generado.
El contenedor Koin se inicializa mediante la función startKoin, que acepta una lambda con configuración. Dentro de esta lambda se describen los módulos con registros, el bloque de construcción principal de la lógica DI.
La función startKoin crea un contenedor global accesible desde cualquier parte de la aplicación a través de GlobalContext; sin embargo, en proyectos multimódulo se recomienda usar KoinApplication para crear contenedores aislados. En Android, se utiliza AndroidContext para la inicialización, que se vincula automáticamente al ciclo de vida de Application. Los módulos se registran mediante el parámetro modules, que acepta una lista de instancias de Module.
val networkModule = module {
single {
OkHttpClient()
}
single {
Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
}
}
startKoin {
modules(networkModule)
}
Cada módulo contiene definiciones mediante single (singleton) o factory (nueva instancia). Las definiciones pueden hacer referencia a otras dependencias registradas a través de get(), formando un grafo de inyección sin especificación explícita de tipos y sin código boilerplate.
Koin utiliza activamente funciones inline con parámetros reified para inferir el tipo del contexto. Esto permite escribir registros sin especificar explícitamente la clase: single { MyService() } determina automáticamente el tipo por el valor de retorno de la lambda.
A diferencia de Dagger, Koin no verifica el grafo de dependencias en tiempo de compilación — todos los errores se detectan en tiempo de ejecución al primer acceso a una dependencia no resuelta. Esto es un compromiso que simplifica significativamente el código y acelera las compilaciones, pero requiere cobertura de pruebas de la configuración DI. Muchos equipos eligen Koin precisamente por la velocidad de desarrollo y la simplicidad, a pesar de la falta de verificaciones en tiempo de compilación.
En la versión de Koin 3.5, apareció la verificación experimental del grafo en tiempo de compilación a través del plugin Koin Annotations. El desarrollador agrega las anotaciones @Module y @KoinComponent, y el plugin genera código de validación que se ejecuta durante la compilación. Sin embargo, la principal ventaja de Koin — la ausencia de generación de código — se pierde en este modo, por lo que la mayoría de los equipos continúan utilizando el enfoque DSL clásico con verificaciones en tiempo de ejecución mediante pruebas.
Koin proporciona varias formas de inyectar dependencias: by inject(), get() y paso directo por constructor. La elección depende del contexto de uso.
El delegado by inject es el método de inyección más común en ViewModels y fragmentos de Android. La dependencia se inicializa de forma perezosa, solo en el primer acceso a la propiedad. Esto es eficiente para servicios que consumen muchos recursos y que pueden no ser necesarios de inmediato.
class MainViewModel : ViewModel() {
private val repository: UserRepository by inject()
fun loadUsers() {
repository.fetchAll()
}
}
La función get devuelve una instancia de dependencia de inmediato. Se utiliza dentro de lambdas de fábrica durante el registro o cuando se necesita la dependencia en un contexto síncrono sin inicialización perezosa. A diferencia de by inject(), get() no admite carga perezosa y requiere que el contenedor ya esté inicializado en el momento de la llamada.
Scope en Koin es un mecanismo para vincular el ciclo de vida de las dependencias a un componente específico, como una Activity, Fragment o sesión personalizada. Esta es una funcionalidad clave para la gestión de memoria en aplicaciones Android.
La función scope dentro de un módulo crea un alcance que vive mientras vive el componente vinculado. Todas las dependencias registradas en el alcance se destruyen cuando se cierra, lo que evita fugas de memoria.
val userScope = module {
scope<UserSession> {
scoped {
UserRepository(get())
}
scoped {
SessionManager(get())
}
}
}
La función scoped registra una dependencia que solo existirá dentro del alcance. Cuando se cierra el alcance, todos los objetos scoped quedan disponibles para el recolector de basura.
single registra una única instancia para toda la aplicación con inicialización perezosa. Se utiliza para servicios sin estado: clientes de red, cachés, registradores.
factory crea una nueva instancia en cada llamada a get(). Se aplica a ViewModel, repositorios y objetos con estado donde se necesita una instancia nueva en cada acceso.
La integración de Koin en un proyecto Android es mínima: basta con agregar una dependencia en build.gradle y llamar a startKoin en Application.onCreate. Koin proporciona módulos para integrarse con Jetpack Compose, Navigation y WorkManager, lo que lo convierte en una alternativa completa a Hilt.
La biblioteca especial koin-android-compose permite inyectar dependencias directamente en funciones Composable a través de koinViewModel() y koinInject(). Esto elimina la necesidad de pasar el contenedor por los parámetros de cada pantalla y hace que el código de ViewModel sea más limpio mediante la vinculación automática al ciclo de vida.
Según Google I/O 2024, Jetpack Compose se convirtió en el framework principal para nuevos proyectos Android. Koin proporciona soporte nativo para Compose sin configuración adicional, vinculando automáticamente los alcances al ciclo de vida de ViewModel a través de koinViewModel() teniendo en cuenta el contexto de corrutinas.
Para pruebas, Koin proporciona las funciones koinTest y koinTestRule, que crean un contenedor de prueba aislado con módulos de prueba y lo cierran automáticamente después de completar la prueba. Esto garantiza el aislamiento de las pruebas y evita fugas de estado entre casos de prueba.
La integración de Koin con Jetpack Navigation se implementa a través del módulo koin-androidx-navigation. El ViewModel de cada pantalla recibe automáticamente las dependencias a través de by viewModel() con paso de SavedStateHandle para preservar el estado al rotar la pantalla y recuperarlo después de suspender la aplicación.
Para pruebas unitarias de ViewModel con Koin, se utiliza koinTestRule de la biblioteca koin-test-junit5 o koin-test-junit4. La regla crea un contenedor aislado con módulos de prueba antes de cada prueba y lo cierra automáticamente al finalizar, evitando fugas de estado entre casos de prueba. Las dependencias reales se reemplazan con mocks mediante MockK: un módulo con registros single
Una de las características clave de Koin 3.x es el soporte para Ktor para crear aplicaciones de servidor en Kotlin y Compose Multiplatform para aplicaciones de escritorio. Esto convierte a Koin en el único framework DI que cubre las tres plataformas de Kotlin sin cambiar el paradigma de inyección. El módulo koin-ktor permite registrar dependencias mediante install(Koin) en el bloque Application e inyectar servicios en las rutas a través de by inject() igual que en Android. Esto hace de Koin una solución DI universal para proyectos Kotlin de cualquier arquitectura, desde un cliente móvil hasta un backend de servidor.
La integración de Koin con Jetpack Navigation a través del módulo koin-androidx-navigation elimina la necesidad de crear manualmente un ViewModelProvider.Factory para cada pantalla. Para proyectos multimódulo, Koin admite la carga perezosa de módulos mediante loadKoinModules, lo que permite que cada módulo de funcionalidad conecte su configuración DI de forma independiente.
Preguntas frecuentes
Koin funciona en tiempo de ejecución sin generación de código ni anotaciones, lo que acelera las compilaciones pero no verifica el grafo de dependencias en tiempo de compilación. Hilt genera código en tiempo de compilación y detecta errores DI antes, pero requiere una configuración compleja y ralentiza la compilación.
Sí, Koin es totalmente compatible con Kotlin Multiplatform. La biblioteca koin-core funciona en todas las plataformas Kotlin, mientras que koin-android y koin-compose añaden capacidades específicas de plataforma para Android e iOS respectivamente.
Las dependencias circulares provocan StackOverflowError en tiempo de ejecución. Koin no las detecta automáticamente. La solución es refactorizar la arquitectura: extraer una interfaz común, usar el patrón Listener/Observer o romper el ciclo mediante una fábrica con inicialización diferida.
En Android, los alcances se pueden vincular al ciclo de vida de Activity o Fragment mediante AndroidScope. Cuando el componente se destruye, Koin cierra automáticamente el alcance correspondiente. En alcances personalizados (sesión de usuario), el cierre se realiza manualmente llamando a scope.close.
Utilice la función koinTest del módulo koin-test. Crea un contenedor aislado con módulos de prueba que se cierra automáticamente después de la prueba. Las dependencias reales se reemplazan con mocks mediante un módulo con Mockito o MockK.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también