Skip to content

Instantly share code, notes, and snippets.

@hrules6872
Last active Feb 16, 2022
Embed
What would you like to do?
The Koin Killer :)
private val data = submodule {
/* ... */
}
private val usecases = submodule {
/* ... */
}
val locator = locator {
single<Application> { App.get() }
single<Context> { get<Application>().applicationContext }
/* ... */
submodule(data).overrideWith { flavorData }
submodule(usecases).overrideWith { flavorUseCase }
}.overrideWith { flavorLocator }
inline fun <reified T> inject(named: String = ""): Lazy<T> = lazy(LazyThreadSafetyMode.NONE) { locator.get(named) }
inline fun <reified T> resolve(noinline instance: Module.() -> T): Lazy<T> = lazy(LazyThreadSafetyMode.NONE) { instance(locator) }
/*
* Copyright (c) 2020. Héctor de Isidro - hrules6872
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import kotlin.reflect.KClass
data class Identifier(val instanceClass: KClass<*>, val named: String) {
override fun toString(): String = "$instanceClass" + if (named.isNotEmpty()) "named $named" else ""
}
abstract class Dependency<T>(val value: () -> T) {
abstract fun get(): T
}
class Factory<T>(value: () -> T) : Dependency<T>(value) {
override fun get(): T = value()
}
class Singleton<T>(value: () -> T) : Dependency<T>(value) {
private val instance by lazy { value() }
override fun get(): T = instance
}
class Module internal constructor() {
private val _definitions = mutableMapOf<Identifier, Dependency<*>>()
val definitions: Map<Identifier, Dependency<*>>
get() = _definitions.toMap()
inline fun <reified T> single(named: String = "", override: Boolean = false, noinline instance: () -> T): Identifier {
val identifier = Identifier(T::class, named)
val singleton = Singleton(instance)
updateDefinition(identifier, singleton, override)
return identifier
}
inline fun <reified T> factory(named: String = "", override: Boolean = false, noinline instance: () -> T): Identifier {
val identifier = Identifier(T::class, named)
val factory = Factory(instance)
updateDefinition(identifier, factory, override)
return identifier
}
inline fun <reified T> resolve(noinline instance: () -> T): T = instance()
fun <T> updateDefinition(identifier: Identifier, instance: Dependency<T>, override: Boolean = false) {
if (!override) if (definitions.containsKey(identifier)) throw DependencyCreationException("Cannot inject 2 instance with same definition: $identifier")
_definitions[identifier] = instance
}
private val latestResolved: MutableList<Identifier> = mutableListOf()
fun checkLatestResolved(identifier: Identifier): Boolean = when {
latestResolved.contains(identifier) -> false
else -> {
latestResolved.add(identifier)
true
}
}
fun clearLatestResolved() = latestResolved.clear()
fun latestResolvedToString() = latestResolved.joinToString(separator = System.lineSeparator())
inline fun <reified T> get(named: String = ""): T {
val identifier = Identifier(T::class, named)
try {
if (!checkLatestResolved(identifier)) throw CyclicDependencyCreationException()
val definition = definitions[identifier]?.get() as T ?: throw DependencyResolutionException("No instance found based on the class: $identifier")
clearLatestResolved()
return definition
} catch (exception: RuntimeException) {
when (exception) {
is CyclicDependencyCreationException,
is StackOverflowError -> throw CyclicDependencyCreationException("Cyclic dependency detected while resolving:${System.lineSeparator()}${latestResolvedToString()}")
else -> throw exception
}
}
}
}
class DependencyCreationException(override val message: String) : RuntimeException()
class DependencyResolutionException(override val message: String) : RuntimeException()
class CyclicDependencyCreationException(msg: String = "") : RuntimeException(msg)
private typealias Declaration = Module.() -> Unit
fun locator(declaration: Declaration): Module = Module().apply(declaration)
fun module(declaration: Declaration): Module = Module().apply(declaration)
fun submodule(declaration: Declaration): Declaration = declaration
fun overrideWith(declaration: Declaration): Declaration = declaration
fun Module.submodule(alias: String = "", declaration: Declaration): Module = this.apply(declaration)
fun Module.submodule(submodule: Declaration): Module = this.apply(submodule)
fun Module.overrideWith(submodule: Declaration): Module = this.apply(submodule)
import org.amshove.kluent.*
import org.junit.Test
class ServiceLocatorTest {
@Test
fun `try to resolve a circular dependency should throw CyclicDependencyCreationException`() {
val module = module {
single { CycleA(get()) }
single { CycleB(get()) }
}
invoking { module.get<CycleA>() } `should throw` CyclicDependencyCreationException::class
}
@Test
fun `duplicate dependency definition should throw DependencyCreationException`() {
invoking {
module {
single { ClazzA() }
single { ClazzA() }
}
} `should throw` DependencyCreationException::class
}
@Test
fun `override duplicate dependency definition should pass ok`() {
invoking {
module {
single { ClazzA() }
single(override = true) { ClazzA() }
}
} `should not throw` DependencyCreationException::class
}
@Test
fun `try to resolve a not defined dependency should throw DependencyResolutionException`() {
val module = module {
single { ClazzA() }
}
invoking { module.get<CycleB>() } `should throw` DependencyResolutionException::class
}
}
private class Clazzes {
class ClazzA
class ClazzB(val a: ClazzA)
class CycleA(val b: CycleB)
class CycleB(val a: CycleA)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment