¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
dev
Pablo Antonio Fuente Hace 18 minutos Cargando comentarios…
¿Te cansa la complejidad de la inyección de dependencias? Koin revoluciona el desarrollo en Kotlin con un enfoque ligero y pragmático. Permite organizar tu proyecto con una sintaxis elegante y mínima configuración. Descubre cómo este framework puede simplificar tu código mientras mantienes el control total sobre tus dependencias.
Es un patrón de diseño de aplicaciones utilizado en programación orientada a objetos para facilitar la creación y el manejo de las instancias de tus objetos en una aplicación. En lugar de que un objeto sea responsable de crear sus propias dependencias (las instancias de otros objetos que necesita para funcionar), las dependencias se proporcionan desde el exterior, generalmente a través de la configuración o la inyección directa.
Este enfoque tiene varios beneficios:
Por añadir un último detalle sobre la DI (Dependency Injection), está totalmente relacionada con los principios SOLID que deben aplicarse en los desarrollos de aplicaciones.
Los creadores de Koin, Arnaud Giuliani y Laurent Broudoux, buscaban resolver varios problemas específicos que encontraban con las soluciones de inyección de dependencias existentes en el ecosistema Java/Kotlin:
En resumen, los creadores de Koin buscaban una solución de inyección de dependencias que fuera:
Varias cosas hacen que Koin sea tan especial. En primer lugar, su enfoque declarativo y su sintaxis simple hacen que sea muy fácil de entender y utilizar. En lugar de depender de configuraciones XML o clases complejas de configuración, Koin utiliza DSLs (Domain-Specific Languages) en Kotlin para definir nuestros componentes y sus dependencias de manera concisa y legible.
Además, Koin es extremadamente liviano y tiene una huella mínima en nuestras aplicaciones, lo que significa que no añade una carga significativa al tamaño de nuestra aplicación ni a su rendimiento. Esto lo hace ideal tanto para aplicaciones pequeñas como para proyectos a gran escala.
Otra característica notable de Koin es su integración perfecta con el ecosistema de Android, proporcionando soporte específico para Android, lo que facilita la inyección de dependencias en actividades, fragmentos, servicios y otros componentes de la plataforma. Soporta perfectamente el uso de Jetpack Compose directamente.
Además, la librería es multiplataforma y soporta ser usada en entornos iOS, web, jvm.
Por último, pero no menos importante, Koin promueve una arquitectura desacoplada y modular, lo que facilita las pruebas unitarias y la refactorización del código. Esto significa que podemos escribir pruebas más efectivas y mantener nuestro código más limpio y mantenible a lo largo del tiempo.
Un punto importante que hay que conocer sobre Koin es su naturaleza inicial.
Principalmente, se asemeja más a un Service Locator que a un inyector de dependencias. Esto puede parecer raro si estamos diciendo todo el rato que se usa para precisamente esto, pero la diferencia entre librerías como Dagger2 o Hilt y Koin es que las primeras son puras, ya que generan código Java al compilar la aplicación y así proporcionar instancias de las dependencias. En tiempo de compilación podemos saber si hemos hecho algo mal o dejado sin declarar en alguna parte.
Koin centraliza la configuración de las dependencias en un lugar específico. Utiliza un componente llamado module donde se definen todas las dependencias y sus proveedores. Cuando un componente necesita una dependencia, solicita esta dependencia a través de Koin, que actúa como un contenedor de servicios. Koin busca en su registro y proporciona la instancia adecuada del servicio solicitado. Es por ello que no podemos saber de antemano si no hemos declarado algo, nos fallará directamente en ejecución.
Para solucionar esto último hay tareas y maneras de hacer un barrido previo en compilación para ver si algo está mal creado y detectarlo.
Para empezar a usar Koin en nuestro proyecto, lo primero es la importación de librerías.
Estas librerías son las que forman todo el ecosistema Koin, pero no todas las vamos a necesitar inicialmente. Configuraremos las primeras y después, según vayamos necesitando, las iremos añadiendo.
Lo primero que vamos a hacer es configurar Gradle y, como Koin usa BOM en sus librerías (desde la versión 3.5.0), lo tenemos fácil pues solo tenemos que añadir koin-bom y koin-core a nuestra aplicación:
Aunque lo mejor es usar ‘version catalogs’ y tenerlo de la siguiente manera:
Y en las dependencias:
De esta forma, siempre estará actualizado a la versión que tengamos en koin-bom.
Para usar Koin en Android necesitaremos algunas librerías más. La primera de todas es koin-android:
También la podemos configurar en BOM. Ahora ya podremos cargar Koin en nuestra aplicación de la siguiente manera:
También podríamos necesitar algunas funcionalidades más en nuestra app, como por ejemplo:
Pensemos primero en un proyecto actual de Android donde tenemos un Application (clase aplicación donde vamos a inicializar Koin), donde habrá un Activity (o muchos, según el proyecto), con Fragments (que, si los hay, suelen ser muchos), con ViewModels (si usamos MVVM) y después puede haber casos de uso, repositories, datastore, room, conexiones remotas a servicios, etc.
En ese proyecto tendremos que usar claramente koin-android y, de esta manera, empezaremos a definir nuestra DI de proyecto. Crearemos un directorio con nombre DI, donde incluiremos todos los ficheros con los módulos de inyección de Koin que vamos a necesitar (a veces es un solo módulo, a veces es mejor separar en varios si es que hay mucho que inyectar).
En ese AppModule.kt tendremos algo así:
val appModule = module {
}
Aquí vamos a ir añadiendo nuestras definiciones de inyección de dependencias o de objetos que necesitemos, usando el DSL que Koin nos proporciona.
Supongamos que tenemos un data class de esta manera:
data class Film(
val characters: List<String>,
val director: String,
val title: String
)
interface StarWarsRepository {
suspend fun getAllfilms(): Result<List<Film>>
}
La clase que implementa este repositorio sería algo así:
class StarWarsRepositoryImpl(
private val starWarsApiService: StarWarsApiService
): StarWarsRepository
{
override suspend fun getAllfilms(): Result<List<Film>> = tryCall {
starWarsApiService.getAllfilms().map { it.map() }
}
}
Al ser un repositorio, esta sería un singleton y, por tanto, en el fichero AppModule.kt quedaría definido de la siguiente manera:
val appModule = module {
single<StarWarsRepository> { StarWarsRepositoryImpl(get()) }
}
StarWarsApiService sería el servicio, como puede ser Retrofit, que hace la llamada al servidor para recuperar la información y que estaría definido también en DI.
Es decir, la palabra ‘single’ en Koin nos indica que esa clase será un singleton y que, cada vez que alguien la pida, usará la misma instancia del objeto que haya creado la primera vez (siempre que no haya sido destruida).
Así pues, lo que esa línea indica es que si alguien pide un objeto tipo StarWarsRepository, Koin devolverá un objeto de ese tipo usando la clase StarWarsRepositoryImpl.
Si tuviéramos un caso de uso, lo podríamos definir de la siguiente forma:
interface GetAllFilmsUseCase {
suspend operator fun invoke(): Result<List<Film>>
}
class GetAllFilmsUseCaseImpl(
private val repository: StarWarsRepository
) : GetAllFilmsUseCase
{
override suspend operator fun invoke(): Result<List<Film>> = repository.getAllfilms()
}
AppModule.kt quedaría así:
val appModule = module {
factory<GetAllFilmsUseCase> { GetAllFilmsUseCaseImpl(get()) }
single<StarWarsRepository> { StarWarsRepositoryImpl(get()) }
}
En este caso, vemos que los casos de uso suelen ser ‘factory’. Se generarán tantos como sea necesario y las veces se pida, aunque la realidad es que muchas veces los casos de uso son únicos y podrían ser single perfectamente.
También vemos que la implementación del caso de uso necesita un parámetro y, casualmente, es del tipo StarWarsRepository. Es ahí donde Koin hace su magia, ya que cuando se pide crear un objeto, encuentra que la clase GetAllFilmsUseCaseImpl necesita un parámetro y que, como tiene indicado ahí, hay un get(). Ese get() indica que tiene que buscar entre el resto de definiciones un objeto de la clase que corresponda y es cuando crea, con el single, la clase StarWarsRepository y la inyecta como parámetro en el caso de uso.
Debemos entender que habrá que poner tantos get() como parámetros sean necesarios para crear la clase que corresponda.
Si ahora disponemos de un ViewModel que utiliza ese caso de uso, entonces tendríamos algo así:
class FilmsViewModel(
private val getAllFilmsUseCase: GetAllFilmsUseCase
) : ViewModel()
{
………
}
Y la definición sería:
val appModule = module {
viewModel { FilmsViewModel(get()) }
factory<GetAllFilmsUseCase> { GetAllFilmsUseCaseImpl(get()) }
single<StarWarsRepository> { StarWarsRepositoryImpl(get()) }
}
En este caso, la palabra viewModel es la encargada de indicar que deberá ser inyectada de una determinada manera cuando sea solicitada. Para pedir un objeto de la clase FilmsViewModel, lo haremos de la siguiente manera:
class MainActivity : AppCompatActivity() {
private val viewModel: FilmsViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//...
}
}
Ese by viewModel()* es el que localizará un objeto del tipo FimsViewModel y lo inyectará directamente en ese Activity.
Claramente, Koin localiza objetos si ya están creados (si son singleton) y, si no están creados, los instancia. Es por eso que a veces, al ir a llamar a un objeto, la app falla en tiempo de ejecución, producido por una mala definición en el fichero module de los objetos que deben crearse.
En el caso de un Fragment, todo se haría igual que en el caso del Activity anterior para tener acceso al viewmodel correspondiente.
Por último, nuestra clase de aplicación quedaría de la siguiente manera:
class CustomApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this@CustomApplication)
modules(
listOf( //listOf por si hay mas de un modulo.
appModule
)
)
}
}
}
Estaríamos definiendo un logger para que Koin nos mande trazas al logcat y también estaríamos indicando a Koin que el contexto a usar en el contenedor es el de la propia aplicación.
Koin representa una alternativa refrescante en el mundo de la inyección de dependencias para desarrolladores Kotlin. Su enfoque minimalista elimina la necesidad de anotaciones complejas y generación de código, permitiéndote centrarte en lo que realmente importa: desarrollar tu aplicación. A lo largo de este viaje, hemos descubierto cómo su sintaxis intuitiva y su facilidad de implementación pueden transformar la arquitectura de tus proyectos.
Como perfiles de desarrollo, valoramos las herramientas que simplifican nuestro trabajo sin sacrificar potencia. Koin demuestra que la inyección de dependencias no tiene por qué ser complicada para ser efectiva. Si buscas una solución pragmática y 100% Kotlin, Koin merece sin duda un lugar en tus proyectos.
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.