Skip to content

Instantly share code, notes, and snippets.

@seadowg
Last active June 14, 2022 16:52
Show Gist options
  • Save seadowg/b684c11a2cc6e092c8ed4301181728f7 to your computer and use it in GitHub Desktop.
Save seadowg/b684c11a2cc6e092c8ed4301181728f7 to your computer and use it in GitHub Desktop.
Tiny DI/service locator (whatever) framework for Kotlin that uses a shared singleton (like an Android Application object) as a host for dependencies. Includes Android extensions because reified is fun.
import android.app.Activity
import android.content.Context
import androidx.fragment.app.Fragment
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
class ObjectProvider {
private val providers = mutableMapOf<Class<*>, () -> Any?>()
private val singletons = mutableMapOf<Class<*>, Any?>()
fun <T> setProvider(clazz: Class<T>, singleton: Boolean = true, provider: () -> T) {
if (singleton) {
providers[clazz] = { getSingleton(clazz, provider) }
} else {
providers[clazz] = provider
}
}
@Suppress("UNCHECKED_CAST")
fun <T> get(clazz: Class<T>): T {
return providers[clazz]!!.invoke() as T
}
private fun <T> getSingleton(clazz: Class<T>, provider: () -> T) {
singletons.getOrPut(clazz) {
provider()
}
}
}
interface ObjectProviderHost {
fun getObjectProvider(): ObjectProvider
}
fun objectProvider(block: ObjectProviderWrapper.() -> Unit): ObjectProvider {
val newObjectProvider = ObjectProvider()
block(ObjectProviderWrapper(newObjectProvider))
return newObjectProvider
}
fun ObjectProvider.override(block: ObjectProviderWrapper.() -> Unit) {
block(ObjectProviderWrapper(this))
}
class ObjectProviderWrapper(private val objectProvider: ObjectProvider) {
fun <T> provider(clazz: Class<T>, block: () -> T) {
objectProvider.setProvider(clazz, false, block)
}
fun <T : Any> provider(clazz: KClass<T>, block: () -> T) {
objectProvider.setProvider(clazz.java, false, block)
}
fun <T> singleton(clazz: Class<T>, block: () -> T) {
objectProvider.setProvider(clazz, true, block)
}
fun <T : Any> singleton(clazz: KClass<T>, block: () -> T) {
objectProvider.setProvider(clazz.java, true, block)
}
}
inline fun <reified T> Activity.inject(): InjectDelegate<T> {
return InjectDelegate(T::class.java) { getObjectProvider() }
}
inline fun <reified T> Fragment.inject(): InjectDelegate<T> {
return InjectDelegate(T::class.java) { requireContext().getObjectProvider() }
}
fun Context.getObjectProvider(): ObjectProvider {
return (applicationContext as ObjectProviderHost).getObjectProvider()
}
class InjectDelegate<T>(private val clazz: Class<T>, private val objectProvider: () -> ObjectProvider) {
private var value: T? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value.let {
it ?: objectProvider().get(clazz).apply { value = this }
}
}
}
@seadowg
Copy link
Author

seadowg commented Jun 14, 2022

So in an Android Activity we end up with:

class MyActivity {
    private val someDep: SomeDep by inject()
}

And then the Application looks like:

class MyApplication : Application(), ObjectProviderHost {

    private val objectProvider = objectProvider {
        provider(SomeDep::class) { SomeDepImpl() }
    }

    override fun getObjectProvider(): ObjectProvider {
        return objectProvider;
    }
}

Could probably be extended with a module concept pretty easily. Something like:

val moduleOne = module {
    provider(SomeDep::class) { SomeDepImpl() }
}

val moduleTwo = module {
    provider(SomeOtherDep::class) { SomeOtherDep() }
}

val objectProvider = objectProvider(moduleOne, moduleTwo)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment