Hi, thanks for your work!
This may be a weird feature request which will make the package a bit different from Flutter and there may be performance considerations. However, the usability improvements would be great, in my opinion.
The idea is to provide a middleware for rendering Components, a function that wraps the executions of all build methods, to do various things.
For example, for registering and tracking dependencies that rebuild the Widget when they change. I believe the javascript framework https://www.solidjs.com/ is based on this concept, similar to MobX's tracking of dependencies. Through testing the package I have implemented some MobX and hooks bindings for Components. In this file you can see the main hooks logic and also the tracking of dependencies within build methods of StatelessObserverComponent
similar to the one in package:flutter_mobx
, however this one also supports hooks. In Flutter this is a problem it's difficult to use package:flutter_mobx
and package:flutter_hooks
(two packages implementing Elements) without nesting that the composing of middleware or multiple wrappers can solve. There are no automatic test, but I have tested them manually and you can try the deployed html in gh-pages. More hooks, using the same infrastructure, are implemented in this hooks file.
This is similar to some work I did in a package:deact
fork. However, in that fork a kind of middleware or wrapper is implemented for all build methods, not only for those in StatelessObserverWidget
. Which is nicer, since one does not worry about using the appropriate Widget (every build method is being tracked). You would not have to worry where to place them, one would register the middleware for the whole app (or a section) and use observables (or hooks) that are tracked by the middleware. They would all be tracked and rebuild the widgets in the places where the observables are used. There are perhaps some performance implications. Maybe some kind of similar middleware or wrapper for build functions could be provided as API. In this case, all that is needed is a value that saves state in the element and can be disposed (HookCtx
), the markNeedsBuild
method and a function that executes the build method. As shown in this code:
List<Component> _mobxHooksWrapper(
HookCtx ctx,
void Function() markNeedsBuild,
List<Component> Function() next,
) {
final previousCtx = _ctx;
_ctx = ctx;
final List<Component> children = useTrack(
next,
markNeedsBuild,
name: ctx.hashCode.toString(),
);
_ctx = previousCtx;
return children;
}
_mobxHooksWrapper
is executed on every build and provides MobX tracking with useTrack
and hooks support with the HookCtx
that is saved in the ComponentElement
.
I the repo, I am always using the implemented StatelessObserverComponent
or StatefulObserverComponent
since they provide hooks and MobX tracking. To make sure I use them I import package:jaspr
though a prelude.dart
file that overrides StatelessComponent
to StatelessObserverComponent
.
Other use cases may include some kind of profiling, however there are probably other ways to do it too. Maybe some kind of dynamic (can be turned on and off) profiling of certain parts of the Widget tree though similar middleware.
Maybe unrelated, but maybe some API for dependency injection can be explored though a middleware also. Within the repo there is also an implementation of dependency injection, similar to package:riverpod
. For watching, I am relying on MobX's dependency tracking, the pod's value in a pod scope is immutable. However, their properties may be mutable and, if they are MobX observables, everywhere they are used, they are being tracked (provided one uses StatelessObserverComponent
). For disposing, the values are disposed when the scope is disposed which is created using the _InheritedPodScope
InheritedWidget. One could also create scopes outside of jaspr, but within jasper one reads the pod values with an extension over BuildContext
that implements a T pod<T>(Pod<T> pod)
method that reads the closest _InheritedPodScope
and retrieves the value. Pods can be global or scoped. If they are global a single instance will be created for all scopes within the tree and will be disposed when the whole tree is disposed. If it's not global, then it will be created within a subtree, all children of the scope will be have access to the same instance, but there could be other instances in other scopes (subtrees).
In this case maybe a middleware could be implemented to support the autoDispose
feature in riverpod, where the dependencies are saved within the element and when the Pod's Element dependencies are disposed, then the Pod is disposed.
In general, this would allow for the framework to be extensible and to test or evaluate framework-wide functionalities that may be implemented natively by the framework in the future, or maybe just leave them as separate packages.
Thanks!