<@UHAJKUSTU> how do you feel about a similar plugi...
# mvikotlin
j
@Arkadii Ivanov how do you feel about a similar plugin functionality in Decompose as badoo/Ribs? I'm looking to extend the component context functionality to include an authorization component and I'm waffling between tweaking decompose internals or investigating the plugin route
a
It is an interesting idea indeed. But on the other hand it would be good to keep Decompose as simple as possible. I was not expecting that somebody would want to add anything to 'ComponentContext'. From my point of view it just brings some essentials into components' context. Don't you think it is better to explicitly pass your "authentication component" as a normal dependency? Anyway I will think about it. Thanks for raising. Maybe something like the "ambient" thing in Compose would work even better.
j
I could pass the dependency, it's just definitely in every component for my use case. I'd like to trigger a modal to re-authenticate anywhere in the app (which I figured could be a root component configuration). On successful re-auth, pop the config once credentials are updated. I could also chain outputs through every component to the root but that seems like quite a bit of additional overhead per component
a
Yeah, I got it. Looks we need something like "ambient". Also you can consider the following trick for transitive dependency propagation:
j
Slick, I dig it. Thanks for the input.
I'm also looking into adapting the compose-backstack library for transitions. I'll share anything useful!
a
Please do. Btw there are some transition examples in Compose samples. Both android and iOS.
j
I saw the crossfade addition for android. Still learning compose so that was extremely helpful. I haven't dug into the iOS side of things yet as target audience is 80-90% Android.
a
So, I thought about this use case. If you want to permanently add some limited amount of things into the
ComponentContext
then you use the following approach:
Copy code
interface AppComponentContext : ComponentContext {
    val database: Database
}

fun AppComponentContext.appComponentContext(context: ComponentContext): AppComponentContext =
    object : AppComponentContext, ComponentContext by context {
        override val database: Database = this@appComponentContext.database
    }

fun AppComponentContext.child(key: String): AppComponentContext =
    appComponentContext(child(key))

fun <C : Parcelable, T : Any> AppComponentContext.appRouter(
    initialConfiguration: C,
    configurationClass: KClass<out C>,
    key: String = "DefaultRouter",
    handleBackButton: Boolean = false,
    componentFactory: (configuration: C, AppComponentContext) -> T
): Router<C, T> =
    router(
        initialConfiguration = initialConfiguration,
        configurationClass = configurationClass,
        key = key,
        handleBackButton = handleBackButton
    ) { configuration, componentContext ->
        componentFactory(configuration, appComponentContext(componentContext))
    }

inline fun <reified C : Parcelable, T : Any> AppComponentContext.appRouter(
    initialConfiguration: C,
    key: String = "DefaultRouter",
    handleBackButton: Boolean = false,
    noinline componentFactory: (configuration: C, AppComponentContext) -> T
): Router<C, T> =
    appRouter(
        initialConfiguration = initialConfiguration,
        configurationClass = C::class,
        key = key,
        handleBackButton = handleBackButton,
        componentFactory = componentFactory
    )
Something like
ambient
from Decompose can be easily implemented with the following code:
Copy code
interface AppComponentContext : ComponentContext {
    val ambient: Map<KClass<*>, Any>
}

inline operator fun <reified T : Any> AppComponentContext.plus(value: T): AppComponentContext =
    object : AppComponentContext, ComponentContext by this {
        override val ambient: Map<KClass<*>, Any> =
            this@plus.ambient + (T::class to value)
    }

inline fun <reified T : Any> AppComponentContext.ambientOrNull(): T? = ambient[T::class] as T?

inline fun <reified T : Any> AppComponentContext.ambient(): T = requireNotNull(ambientOrNull())
Please not, there is no compile time safety in this case. Which I believe is very important.
I will also think about some sort of plugins. However I think they will be also not compile time safe. Or maybe I will add this
ambient
thing into Decompose itself.
j
I quite like the ambient implementation. I'll refactor some code later tonight / tomorrow morning to try it out. The transitive dependency propagation relieved a lot of my headache from the original post. And having the dependencies listed in the component interface enhances readability, in my opinion
1
r
Thank you @Arkadii Ivanov extended app context was what I was looking for
But why the method “child” is calling recursively ?
a
Maybe this is a bug. If it's crashes, could you please file an issue?