-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Description
This is related to #12992 (comment)
Use-case
Currently, widgets never stop listening to an InheritedWidget, even if they stop using it. As such, the code:
Widget build(context) {
if (condition) {
context.dependOnInheritedWidgetOfExactType<T>();
}
}when condition changes from true to false, the consumer will keep rebuilding when the InheritedWidget changes.
That current behavior is supposedly a performance optimization. To quote Hixie:
This was an intentional performance optimization - by not having to clear the dependencies each frame, we can avoid a lot of work.
The problem is, while for simple usages that's fine, this behavior is incompatible with some use-cases
For example, in package:riverpod, one core feature is autoDispose, which is incompatible with this behavior.
In Riverpod, autoDispose is used to free the resources linked to an InheritedWidget when that InheritedWidget stops being used.
The problem with this behavior is:
If a consumer widget stops calling to context.dependOnInheritedWidget , users would expect autoDispose to take effect and free the resources.
But that wouldn't be the case as the consumer widget will not unsubscribe to the InheritedWidget until its element is unmounted. This would delay the effect of autoDispose by potentially a significant amount of time, or simply prevent it to take effect.
Currently, package:riverpod works around that issue by not relying on context.dependOnInheritedWidget.
This leads to an ugly workaround, which is subclassing StatelessWidget/StatefulWidget to override Element.build (cf ConsumerWidget, a custom StatelessWidget-like)
While this workaround works, it has an important downside:
This leads to package:riverpod being used differently from what Flutter typically does.
Instead of a:
class Example extends StatelessWidget {
Widget build(BuildContext context) {
MyInheritedWidget.of(context);
}
}Riverpod users have to do:
class Example extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
MyInheritedWidget.of(ref);
}
}This difference is the reason why I had to basically stop developing package:provider and instead start working on package:riverpod, as package:provider couldn't keep using the context syntax while supporting autoDispose which is a core feature for numerous commonly requested enhancements.
Implementing this feature along with #106546 would allow package:riverpod to use the context API again. This would both significantly simplify its usage and allow fusing package:riverpod/package:provider
Proposal
Since the described behavior exists as a performance optimization, my proposal would be to keep the default behavior of context.dependOnInheritedWidget as is, and instead add a new API
This could either be a flag
context.dependOnInheritedWidgetOfExactType<T>(removeDependencyOnRebuild: true);or a new BuildContext method (although I'm not sure how to name it).
This flag/method would tell ComponentElement to keep track of the InheritedWidget dependencies within ComponentElement.build, and remove the unused ones.
This feature would need to consider #106546, to avoid calling InheritedWidget.removeDependent every time the consumer rebuilds, and instead only when a consumer truly stops listening to an InheritedWidget.