Skip to main content

Aliases

It is possible to add aliases to your environment.

Aliases allow you to point to an existing declaration in a different way.

Syntax

Similarly to regular put calls, adding an alias can be done using an inline-reified approach or a KClass-based approach.

interface AliasType
class TargetType : AliasType

val env = tegralDi {
put(::TargetType)

// Using reified types (you can read this as 'AliasType --> TargetType')
putAlias<AliasType, TargetType>()

// Using KClass objects
putAlias(aliasClass = AliasType::class, targetClass = TargetType::class)
}

In this case, our environment will only have a single component TargetType, and an additional alias AliasType pointing to it.

This allows us to retrieve the same component using two different identifiers:

class UserA(scope: InjectionScope) {
private val targetType: TargetType by scope()
}

class UserB(scope: InjectionScope) {
private val aliasType: AliasType by scope()
}

Although we are retrieving an object with two different identifiers, it is the same object:

class Checker(scope: InjectionScope) {
private val targetType: TargetType by scope()
private val aliasType: AliasType by scope()

fun check() {
if (targetType === aliasType) {
println("They are the same object!")
} else {
println("Not the same object!")
}
}
}

val env = tegralDi {
put(::TargetType)
putAlias<AliasType, TargetType>()
put(::Checker)
}

env.get<Checker>().check() // "They are the same object!"

Qualifiers

You can also use qualifiers to further specify the identifiers. There are two (sets of) qualifiers you can define:

  • The qualifiers for the alias, i.e. the qualifiers that will be used when you want to use the alias.
  • The qualifiers for the target, i.e. the qualifiers that the alias will use to find the actual object to use.

The syntax is as follows:

val env = tegralDi {
put(::Component)
put(named("myTarget"), ::TargetType)

// Using reified types (you can read this as 'AliasType --> TargetType')
putAlias<AliasType, TargetType>(aliasQualifier = named("myAlias"), targetQualifier = named("myTarget"))

// Using KClass objects
putAlias(
aliasClass = AliasType::class,
aliasQualifier = named("myAlias"),
targetClass = TargetType::class,
targetQualifier = named("myTarget")
)
}

class Component(scope: InjectionScope) {
private val directlyUsingTarget: TargetType by scope(named("myTarget"))
private val usingAlias: AliasType by scope(named("myAlias"))
}

Examples

There are a few possible use-cases for aliases.

Expose a contract and its implementation

For example, say we have the following class and interface:

interface Say {
fun saySomething()
}

class SayHello : Say {
override fun saySomething() {
println("Hello!")
}
}

Now, let's say we have the two following components:

class ComponentA(scope: InjectionScope) {
private val say: Say by scope()

// ..
}

class ComponentB(scope: InjectionScope) {
private val sayHello: SayHello by scope()

// ...
}

Aliases can help us achieve this pattern like so:

val env = tegralDi {
put(::SayHello)
putAlias<Say, SayHello>()
put(::ComponentA)
put(::ComponentB)
}

Use different qualifiers for the same object

If, for whatever reason, you need to expose the same object under different qualifiers, you can use aliases for this.

Let's say that we want to expose a string both without qualifiers and with a named qualifier. You could do it like so:

val env = tegralDi {
put { "abcdef" }
put<String, String>(aliasQualifier = named("alphabet"))
}

println(env.get<String>()) // "abcdef"
println(env.get<String>(named("alphabet"))) // "abcdef"