-
Notifications
You must be signed in to change notification settings - Fork 176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Help with understanding of Context <> Scope relationship (expanded docs)? #464
Comments
Okay here's what I was able to achieve, for anyone's future reference: // setup
interface DestinationContext {
val coroutineScope: CoroutineScope
val subLifecycle: SubscriberLifecycle
val registry: ScopeRegistry
}
// scope
@PublishedApi
internal object DestinationScope : Scope<DestinationContext> {
override fun getRegistry(context: DestinationContext) = context.registry
}
// declare in modules
@Suppress("INVISIBLE_REFERENCE") // to resolve the ambiguity
@kotlin.internal.LowPriorityInOverloadResolution
inline fun <reified T : Container<*, *, *>, reified P : Any> DI.Builder.container(
@BuilderInference crossinline definition: BindingDI<DestinationContext>.(P) -> T
) = bind<T>() with scoped(DestinationScope).multiton { params: P ->
definition(params).apply { store.start(context.coroutineScope) }
}
inline fun <reified T : Container<*, *, *>> DI.Builder.container(
@BuilderInference crossinline definition: NoArgBindingDI<DestinationContext>.() -> T
) = bind<T>() with scoped(DestinationScope).singleton {
definition().apply { store.start(context.coroutineScope) }
}
// provide context for each child
@Composable
internal fun ProvideDestinationLocals(
component: DestinationComponent,
content: @Composable () -> Unit
) = CompositionLocalProvider(
LocalSubscriberLifecycle provides component.subLifecycle,
LocalDestinationContext provides component,
) {
val di = localDI()
withDI(remember(di) { di.On(component.diContext) }, content = content)
}
// implement components
internal fun destinationComponent(
context: ComponentContext,
): DestinationComponent = object :
DestinationContext,
ComponentContext by context {
override val coroutineScope = instanceKeeper.retainedScope()
override val subLifecycle = lifecycle.asSubscriberLifecycle
private val _registry by fastLazy { context.retainedInstance { KeptScopeRegistry() } }
override val diContext by fastLazy { diContext<DestinationContext>(this) }
override val registry by _registry::delegate
}
// inject
@Composable
inline fun <reified T : Container<S, I, A>, S : MVIState, I : MVIIntent, A : MVIAction> container(): Store<S, I, A> {
val value by rememberInstance<T>()
return value.store
}
@Composable
inline fun <reified T : Container<S, I, A>, reified P : Any, S : MVIState, I : MVIIntent, A : MVIAction> container(
noinline params: () -> P,
): Store<S, I, A> {
val value by rememberInstance<P, T>(fArg = params)
return value.store
} I will keep this open as the docs definitely need improvement |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm trying to implement a simple behavior, where there is a Decompose component. This component has a lifecycle that is similar to Android lifecycle, but is retained across configuration changes. It is also scoped to the navigation destination. I need two things from kodein in this case:
coroutineScope
that is provided by the component to start and stop the coroutine-based logic (exactly like aviewModelScope
). That logic is named Container.lifecycle
inside the Compose UI. This lifecycle controls when they subscribe and unsubscribe to data streams provided by the Container.The way I want this to work is:
lifecycle
along with it to the compose UI. Each time a composable is rendered and DI is used inside, it should get the samelifecycle
andstore
as the parent Component. So there must be a DI scope that:a) survives configuration changes
b) is bound to the Component lifecycle. When the parent component is destroyed, the scope must be cleared and no longer available for use
c) exposes the lifecycle for injection into the DI scope (to create child components etc.)
When a particular composable is rendered, it wants to grab a business logic
Container
from the current destination scope, and a lifecycle to subscribe to that Container. The Container must be started using thecoroutineScope
.So far I attempted multiple approaches after scouring the documentation for hours:
context
variables. These are good because the root Component is created, and then creates its navigation children, which each override thatcontext
to the correct value. Each one persists.However, I was unable to implement dependencies that are strictly scoped to that particular context. There is no way to permanently "close" the context.
DI.Module
s, I cannot reference it. Not only it is in another module, but it is also not exactly a singleton, so using anobject
won't do. The root component itself can be destroyed sometimes along with the activity'sviewModelStore
, in which case it'sscope
, too, should be closed permanently. Besides, theScope
requires a context, but in that case, the scope itself (Component) is already the context. UsingScope<ComponentScope>
results in an infinite recursion.#2
but also it isn't really clear how to close the scope permanently.Here's the code I have so far:
That above is what I need from each destination. The
coroutineScope
is used to start the Container injected fromscope
, the Container instance is scoped to thescope
, which is itself scoped to theStoreLifecycle
strictly. Each invocation ofscope.instance<MyContainer>()
must return the same value until the ComponentScope is closed, after which it should simply throw.In my DI.Modules, I want to declare how the
Container
instances are going to be created, and nothing more. I created utility functions:The functions above are not correct however, because they are not properly reusing the same instance. Instead, they should use
singleton
scoped to theComponentScope
's lifecycle. If I replace thecontexted
withscoped
, it wants a DI scope instance, which I do not have. The only way I can get it is from theComponentScope
variable. However, when I do that by using both contexted and scoped, I can no longer declare the dependency properly, as thecontexted
has noscoped
extension function.Despite reading the documentation many times, I still struggle to understand how to implement my desired outcome. Can you provide some help?
P.S. I was able to implement the desired outcome with Koin very easily:
The
DestinationScope
above is then easily provided as aCompositionLocal
using Koin'sLocalKoinScope
.The text was updated successfully, but these errors were encountered: