Line data Source code
1 : import 'package:flutter/foundation.dart';
2 : import 'package:flutter/material.dart';
3 : import 'package:flutter/widgets.dart';
4 :
5 : import '../notifiers/state_notifier.dart';
6 : import '../provider/filters.dart';
7 : import '../provider/state_notifier_provider.dart';
8 :
9 : part 'consumer.dart';
10 :
11 : typedef _ListenerCallback<T> = void Function(T);
12 :
13 : /// A [StatefulWidget] that can read providers.
14 : abstract class ConsumerStatefulWidget extends StatefulWidget {
15 5 : const ConsumerStatefulWidget({Key? key}) : super(key: key);
16 :
17 : @override
18 : // ignore: no_logic_in_create_state
19 : ConsumerState createState();
20 :
21 2 : @override
22 : ConsumerStatefulElement createElement() {
23 2 : return ConsumerStatefulElement(this);
24 : }
25 : }
26 :
27 : /// A [State] that has access to a [BuilderRef] through [ref], allowing
28 : /// it to read providers.
29 : abstract class ConsumerState<T extends ConsumerStatefulWidget>
30 : extends State<T> {
31 : /// An object that allows widgets to interact with providers.
32 : late final BuilderRef ref = context as BuilderRef;
33 : }
34 :
35 : class ConsumerStatefulElement extends StatefulElement implements BuilderRef {
36 4 : ConsumerStatefulElement(StatefulWidget widget) : super(widget);
37 :
38 : Map<StateNotifier, Function> _builders = {};
39 : Map<StateNotifier, Function> _listeners = {};
40 : Map<StateNotifier, Function> _selectListeners = {};
41 : Map<StateNotifier, Function> _whenListeners = {};
42 :
43 : Map<StateNotifier, BaseStateNotifierProvider> _targets = {};
44 :
45 : // initialized at true for the first build
46 : bool _isExternalBuild = true;
47 :
48 : bool _mounted = true;
49 : bool _init = false;
50 :
51 : @override // coverage:ignore-line
52 : void didChangeDependencies() {
53 : super.didChangeDependencies(); // coverage:ignore-line
54 : _isExternalBuild = true; // coverage:ignore-line
55 : }
56 :
57 2 : @override
58 : void mount(Element? parent, Object? newSlot) {
59 2 : super.mount(parent, newSlot);
60 2 : _init = true;
61 : }
62 :
63 : /// force the widget update
64 1 : void _rebuild() {
65 1 : if (_mounted) {
66 1 : markNeedsBuild();
67 : }
68 : }
69 :
70 2 : @override
71 : void unmount() {
72 2 : _mounted = false;
73 2 : _clearDependencies();
74 2 : super.unmount();
75 : }
76 :
77 : /// clear the listeners for this widget
78 2 : void _clearDependencies() {
79 2 : void clearListeners(Map<StateNotifier, Function> map) {
80 4 : for (final e in map.entries) {
81 4 : if (!e.key.disposed) {
82 6 : (e.key as dynamic).removeListener(e.value);
83 : }
84 : }
85 2 : map.clear();
86 : }
87 :
88 4 : clearListeners(_builders);
89 4 : clearListeners(_listeners);
90 4 : clearListeners(_selectListeners);
91 4 : clearListeners(_whenListeners);
92 :
93 4 : _targets.clear();
94 : }
95 :
96 1 : @override
97 : N listen<N extends StateNotifier<S>, S>(
98 : BaseStateNotifierProvider<N, S> providerOrFilter, {
99 : required void Function(N notifier) callback,
100 : String? tag,
101 : }) {
102 1 : return _buildWatcher(
103 : providerOrFilter: providerOrFilter,
104 : tag: tag,
105 : isListener: true,
106 1 : callback: (notifier) {
107 2 : if (_init && mounted) {
108 1 : callback(notifier);
109 : }
110 : },
111 : );
112 : }
113 :
114 : /// read a Notifier from one provider and subscribe the widget to the changes of this Notifier.
115 : ///
116 : /// [providerOrTarget] this param is required to decide when the Consumer
117 : /// needs to be rebuilded, if [providerOrTarget] is a [SimpleProvider] or a
118 : /// [StateProvider] the widget will be rebuilded when the notify method is called
119 : /// inside a SimpleNotifier or StateNotifier.
120 : ///
121 : /// If [providerOrTarget] is a value gotten from .select, .ids or .when
122 : /// the widget only will be rebuilded depending of the condition of each method.
123 1 : @override
124 : N watch<N extends StateNotifier<S>, S>(
125 : BaseStateNotifierProvider<N, S> providerOrFilter, {
126 : String? tag,
127 : }) {
128 1 : return _buildWatcher(
129 : providerOrFilter: providerOrFilter,
130 : tag: tag,
131 : isListener: false,
132 2 : callback: (_) => _rebuild(),
133 : ); // coverage:ignore-line
134 : }
135 :
136 : /// read a Notifier from one provider and subscribe the widget to the changes of this Notifier.
137 : ///
138 : /// [target] is a value gotten from .select or .when
139 : ///
140 : /// the widget only will be rebuilded depending of the condition of each method.
141 1 : @override
142 : R select<N extends StateNotifier<S>, S, R>(
143 : SelectFilteredProvider<N, S, R> filter,
144 : ) {
145 : // if the widget was rebuilded
146 1 : if (_isExternalBuild) {
147 1 : _clearDependencies();
148 : }
149 1 : _isExternalBuild = false;
150 1 : final notifier = filter.notifier;
151 :
152 2 : final insideDependencies = _builders.containsKey(notifier);
153 : // if there is not a listener for the current provider
154 : if (!insideDependencies) {
155 3 : filter.reaction = (_) => _rebuild();
156 1 : filter.createListener();
157 : // add the listener to the current notifier
158 3 : _builders[notifier] = filter.listener;
159 2 : _targets[notifier] = filter;
160 2 : notifier.addListener(filter.listener);
161 : }
162 2 : return (_targets[notifier] as SelectFilteredProvider)
163 : .selectValue; // coverage:ignore-line
164 : }
165 :
166 : // coverage:ignore-start
167 : @override
168 : void debugFillProperties(DiagnosticPropertiesBuilder properties) {
169 : super.debugFillProperties(properties);
170 : properties.add(
171 : DiagnosticsProperty(
172 : '_notifiers',
173 : _builders.keys.map((e) => e.runtimeType.toString()),
174 : ),
175 : );
176 : }
177 : // coverage:ignore-end
178 : }
179 :
180 : /// A interface that must be implemented in the ConsumerWidget
181 : abstract class BuilderRef {
182 : /// A function to read SimpleProvider or a StateProvider and subscribe to the events.
183 : ///
184 : /// this method returns the Notifier linked to the provider
185 : N listen<N extends StateNotifier<S>, S>(
186 : BaseStateNotifierProvider<N, S> providerOrFilter, {
187 : required void Function(N notifier) callback,
188 : String? tag,
189 : });
190 :
191 : /// A function to read SimpleProvider or a StateProvider and subscribe to the events.
192 : ///
193 : /// this method returns the Notifier linked to the provider
194 : N watch<N extends StateNotifier<S>, S>(
195 : BaseStateNotifierProvider<N, S> providerOrFilter, {
196 : String? tag,
197 : });
198 :
199 : /// A function to read SimpleProvider or a StateProvider and subscribe to the events.
200 : ///
201 : /// this method returns the value returned by the select or when methods
202 : R select<N extends StateNotifier<S>, S, R>(
203 : SelectFilteredProvider<N, S, R> filter,
204 : );
205 : }
206 :
207 : /// {@template meedu.consumerwidget}
208 : /// A base-class for widgets that wants to listen to providers
209 : /// ```dart
210 : /// class Example extends ConsumerWidget {
211 : /// const Example({Key? key}): super(key: key);
212 : ///
213 : /// @override
214 : /// Widget build(BuildContext context, ref) {
215 : /// final value = ref.watch(myProvider);
216 : /// return YOUR_WIDGET;
217 : /// }
218 : /// }
219 : /// ```
220 : /// {@endtemplate}
221 : abstract class ConsumerWidget extends ConsumerStatefulWidget {
222 : // ignore: public_member_api_docs
223 5 : const ConsumerWidget({Key? key}) : super(key: key);
224 :
225 : // ignore: public_member_api_docs
226 : Widget build(BuildContext context, BuilderRef ref);
227 :
228 2 : @override
229 : // ignore: library_private_types_in_public_api
230 2 : _ConsumerState createState() => _ConsumerState();
231 : }
232 :
233 : class _ConsumerState<T extends ConsumerWidget>
234 : extends ConsumerState<ConsumerWidget> {
235 : /// An object that allows widgets to interact with providers.
236 2 : @override
237 2 : BuilderRef get ref => context as BuilderRef;
238 :
239 2 : @override
240 : Widget build(BuildContext context) {
241 6 : return widget.build(context, ref);
242 : }
243 : }
|