It is very connivent to use any DI framework when all the objects required are available in application. For example, I have two classes Logger, Service and class Middleware is dependent on these classes.

class Logger()

class Service()

//this class requires Logger & Service object
class Middleware(private val logger:Logger. private val service:Service)

// middleware object
val middleware = Middleware(Logger(),Service())

Dagger can build objects of Logger and Service classes by indicating @Inject annotation to all three classes

internal class Logger @Inject constructor()

internal class Service @Inject constructor()

//this class requires Logger & Service object
internal class Middleware @Inject constructor(private val logger:Logger. private val service:Service)

@Component
interface MiddlewareComponent {
    fun buildComponent():Middleware
}

// middleware object
val middleware = DaggerMiddlewareComponent.builder().build().buildComponent()

If the project requirements are modified to get Logger with custom implementation, we can write @Module to support dependency injection.

// now, Logger is interface
interface Logger 

class Service @Inject constructor()

class Middleware @Inject constructor(val logger: Logger, val service: Service)

@Module
class LoggerModule constructor(val logger: Logger) {

    @Provides
    fun providesLogger(): Logger {
        return logger
    }
}

@Component(modules = [LoggerModule::class])
interface MiddlewareComponent {
    fun buildComponent(): Middleware
}

//custom implementation
class NullLogger : Logger

//injecting logger module with interface implementation
val middleware = DaggerMiddlewareComponent.builder().loggerModule(LoggerModule(NullLogger())).build().buildComponent()