Skip to content

Delegate Factory

Table of Contents

Overview

When a class exposes multiple dependencies through its methods (e.g., a Room database with DAO accessors), use DelegateBindingFactory. The processor generates @Provides functions for both the class itself and every public zero-argument non-Unit-returning method declared on it.

Creating a Factory

Implement DelegateBindingFactory<T> where T is the type of the delegate class. The provideDelegate() method creates and returns the delegate instance:

class AppDatabaseFactory @Inject constructor(
    @param:ApplicationContext private val context: Context,
) : DelegateBindingFactory<AppDatabase> {

    @AutoScoped
    override fun provideDelegate(): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "app-database",
        ).build()
    }
}

Annotating the Class

Point the class to the factory using @AutoBinds(factory = ...):

@AutoBinds(factory = AppDatabaseFactory::class)
@Database(entities = [NoteEntity::class, OrderEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun noteDao(): NoteDao
    abstract fun orderDao(): OrderDao
}

Scoping with @AutoScoped

Annotate provideDelegate() with @AutoScoped to scope the main delegate provider. Sub-delegate methods (like noteDao()) remain unscoped and are re-resolved from the delegate on each injection:

class AppDatabaseFactory @Inject constructor(
    @param:ApplicationContext private val context: Context,
) : DelegateBindingFactory<AppDatabase> {

    @AutoScoped  // scopes the returned AppDatabase instance
    override fun provideDelegate(): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "app-database",
        ).build()
    }
}

The scope annotation is determined by the resolved component. See Scopes and Components for the full mapping table.

Generated Code

The processor generates an object Hilt module with:

  • A @Provides function for the delegate itself (via provideDelegate()).
  • A @Provides function for each public zero-argument non-Unit-returning method declared directly on the annotated class.
@Module
@InstallIn(SingletonComponent::class)
internal object AppDatabaseModule {
    @Provides
    @Singleton  // present only if @AutoScoped is used
    fun provideAppDatabase(factory: AppDatabaseFactory): AppDatabase =
        factory.provideDelegate()

    @Provides
    fun provideNoteDao(delegate: AppDatabase): NoteDao = delegate.noteDao()

    @Provides
    fun provideOrderDao(delegate: AppDatabase): OrderDao = delegate.orderDao()
}

Both AppDatabase and all its DAOs become injectable with a single annotation.

Factory Requirements

A DelegateBindingFactory subclass must:

  • Be a final class (no open or abstract modifier, not an object).
  • Be free of type parameters (specify the concrete type on the parent, e.g., DelegateBindingFactory<AppDatabase>).
  • Have a primary constructor annotated with @Inject.
  • Directly implement DelegateBindingFactory (not through an intermediate class).
  • Declare provideDelegate() with override directly in the factory class body — an inherited implementation is not sufficient.
  • Have provideDelegate() return the annotated class type exactly (e.g., if @AutoBinds(factory = MyDbFactory::class) is on AppDatabase, then provideDelegate() must return AppDatabase).

The annotated class itself must also:

  • Be free of type parameters (generic annotated classes are not supported with DelegateBindingFactory).

The processor emits a compile-time error if any of these conditions are not met.