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