Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Binding by generic type parameter #449

Open
nsragow opened this issue Jan 22, 2024 · 11 comments
Open

Binding by generic type parameter #449

nsragow opened this issue Jan 22, 2024 · 11 comments

Comments

@nsragow
Copy link

nsragow commented Jan 22, 2024

I can't figure out a way to bind a generic (non-reified) type parameter:

class Container<Type>(val obj: Type) {
    fun typeBinder(builder: Kodein.Builder) {
        builder.apply { 
            bind() from singleton { obj }
        }
    }
}

Is this possible/supported?

Currently I am using the following:

class Container<Type : Any>(val obj: Type, val klass: KClass<Type>) {
    fun typeBinder(builder: Kodein.Builder) {
        builder.apply {
            val s = Singleton(
                scope,
                contextType,
                TT(klass),
                null,
                true,
            ) {
                obj
            }
            bind() from s
        }
    }
}

Is this the expected usage or is there a bind interface that takes a KClass?

@romainbsl
Copy link
Member

I still don't understand what you are trying to achieve here.

What do you need to bind?
What's your use case?

@nsragow
Copy link
Author

nsragow commented Jan 22, 2024

The exact use case is a super class for tests that binds an Config (subclass of AppConfig) component for each test:

abstract class TcTest<Config : AppConfig>(val klass: KClass<Config>) {
    abstract val baseModule: Kodein.Module
    lateinit var module: Kodein.Module
    lateinit var config: Config

    abstract fun envVariables(): Map<String, String>
    abstract fun config(overrides: Map<String, String>): Config

    @Before
    fun before() {
        val config = config(envVariables())
        this@TcTest.module = Kodein.Module("TcTestModule", allowSilentOverride = true) {
            importOnce(baseModule, allowOverride = true)
            val s = Singleton(scope, contextType, TT(klass), null, true) {
                config
            }
            bind() from s
            bind<AppConfig> with singleton { config }
        }
        this@TcTest.config = config
    }
}

As far as I know there is no way to get reified class types when they are passed as constructor parameters, and the bind function I typically use requires a reified type.

@romainbsl
Copy link
Member

Ok, I think I got it.
Indeed, there is no function that facilitate the binding of a KClass.
Are you using an old version of Kodein? the TT(...) function has been rename a long time ago, by erased(...) .

@romainbsl
Copy link
Member

But, I trully why you would need to do this, and I don't get why this bindings are needed

            val s = Singleton(scope, contextType, TT(klass), null, true) {
                config
            }
            bind() from s
            bind<AppConfig> with singleton { config }

maybe something like this would do the trick for your use case ?

bindSingleton<AppConfig> { config }
delegate<Config>().to<AppConfig()

@nsragow
Copy link
Author

nsragow commented Jan 22, 2024

Yeah we're using an older Kodein. The delegate call would not solve the issue because it still requires a reified type, which is not available because Config is a class generic type parameter. Unless there is a way to get a class generic reified.

@nsragow
Copy link
Author

nsragow commented Jan 22, 2024

I think maybe it would be worth supporting in the case where a class has a type it wants to bind (like my case). Unless that is an uncommon way to use Kodein. Do you have contribution guidelines? I can add that in

@romainbsl
Copy link
Member

We do not support generic class type binding, even tho Kaverit has a function that could help
public expect fun <T: Any> erased(cls: KClass<T>): TypeToken<T>.

Maybe we could add something like this to the DIBuilder file

public inline fun <reified T : Any> DI.Builder.bind(
    cls: KClass<T>,
    tag: Any? = null,
    overrides: Boolean? = null,
): DI.Builder.TypeBinder<T> = Bind(erased(cls), tag, overrides)

We do not have other contribution guidelines than make a PR and we iterate on it ;)

@nsragow
Copy link
Author

nsragow commented Jan 24, 2024

I also made a KSP integration for Kodein (been using it at our company for 2 years) that generates Kodein modules based on annotations:

@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class Component

@Component
@Suppress("unused")
class DownstreamClient (
    client: HttpClient,
    appConfig: AppConfig,
    private val singletonJobMgr: SingletonJobMgr,
    clientTimeoutMapping: ClientSocketTimeoutMapping,
    private val tokenCache: TokenCache,
    metrics: Metrics,
 )

outputs:

public class ComponentProviderImpl : ComponentProvider {
  public override fun bindAll(builder: Kodein.Builder): Unit {
        builder.bind<DownstreamClient>() with builder.singleton {
              DownstreamClient(
                  client=instance(),
                  appConfig=instance(),
                  singletonJobMgr=instance(),
                  clientTimeoutMapping=instance(),
                  tokenCache=instance(),
                  metrics=instance(),
              )
            }
  }
}

We use this so we don't need to update our bindings when we make changes to dependencies or creating new dependencies (among other things, like creating Spring-like proxy classes for cacheable functions, etc).

Do you think it could work in the scope of Kodein?

@romainbsl
Copy link
Member

In fact, we've created Kodein to move away from annotation nightmare of Dagger / Hilt, so we are very not sur that we want to go back on that path, and going full circle. To avoid the update of bindings we generally use the new and bindSingletonOf, like:

bindSingletonOf(::MyController)
bindSingleton { new(::MyController) }

We also try to work on some KSP tools to help with simpler bindings. But its far from ready.

Did the erased(cls: KClass<T>) suggestion above helped you ?

@nsragow
Copy link
Author

nsragow commented Jan 24, 2024

Yeah it is working, thanks

@romainbsl
Copy link
Member

I keep this open to keep track of it.I will add it to the core.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants