Notas sobre dagger

Inyección de dependencias automatizada

Metaprogramación

La metaprogramación es un programa que trata a otros programas como sus datos, para agregar o cambiar el comportamiento y las funcionalidades.

La metaprogramación puede ocurrir en:

  • Tiempo de ejecución: Reflexión.

  • Tiempo de compilación: Generadores (anotaciones).

Opciones de biblioteca

  • Dagger: popular biblioteca de inyección de dependencias para Java, Kotlin y Android.

  • Hilt: la biblioteca recomendada de Jetpack para la inyección de dependencia en Android.

Dagger

Dagger es un marco de inyección de dependencias en tiempo de compilación totalmente estático para Java, Kotlin y Android. Es una adaptación de una versión anterior creada por Square y ahora mantenida por Google.

Dagger tiene como objetivo abordar muchos de los problemas de desarrollo y rendimiento que han plagado las soluciones basadas en la reflexión.

Beneficios

Dagger lo libera de escribir código repetitivo tedioso y propenso a errores al:

  • Generando el código (gráfico de dependencias la aplicación) que implementaste manualmente en la sección de inyección manual.

  • Creación de factorías para las clases disponibles. Así es como las dependencias se satisfacen internamente.

  • Decidir si reutilizar una dependencia o crear una nueva instancia mediante el uso de alcances.

  • Creación de contenedores para flujos específicos.

Dagger en acción

@Singleton
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
    @Component.Factory
    interface Factory {
        fun create(@BindsInstance context: Context): ApplicationComponent
    }

    fun repository(): UserRepository

    fun inject(activity: LoginActivity)
}

@Module
class NetworkModule {
    @Singleton
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService { ... }
}

@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }
class MyApplication : Application() {

    val applicationComponent: ApplicationComponent by lazy {
        initializeComponent()
    }

    private fun initializeComponent(): ApplicationComponent = DaggerApplicationComponent
        .factory()
        .create(applicationContext)
}
class LoginActivity : AppCompatActivity() {
    @Inject lateinit var loginViewModel: LoginViewModel
    ...
    override fun onCreate(savedInstanceState: Bundle?) {

        (application as MyApplication).applicationComponent.inject(this)

        super.onCreate(savedInstanceState)
        ...
    }
}
@Component(modules = [StorageModule::class])
interface ApplicationComponent {

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance context: Context): ApplicationComponent
    }
}
  • Anotación @Inject

        class RegistrationViewModel @Inject constructor(val userManager: UserManager) {
            ...
        }
    
  • Anotación @Binds

        @Module
        abstract class StorageModule {
    
            @Binds
            abstract fun provideStorage(storage: SharedPreferencesStorage): Storage
        }
    
  • Anotación @Provides

        @Module
        class StorageModule {
    
            @Provides
            fun provideStorage(context: Context): Storage {
                return SharedPreferencesStorage(context)
            }
        }
    
  • @Binds y @Provides juntos

        @Module
        abstract class ExampleModule {
            @Binds
            abstract fun bindsExampleClass(factory: ExampleClassImpl): ExampleClass
    
            companion object {
                @Provides
                fun provideExampleClass(): ExampleClass = ProvidedExampleClass()
            }
        }
    

Alcances personalizados

@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class ActivityScope

@ActivityScope
@Subcomponent
interface RegistrationComponent { ... }

@ActivityScope
class RegistrationViewModel @Inject constructor(val userManager: UserManager) {
    ...
}

subcomponentes

@Subcomponent
interface RegistrationComponent { 
        @Subcomponent.Factory
    interface Factory {
        fun create(): RegistrationComponent
    }

    fun inject(activity: RegistrationActivity)
 }

@Module(subcomponents = [RegistrationComponent::class])
class ApplicationSubcomponents

@Singleton
@Component(modules = [StorageModule::class, ApplicationSubcomponents::class])
interface applicationComponent { 
    ...

    fun registrationComponent(): RegistrationComponent.Factory
 }
class RegistrationActivity : AppCompatActivity() {
    ...

    override fun onCreate(savedInstanceState: Bundle?) {

       registrationComponent = (application as MyApplication).applicationComponent.registrationComponent().create() 
        registrationComponent.inject(this)

        super.onCreate(savedInstanceState)
        ...
    }
    ...
}

Desafío

  • Automatiza la inyección de dependencias en tu proyecto con dagger.

Referencias