Skip to content

Instantly share code, notes, and snippets.

@bmc08gt
Last active May 20, 2021 15:01
Show Gist options
  • Select an option

  • Save bmc08gt/2a6076588401a07fda709df06485c295 to your computer and use it in GitHub Desktop.

Select an option

Save bmc08gt/2a6076588401a07fda709df06485c295 to your computer and use it in GitHub Desktop.
DIY DI
interface AppComponent : Component {
val appContext: Context
}
class AppConfig(
override val appContext: Context,
) : AppComponent
import androidx.annotation.MainThread
interface Component
@MainThread
inline fun <reified C : Component> components(): Lazy<C> {
return ComponentLazy(C::class)
}
// jetpack startup initializer to not add this to App subclass
class ComponentInitializer : Initializer<Unit> {
override fun create(context: Context) {
ComponentRouter.init(context) {
inject(FeatureConfig(app.appContext))
}
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return emptyList()
}
}
import kotlin.reflect.KClass
import kotlin.reflect.KProperty0
class ComponentLazy<C : Component> (
private val componentClass: KClass<C>
) : Lazy<C> {
private var cached: C? = null
override val value: C
get() {
val component = cached
return if (component == null) {
val match = ComponentRouter.getComponent<C>(componentClass.java)
requireNotNull(match) {
ComponentNotRegisteredException(componentClass::simpleName)
}
return match.also { cached = it }
} else {
component
}
}
override fun isInitialized() = cached != null
}
class ComponentNotRegisteredException(name: KProperty0<String?>) : RuntimeException("${name.get()} not registered")
object ComponentRouter {
private const val TAG = "ComponentRouter"
val DEBUG = false
val components: MutableMap<Class<out Component>, Component> = mutableMapOf()
fun <T: Component> getComponent(clazz: Class<out Component>): T? {
fun checkInterfaces(klass: Class<Component>): Boolean {
if (klass.interfaces.isEmpty()) return false
log("klazz name=${klass.interfaces.first().canonicalName}")
return klass.interfaces.first().canonicalName == clazz.canonicalName
}
fun iterateInterfaces(klass: Class<Component>): Boolean {
if (klass.canonicalName == clazz.canonicalName) return true
val match = checkInterfaces(klass)
if (match) {
return match
}
return klass.interfaces.any { k -> iterateInterfaces(k as Class<Component>) }
}
val match = components.values.find { iterateInterfaces(it.javaClass) }
return match as? T
}
fun init(appContext: Context, block: (Initializer.() -> Unit)? = null): ComponentRouter {
Initializer().apply {
with(AppConfig(appContext)) {
inject(this)
appComponent = this
}
block?.invoke(this)
}
return this
}
private fun log(message: String) {
if (DEBUG) {
Log.d(TAG, message)
}
}
class Initializer {
lateinit var appComponent: AppComponent
fun inject(component: Component) {
with(component) {
components[this::class.java] = component
}
}
}
}
interface FeatureComponent : Component {
val db: RoomDatabase
}
class FeatureConfig(
context: Context,
) : FeatureComponent {
override val db: RoomData = // construct your db
}
FeatureComponent component = ComponentRouter.INSTANCE.getComponent(FeatureComponent.class);
if (component != null) {
component.getDb();
}
val feature by components<FeatureComponent>()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment