Replies: 2 comments
|
We have a use case that could benefit from this feature. So we have a native C++ library (audio engine) to load at app start, and currently we block app start until the library loads, then start the DI. And it's been one of our known ANRs (application not responding) for a while. The idea is to start the DI first and make all native type providers suspended, so we don't necessarily need to block the app start. For example: // From the Native Library
abstract class MixHandler
// From the app
@ContributesTo(AppScope::class)
interface MixHandlerProvider {
@Provides
suspend fun provideMixHandler(): MixHandler {
// await until native library is loaded
return object : MixHandler {}
}
}Recently, we started some work around this, and it appears to be in a very similar way to how suspend providers work. We introduced a suspend wrapper for the provider type, and forbade direct injection of native types. When the suspend provider feature is ready to be tested, I think we can have a seamless migration and provide some early feedback if it will be helpful. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
The Proposal
Here's the idea:
A "suspending provider" may be written as follows:
And injected as follows:
Non-scoped suspending providers
Invoking a non-scoped suspending provider works exactly like invoking the provider method directly would.
Scoped suspending providers
If the provider is scoped:
...then callers of the suspending provider will make use of a cached value shared across the scope. Cache misses will directly invoke the provider method.
If a caller of the suspending provider is cancelled while producing the value, the next caller will produce the value again from the start. (This is similar to the current non-suspending implementation used for scoped values.)
Existing support
The biggest alternative worth considering to this proposal is to do nothing.
It's possible with Metro's current APIs to provide values into the graph that must be produced in a suspendful way. Here's a couple of examples of how you might do that.
First would be to do something analogous by hand:
Issues with this solution:
invoke()method is non-obvious.fun getDeviceIdProvider(): SuspendingProvider { generateDeviceId() }), but it's extra work.SuspendingProvideradds unnecessary overhead for non-scoped providers.Second, use a deferred:
This has a few downsides as an API:
CoroutineScope, the implementer of this solution really does have to think of the graph or graph extension as something that has its own lifetime that must be managed. A scope whose lifetime corresponds to the scope lifetime must be injected, and cancelled when it is abandoned (to avoid loose provider invocations).LAZYin the above example, then the deferred coroutine is run as soon as it is injected, not upon first interaction.asynccalls (Deferred<T>) directly. This means that callers can callcancel()on them and cancel the underlying coroutine. There's no sensible option for what this should do, other than no-op.All reactions