Hola, soy Miguel y aquí les traigo este artículo.
Índice
¿Qué es la inyección de dependencia?
La inyección de dependencia es otra forma en que las clases adquieren referencias de otras clases. Por ejemplo, hay una clase BananaMilkshake
que puede requerir la clase Milk
.
Aquí, BananaMilkShake
depende de la clase Milk
. A menudo, estas clases obligatorias, como Milk
, se denominan dependencias.
La implementación de la inyección de dependencias le brinda las siguientes ventajas:
- Reutilización del código.
- Facilidad de refactorización.
- Facilidad de prueba.
Hay tres tipos de inyecciones de dependencia
- Crear los objetos necesarios en la propia clase (como con el objeto
Milk
de clase que se crea en la claseBananaMilkshake
). - Tomando los objetos requeridos de otro lugar (como el contexto en el componente de actividad de Android).
- Proporcionar el objeto de clase requerido como parámetro (la creación de esto puede ser a través de un constructor).
Tipos de inyección de dependencia
Tomemos el ejemplo anterior y veamos cómo funciona sin inyección de dependencia. Echar un vistazo:
class BananaMilkshake { private val milk = Milk() fun prepare() { milk.mixitup() } } fun main(args: Array) { val bananaMilkshake = BananaMilkshake() bananaMilkshake.prepare() }
Puede parecer fácil hacerlo porque es un ejemplo sencillo.
Aquí las clases BananaMilkshake
y Milk
están estrechamente acopladas, lo que no es bueno en el mundo de la programación. BananaMilkshake
también depende de Milk
durante las pruebas, lo que dificulta.
¿Qué tal cuando se complica, como cuando se requieren BananaMilkshake
varias clases, como se muestra a continuación?
class BananaMilkshake { private val milk = Milk() private val sugar = Sugar() private val penutButter = PenutButter() private val mixer = Mixer() fun prepare() { ... } } fun main(args: Array) { val bananaMilkshake = BananaMilkshake() bananaMilkshake.prepare() }
¿No parece abrumador? Y será más complicado en casos en tiempo real.
Básicamente, lo que hacemos aquí es crear una clase de nivel superior que está disponible para todas las clases, crear todos los objetos que necesitamos en ella y acceder a ellos desde diferentes clases cuando sea necesario. Echar un vistazo:
object ServiceGenerater { fun getMilk(): Milk = Milk() } class BananaMilkshake { private val milk = ServiceGenerater.getMilk() fun prepare() { milk.mixitup() } } fun main(args: Array) { val bananaMilkshake = BananaMilkshake() bananaMilkshake.prepare() }
Esto es lo que hicimos: tenemos una clase de objeto ServiceGenerater
, en la que creamos todos los objetos que necesitábamos, y cuando se requería BananaMilkshake
la clase Milk
, simplemente tomaba el objeto de ServiceGenerater
.
Problemas con los generadores de servicios:
- Mantener objetos de por vida es difícil, lo que puede provocar bloqueos en algún momento.
- Esto también complica las pruebas debido a que todas las dependencias deben ser de una clase.
- No podemos mantener el alcance de los objetos.
Hay dos formas en que podemos pasar por DI
.
- Inyección de constructor: de esta manera, podemos pasar todos los objetos necesarios a la clase en el constructor y usarlos. Echar un vistazo:
class BananaMilkshake ( leche val privada : Leche ) { divertido prepare () { milk.mixitup () } } fun main ( args : Array ) { val milk = Leche () val bananaMilkshake = BananaMilkshake (leche) bananaMilkshake.prepare () }
- Inyección de campo (o inyección de setter): el sistema crea una instancia de ciertas clases de framework de Android, como actividades y fragmentos, por lo que la inyección de constructores no es posible.
- Con la inyección de campo, las dependencias se instancian después de que se crea la clase. El código se vería así:
class BananaMilkshake { lateinit var milk: Milk fun prepare() { milk.mixitup() } } fun main(args: Array) { val bananaMilkshake = BananaMilkshake() bananaMilkshake.milk = Milk() bananaMilkshake.prepare() }
Elegir la técnica adecuada para su aplicación
Como se definió anteriormente, tenemos diferentes tipos de enfoques para lograr la inyección de dependencia.
Como recomendó el equipo de Android, si su aplicación contiene tres o menos pantallas, puede prescindir de la inyección de dependencia. Pero con más de tres pantallas, siempre se recomienda usar la inyección de dependencia.
Le resultará más fácil elegir DI
si tiene un conocimiento profundo de lo que ofrece:
- Reutilización: amedida que los proyectos se expanden, será más fácil crear múltiples implementaciones de las dependencias, pero con el control de inversión
DI
, las clases dependientes ya no controlan cómo se crean las dependencias. - Facilidad de refactorización: lacreación del objeto de dependencias se puede verificar en tiempo de compilación en lugar de ocultarla en tiempo de ejecución.
- Facilidad de prueba: con
DI
, no será una clase la que cree dependencias, por lo que será más fácil de probar.
Inyección de dependencia en Android
DI
en Android no es diferente de otros. Aquí, los componentes de Android, como actividades, fragmentos y otros, dependen de instancias de Retrofit, Glide y otros. En Android, usamos Dagger para mantener DI
.
La principal ventaja de usar Dagger es que generará el código para crear los objetos requeridos en tiempo de compilación. Esto tiene dos ventajas principales.
Uno no es más errores en tiempo de ejecución, ya que Dagger es una biblioteca estática en tiempo de compilación. La otra es que ya no necesita escribir todo el código para mantener las dependencias.
En resumen, Dagger proporciona administración de dependencias en tiempo de compilación sin código repetitivo para escribir.
Historia de Dagger y sus ventajas
Dagger es una biblioteca de inyección de dependencias creada por desarrolladores en Square en 2012. Dagger1 usó los conceptos de reflexiones para crear instancias de las clases y dependencias.
Después de eso, colaboraron con los desarrolladores de Google para construir Dagger2 eliminando reflejos, que son muy poderosos. Dagger2 se basa en un procesador de anotaciones.
Dagger le libera de escribir código repetitivo tedioso y propenso a errores al:
- Creación del código
AppContainer
(gráfico de aplicación) que implementó manualmente en la secciónDI
manual. - Creación de fábricas para las clases disponibles en el gráfico de la aplicación. Así es como las dependencias se satisfacen internamente y también por qué no necesitamos escribir todo el código repetitivo.
- Reutilizar una dependencia o crear nuevas instancias de un tipo en función de cómo configure el tipo mediante ámbitos.
- Daggers también se preocupa por la gestión de la memoria liberando los objetos que ya no tienen uso.
Terminología
- @Módulo: Esto se usa en las clases que crean los objetos de las clases de dependencia.
- @Proporciona: Se usa en los métodos que están dentro de la clase del módulo y proporciona las dependencias.
- @Inyectar: Esto se usa en un constructor, campo o método donde se solicita la dependencia.
- @Componente: La clase del módulo no proporcionará las dependencias directamente, sino que creará una interfaz que actúa como puente entre
@Module
y@Inject
. - @Único: Esto también se usa en métodos en el módulo, pero, específicamente, el objeto de dependencia solo debe crearse una vez.
Integración
La integración de Dagger2 es tan fácil como cualquier otra biblioteca; solo necesita incluir el siguiente código en el archivo build.gradle
.
def dagger: '2.17' //Dagger implementation "com.google.dagger:dagger-android:$dagger" implementation "com.google.dagger:dagger-android-support:$dagger" kapt "com.google.dagger:dagger-compiler:$dagger" kapt "com.google.dagger:dagger-android-processor:$dagger"
Configuración de Dagger2
Como dije, habrá clases de módulo en las que habrá métodos @Provides
que devuelvan dependencias, por lo que nuestro primer trabajo es crear las clases de módulo. Veamos cómo hacerlo.
Aquí voy a crear una instancia de Retrofit
para la que necesito instancias de OkHttp
y Moshi
. Junto con eso, voy a crear la instancia ApiService
de clase para invocar una llamada de servicio. Echar un vistazo:
@Module class NetworkModule { @Provides fun provideOkHttpClient(preferenceHelper: PreferenceHelper, moshi: Moshi): OkHttpClient { val logging = HttpLoggingInterceptor() if (BuildConfig.DEBUG) { logging.level = HttpLoggingInterceptor.Level.BODY } else { logging.level = HttpLoggingInterceptor.Level.NONE } val okHttpBuilder = OkHttpClient.Builder() .addInterceptor(logging) .authenticator( TokenAuthenticator( preferenceHelper, moshi ) ) .addInterceptor(HeaderInterceptor(preferenceHelper)) .addNetworkInterceptor(StethoInterceptor()) return okHttpBuilder.build() } @Provides @Singleton fun providesMoshi(): Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() @Provides @Singleton fun provideRetrofit(okHttpClient: OkHttpClient, moshi: Moshi): Retrofit { return Retrofit.Builder() .baseUrl("https://fundaa.in/") .client(okHttpClient) .addConverterFactory(MoshiConverterFactory.create(moshi)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build() } @Provides @Singleton fun provideApiService(retrofit: Retrofit): APIService { return retrofit.create(APIService::class.java) } }
Podemos crear tantos módulos como queramos, y la clave es mencionar todos los módulos de la clase appcomponent
, que es nuestro siguiente paso.
Antes de eso, ¿por qué necesitamos diferentes clases de módulos? ¿No podemos incluir todas las dependencias en una clase de módulo?
Por supuesto, pero declarar todas las redes, datos y otras dependencias en un módulo creará confusión cuando el proyecto crezca, por lo que sería más claro y más fácil de entender si creamos diferentes clases de módulo para diferentes tipos de dependencias.
Ahora que hemos creado instancias de las dependencias de red necesarias, el siguiente paso sería crear AppComponent
, que actúa como un puente entre module
y inject
.
@Singleton @Component( modules = [DataModule::class, AppModule::class, ViewModelModule::class, AndroidSupportInjectionModule::class, AndroidInjectionModule::class, AppActivityBindingModule::class] ) interface AppComponent : AndroidInjector<FundaaApplication> { @Component.Builder interface Builder { @BindsInstance fun application(application: Application): AppComponent.Builder fun build(): AppComponent } }
Si observa claramente el código anterior, utilicé seis clases de módulo para proporcionar todas las dependencias requeridas y las declaré todas en mi interfaz AppcCmponent
.
Ahora, en este punto, hemos terminado con todo el trabajo; lo único que queda fuera es vincularlo con AppComponent
el ciclo de vida de la aplicación.
Antes de hacerlo, tenemos que r
ebuild el proyecto de manera Daga creará todo el código de la plancha de caldera en el fondo.
Después de una reconstrucción exitosa, es hora de declarar el AppComponent
en la clase de aplicación. Echar un vistazo:
class TestApplication : DaggerApplication() { lateinit var appComponent: AppComponent override fun onCreate() { super.onCreate() Stetho.initializeWithDefaults(this) if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree()) } } override fun applicationInjector(): AndroidInjector<out DaggerApplication> { appComponent = DaggerAppComponent.builder().application(this).build() return appComponent } }
Eso es todo lo que necesitamos hacer para configurar la inyección de dependencia a través de Dagger2.
Uso
Ahora que hemos terminado con la configuración básica y todo, lo único que queda es inyectar las dependencias en las clases requeridas y usarlas. Veamos cómo inyectar una instancia de actualización.
// field injection @Inject lateinit var retrofit : Retrofit
Esto se denomina inyección de campo porque estamos inyectando la dependencia requerida a través de un campo conocido como variable.
Y también podemos hacer esto a través de la inyección del constructor, donde pasaremos las dependencias requeridas a través del constructor de la clase. Echar un vistazo:
class RepositoryImpl @Inject constructor( private val apiService: APIService ) : Repository { override fun login(params: Map<String, String>): Single<AuthResponse> { return apiService.authorisation(params) } }
Dagger
en aplicaciones multimódulo
Para comprender este bloque, primero debe comprender qué son las aplicaciones multimódulo.
Cuando iniciamos una nueva aplicación, es normal que no sepamos todo lo que tendremos que hacer con el tiempo. Esto significa que a medida que pase el tiempo, su gerente de producto presentará diferentes ideas para implementar en la aplicación.
Es bastante normal en el mundo del software. En general, lo que hacemos en esta situación es crear aquellas características en el mismo módulo que, con el tiempo, ocasionan serios problemas en el mantenimiento del código.
Las nuevas características que se requieran se implementarán como nuevos módulos en el proyecto, por lo que el mantenimiento del código será más sencillo y mucho más fácil de manejar, incluso cuando varios desarrolladores estén trabajando en el proyecto.
Ahora que sabemos qué son los proyectos de varios módulos, es hora de abordar la inyección de dependencias entre módulos.
Es común que se requieran ciertos recursos en cada módulo de su aplicación, como la instancia de Retrofit
para llamadas de servicio o la SharedPreferenceinstancia
.
Por lo tanto, debemos compartir los recursos entre los módulos en lugar de crear una nueva instancia; así es como se ve la inyección de dependencia en los proyectos de varios módulos.
Veamos cómo lo implementamos.
Lo que hacemos básicamente es crear un componente separado para cada módulo de funciones. Digamos que tenemos un módulo de inicio de sesión.
Como dije, necesitamos tener un componente de inicio de sesión, que se crea como se muestra a continuación:
@Component(dependencies = [AppComponent::class]) interface LoginComponent { @Component.Factory interface Factory { // Takes an instance of AppComponent when creating // an instance of LoginComponent fun create(appComponent: AppComponent): LoginComponent } fun inject(activity: LoginActivity) }
Y así es como implementamos componentes a nivel de módulo.
Ahora, veamos cómo usarlos.
class LoginActivity: Activity() { // You want Dagger to provide an instance of LoginViewModel from the Login graph @Inject lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { // Gets appComponent from MyApplication available in the base Gradle module val appComponent = (applicationContext as MyApplication).appComponent // Creates a new instance of LoginComponent // Injects the component to populate the @Inject fields DaggerLoginComponent.factory().create(appComponent).inject(this) super.onCreate(savedInstanceState) // Now you can access loginViewModel } }
Espero que te haya sido de utilidad. Gracias por leer este artículo.
Añadir comentario