Inyección de dependencias en Android con Koin. Guía básica

Hasta hace no mucho las soluciones acerca de poder hacer inyección de dependencias en Android se reducían prácticamente a una, Dagger. Creo que todo el mundo utilizaba esta con lo bueno y lo malo que tiene.

Digo bueno y malo, porque la curva de aprendizaje no era muy suave y todo el código que hay que escribir para utilizarla, desde mi punto de vista, es un poco extenso. Por supuesto tiene sus cosas buenas.

Con la llegada de Kotlin y las funcionalidades que nos brinda el propio lenguaje, llegó Koin, un framework que nos permite hacer una inyección de dependencias en Android mucho más ágil a la hora de crear todo lo necesario y con mucho menos código que el necesario en Dagger. A esto le unimos que su utilización es muy sencilla y por tanto su curva de aprendizaje es casi instantánea. 

Por supuesto también tiene sus cosas malas, o menos buenas, como que por ejemplo no es un inyector de dependencias estrictamente hablando, es un Service Locator.

Service Locator vs Dependency Injector

Dependency Injector: Las dependencias que una clase necesita para inicializarse se las provee otra clase del módulo del inyector de dependencias y esta clase no se tiene que preocupar por instanciar nada.

Service Locator: En este caso es la propia clase que se instancia la que tiene que preocuparse por pedir al Service Locator las clases que le hacen falta para instanciarse.

En ambos casos las librerías, Dagger o Koin, nos proveen formas tanto de crear el grafo de dependencias de cada clase como de hacer la inyección en el lugar preciso.

Gracias a la inyección de dependencias de una u otra forma, lo que sí obtenemos es la facilidad a la hora de realizar test a nuestro software, una escalabilidad mayor y mucho más fácil, un acoplamiento menor y ponemos en práctica uno de los principios SOLID.

Configurando Koin en Android

Vamos a meternos en faena. Y lo primero es añadir las dependencias necesarias en el archivo build de nuestra aplicación. En la web de Koin está indicada la versión más reciente de esta librería. A día de hoy es la “2.1.5”.

En el archivo build.gradle del proyecto añadimos, si no lo tenemos ya, el repositorio jcenter.

repositories {
    jcenter()    
}

En el build.gradle de la aplicación añadimos:

dependencies {
    implementation 'org.koin:koin-androidx-scope:2.1.5'
    //necesario para inyecciones de clases que extiendan de ViewModel
   implementation "org.koin:koin-androidx-viewmodel:2.1.5"
}

Utilizando Koin en Android

Para utilizar Koin necesitamos crear una clase que herede de Application ya que ahí es donde vamos a inicializar nuestro inyector. Creamos la clase y la registramos en el Android Manifest.

Una vez hecho ya podemos inicializar Koin. Lo inicializamos en el método onCreate:


class KoinApplication: Application() {


    override fun onCreate() {
        super.onCreate()
        startKoin{
            //nos ayuda a ver posibles errores 
            androidLogger()
            // por aquí le pasamos al injetor la referencia del contexto de nuestra app para que después él nos la provea donde nos haga falta
            androidContext(this@KoinApplication)
            // añadimos nuestros módulos donde definimos las  dependencias
            modules(presenterModule, providersModule)
        }
    }
}

Clases de nuestro proyecto

Nuestro proyecto de ejemplo está compuesto por una serie de clases que nos van a servir para ver como utilizar Koin. Tenemos una Activity – MainActivity- la cual necesita que le inyectamos  un presenter – MainPresenter. 

También para ver más ejemplos le inyectamos otro presenter que requiere cierto número de parámetros.

class MainActivity : AppCompatActivity() {

    private val presenter: MainPresenter by inject()

    private val otherPresenter: OtherMainPresenter by inject { parametersOf(this@MainActivity, 3) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        fetchData()
    }

    private fun fetchData() {
        val result = presenter.fetch()
        showAlertWithData(result)
    }

    private fun showAlertWithData(message: String) {

        val alertBuilder = AlertDialog.Builder(this).apply {
            title = "Hello Koin"
            setMessage(message)
        }
        alertBuilder.create().show()
    }
}

Este presenter a su vez necesita como dependencia otra clase que será nuestro proveedor de datos, ApiProvider. 

class MainPresenter(private val apiProvider: ApiProvider) {
    fun fetch() = apiProvider.fetchData()
}

La otra clase de ejemplo que también inyectamos en la MainActivity tiene estas dependencias:


class OtherMainPresenter(
    private val apiProvider: ApiProvider,
    private val activity: MainActivity,
    private val number: Int
) {

    fun fetch() = apiProvider.fetchDataWithActivity(number)
}

Todas estas clases requieren que les pasemos por contructor una serie de parámetros entre ellos un contexto de la aplicación, una referencia a la activity y algunos campos adicionales.

Por último la clase ApiProvider tiene como dependencia el contexto de la aplicación.

class ApiProvider(private val context: Context) {

    fun fetchData() = "Remote data from webservice"

    fun fetchDataWithActivity( number: Int) = "Remote other data from webservice"

    fun otherFetchWithContext() : String{
        //si aquí necesitásemos el contexto
        //solo hay que utilizarlo ya que lo tenemos disponible gracias a la
        //inyección por el contructor
        return context.getString(R.string.app_name)
    }
}

Creación de módulos en Koin

Los módulos de Koin son los medios donde definimos que dependencias tiene cada clase en la que queramos obtener una instancia concreta de otras clases de nuestra app.

Con esta definición que hacemos Koin será capaz de resolver las dependencias necesarias en cada caso e inyectarlas en elemento oportuno.

La definición de un módulo es muy simple:

val presenterModule = module{
   single { MainPresenter(get()) }
   single { (activity: MainActivity, number: Int) -> OtherMainPresenter(get(), activity, number)  }
}

val providersModule = module{
    single { ApiProvider(androidApplication()) }
}

En la primera línea creamos el módulo que vamos a utilizar para definir las  dependencias de los presenters de nuestra aplicación. Podemos crear tantos módulos como queramos, es más, sería conveniente ir creando módulos conforme vaya creciendo nuestra aplicación para tener el código lo más ordenado posible.

En primera definición de dependencia utilizamos la palabra single, con ella, estamos diciendo que nos cree un singleton de la clase MainPresenter. Si queremos que sea una instancia distinta en cada instanciación, valga la redundancia, entonces utilizaremos factory.

La clase MainPresenter requiere un parámetro por el constructor, una instancia de la clase ApiProvider, que se lo estamos inyectando al llamar al método get().

Al llamar a este método get(), el propio inyector buscará internamente que clase le hace falta instanciar según la definición que hemos hecho en los módulos, y él creará la instancia de ella y se la pasará cuando sea necesario.

En la segunda línea dentro del modulo estamos definiendo las dependencias de la clase OtherMainpresenter. Esta clase necesita de tres parámetros a pasar por el constructor: una instancia de ApiProvider, una referencia de la Activity, y un parámetro number de tipo entero.

Para definir esto en Koin, utilizamos los parámetros que están a la izquierda del símbolo -> Es una función que recibe como parámetros una Activity de tipo MainActivity, y un entero de nombre number.

Por último la instancia de la clase ApiProvider se configura llamando al método get() igual que hicimos antes.

Inyectando dependencias en Koin

Con todos los módulos creados con las definiciones de las inyecciones, solo nos queda inyectarlas donde nos haga falta. Todas las creaciones de clases que sean necesarias por constructor las resolverá el inyector y únicamente tendremos que hacer es inyectar en donde se inicializa todo el grado de inyecciones.

En nuestro caso en la MainActivity, por tanto aquí tenemos que inyectar una instancia del presenter para poder acceder él.

private val presenter : OtherMainPresenter by inject { parametersOf(this@MainActivity, 2) }

Con esto, estamos diciéndole  a Koin que nos cree una instancia de la clase MainPresenter cuando la clase MainActivity se cree. Además le pasamos los parámetros que configuramos en el módulo y que son necesarios, una referencia a la Activity y un campo de tipo entero.

Unicamente con esta configuración y esta llamada a la inyección en la activity, se genera automáticamente todas las dependencias de todas las clases que entran en juego.

Todo el código lo tenéis disponible en Github

Comparte si lo consideras interesante
  •  
  •  
  •  
  •  
  •  
  •  
  •  

1 thought on “Inyección de dependencias en Android con Koin. Guía básica

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.