Scoped DI
Dependencies can be managed within the Vyuh framework using a Dependency
Injection (DI) container. We use GetIt 🔗
internally as our default plugin for managing DI. The plugin itself can be
referenced with vyuh.di
and can be used to set up and lookup dependencies.
However, all the dependencies that you’re creating are managed at a global level. This means we have a single DI container globally in which you put all of your dependencies.
Sometimes it may be necessary that some of the dependencies are only at specific route levels. As long as the route is active, you want the dependencies to be active. Implicitly, the lifetime of the dependencies is linked with the lifetime of the route.
To manage such a scenario, we have introduced the concept of a Scoped DI. This allows you to create a scoped-DI container for a widget-subtree within which you can manage your dependencies. Currently, the default support exists for a Route, but you can also apply this scoping for your own widget trees.
This keeps your dependencies local to the route (or Widget), and if you look up
a dependency within that widget tree, it will first try to find something at the
Route
level (or your own Widget
level). If it doesn’t find it, then it goes
to the global container (vyuh.di
) as a fallback.
Looking up scoped dependencies
You can look up a scoped dependency using the context.di
API. The context.di
looks up a scoped DI-container up the widget ancestry and finds the nearest one
. If none is found, it will fall back to vyuh.di
. In any case, you will be
able to look up a dependency either locally within the tree or go up the
hierarchy until you find the one inside the global container.
Note that you do need the
context
parameter to look up the scope-di container. This is by design, as you only want this lookup to happen within a widget tree.
Notice the use of context.di
to look up the TestStore
from the scoped di
container.
Setting a scoped dependency
Since the scope dependency is very limited to the Route, there has to be a way
to inject the dependencies just at the time of initializing the route. We do
this with the use of the RouteLifecycleConfiguration
.
This lifecycle configuration can be set up at the CMS level, or you can also do it explicitly if you are managing the route locally within your code.
The RouteLifecycleConfiguration
has the init(BuildContext, RouteBase)
and
dispose()
methods which are invoked whenever the route is about to be
initialized or about to be disposed. This is a perfect opportunity for you to
set up scoped-dependencies since you also have access to the context
within
the init()
method. Let’s see how this is done for the TestStore
.
Typically, you will setup these lifecycle handlers within the CMS, as shown in
the image below. This is done in a Route
where such dependencies are needed.
You would also need to register the TypeDescriptor
for the lifecycle-handler
inside the Feature. Notice how this is done within the RouteDescriptor
using
the lifecycleHandlers
property.
The init()
of the lifecycle-handler is automatically invoked as part of the
Route
deserialization.
Summary
The introduction of the Scoped-DI container gives you new abilities to localize your dependencies within a route. This avoids polluting the global DI container and also takes care of cleaning up the dependencies after they’re done.
By linking the lifetime of the dependencies to the lifetime of your route, you get better memory management, better performance and makes your routes more efficient.