Line data Source code
1 : part of flutter_data;
2 :
3 : mixin _RemoteAdapterWatch<T extends DataModel<T>> on _RemoteAdapter<T> {
4 1 : @protected
5 : DataStateNotifier<List<T>?> watchAllNotifier({
6 : bool? remote,
7 : Map<String, dynamic>? params,
8 : Map<String, String>? headers,
9 : bool? syncLocal,
10 : String? finder,
11 : DataRequestLabel? label,
12 : }) {
13 1 : remote ??= _remote;
14 : syncLocal ??= false;
15 :
16 3 : final _maybeFinder = _internalHolder?.finders[finder]?.call(this);
17 2 : final _finder = _maybeFinder is DataFinderAll<T> ? _maybeFinder : findAll;
18 :
19 : // we can't use `findAll`'s default internal label
20 : // because we need a label to handle events
21 2 : label ??= DataRequestLabel('watchAll', type: internalType);
22 :
23 1 : log(label, 'initializing');
24 :
25 : // closure to get latest models
26 1 : List<T>? _getUpdatedModels() {
27 2 : return localAdapter.findAll();
28 : }
29 :
30 1 : final _notifier = DataStateNotifier<List<T>?>(
31 2 : data: DataState(_getUpdatedModels(), isLoading: remote!),
32 : );
33 :
34 2 : _notifier._reloadFn = () async {
35 1 : if (!_notifier.mounted) {
36 : return;
37 : }
38 :
39 : if (remote!) {
40 1 : _notifier.updateWith(isLoading: true);
41 : }
42 :
43 2 : await _finder(
44 : remote: remote,
45 : params: params,
46 : headers: headers,
47 : syncLocal: syncLocal,
48 : label: label,
49 1 : onError: (e, label, _) async {
50 : try {
51 2 : await onError<List<T>>(e, label);
52 1 : } on DataException catch (err) {
53 : e = err;
54 : } catch (_) {
55 : rethrow;
56 : }
57 1 : if (_notifier.mounted) {
58 1 : _notifier.updateWith(isLoading: false, exception: e);
59 : }
60 : return null;
61 : },
62 : );
63 : if (remote) {
64 : // trigger doneLoading to ensure state is updated with isLoading=false
65 4 : graph._notify([label.toString()], type: DataGraphEventType.doneLoading);
66 : }
67 : };
68 :
69 : // kick off
70 1 : _notifier.reload();
71 :
72 3 : final _dispose = graph.addListener((event) {
73 1 : if (!_notifier.mounted) {
74 : return;
75 : }
76 :
77 : // handle done loading
78 2 : if (_notifier.data.isLoading &&
79 4 : event.keys.last == label.toString() &&
80 2 : event.type == DataGraphEventType.doneLoading) {
81 1 : final models = _getUpdatedModels();
82 1 : _notifier.updateWith(model: models, isLoading: false, exception: null);
83 : }
84 :
85 3 : if (_notifier.data.isLoading == false &&
86 2 : event.type.isNode &&
87 4 : event.keys.first.startsWith(internalType)) {
88 1 : final models = _getUpdatedModels();
89 1 : log(label!, 'updated models');
90 1 : _notifier.updateWith(model: models);
91 : }
92 : });
93 :
94 2 : _notifier.onDispose = () {
95 1 : log(label!, 'disposing');
96 1 : _dispose();
97 : };
98 : return _notifier;
99 : }
100 :
101 1 : @protected
102 : DataStateNotifier<T?> watchOneNotifier(
103 : String key, {
104 : bool? remote,
105 : Map<String, dynamic>? params,
106 : Map<String, String>? headers,
107 : AlsoWatch<T>? alsoWatch,
108 : String? finder,
109 : DataRequestLabel? label,
110 : }) {
111 2 : final id = graph.getIdForKey(key);
112 :
113 1 : remote ??= _remote;
114 4 : final _maybeFinder = _internalHolder?.finders[finder]?.call(this);
115 2 : final _finder = _maybeFinder is DataFinderOne<T> ? _maybeFinder : findOne;
116 :
117 : // we can't use `findOne`'s default internal label
118 : // because we need a label to handle events
119 : label ??=
120 3 : DataRequestLabel('watchOne', id: key.detypify(), type: internalType);
121 :
122 : var _alsoWatchPairs = <List<String>>{};
123 :
124 : // closure to get latest model and watchable relationship pairs
125 1 : T? _getUpdatedModel({DataStateNotifier<T?>? withNotifier}) {
126 2 : final model = localAdapter.findOne(key);
127 : if (model != null) {
128 : _alsoWatchPairs = {
129 5 : ...?alsoWatch?.call(model).filterNulls.map((r) {
130 5 : return r.keys.map((key) => [r._ownerKey!, key]);
131 2 : }).expand((_) => _)
132 : };
133 : if (withNotifier != null) {
134 1 : model._updateNotifier(withNotifier);
135 : }
136 : } else {
137 : // if there is no model nothing should be watched, reset pairs
138 : _alsoWatchPairs = {};
139 : }
140 : return model;
141 : }
142 :
143 1 : final _notifier = DataStateNotifier<T?>(
144 2 : data: DataState(_getUpdatedModel(), isLoading: remote!),
145 : );
146 :
147 1 : log(label,
148 1 : 'initializing${alsoWatch != null ? ' (with relationships)' : ''}');
149 :
150 2 : _notifier._reloadFn = () async {
151 1 : if (!_notifier.mounted || id == null) return;
152 :
153 : if (remote!) {
154 1 : _notifier.updateWith(isLoading: true);
155 : }
156 :
157 2 : final model = await _finder(
158 : id,
159 : remote: remote,
160 : params: params,
161 : headers: headers,
162 : label: label,
163 1 : onError: (e, label, _) async {
164 : try {
165 1 : await onError<T>(e, label);
166 1 : } on DataException catch (err) {
167 : e = err;
168 : } catch (_) {
169 : rethrow;
170 : }
171 1 : if (_notifier.mounted) {
172 1 : _notifier.updateWith(isLoading: false, exception: e);
173 : }
174 : return null;
175 : },
176 : );
177 : // trigger doneLoading to ensure state is updated with isLoading=false
178 1 : final _key = model?._key;
179 : if (remote && _key != null) {
180 4 : graph._notify([_key, label.toString()],
181 : type: DataGraphEventType.doneLoading);
182 : }
183 : };
184 :
185 : // trigger local + async loading
186 1 : _notifier.reload();
187 :
188 : // local buffer useful to reduce amount of notifier updates
189 2 : var _model = _notifier.data.model;
190 :
191 : // start listening to graph for further changes
192 3 : final _dispose = graph.addListener((event) {
193 1 : if (!_notifier.mounted) return;
194 :
195 1 : final _key = _model?._key ?? key;
196 :
197 : // get the latest updated model with watchable relationships
198 : // (_alsoWatchPairs) in order to determine whether there is
199 : // something that will cause an event (with the introduction
200 : // of `andEach` even seemingly unrelated models could trigger)
201 1 : _model = _getUpdatedModel(withNotifier: _notifier);
202 :
203 2 : if (event.keys.contains(_key)) {
204 : // handle done loading
205 2 : if (_notifier.data.isLoading &&
206 4 : event.keys.last == label.toString() &&
207 2 : event.type == DataGraphEventType.doneLoading) {
208 1 : _notifier.updateWith(
209 : model: _model, isLoading: false, exception: null);
210 : }
211 :
212 : // add/update
213 2 : if (event.type == DataGraphEventType.addNode ||
214 2 : event.type == DataGraphEventType.updateNode) {
215 3 : if (_notifier.data.isLoading == false) {
216 3 : log(label!, 'added/updated node ${event.keys}');
217 1 : _notifier.updateWith(model: _model);
218 : }
219 : }
220 :
221 : // temporarily restore removed pair so that watchedRelationshipUpdate
222 : // has a chance to apply the update
223 2 : if (event.type == DataGraphEventType.removeEdge &&
224 3 : !event.keys.first.startsWith('id:')) {
225 2 : _alsoWatchPairs.add(event.keys);
226 : }
227 : }
228 :
229 : // handle deletion
230 2 : if (event.type == DataGraphEventType.removeNode && _model == null) {
231 3 : log(label!, 'removed node ${event.keys}');
232 1 : _notifier.updateWith(model: null);
233 : }
234 :
235 : // updates on watched relationships condition
236 2 : final watchedRelationshipUpdate = event.type.isEdge &&
237 : _alsoWatchPairs
238 2 : .where((pair) =>
239 6 : pair.sorted().toString() == event.keys.sorted().toString())
240 1 : .isNotEmpty;
241 :
242 : // updates on watched models (of relationships) condition
243 2 : final watchedModelUpdate = event.type.isNode &&
244 : _alsoWatchPairs
245 5 : .where((pair) => pair.contains(event.keys.first))
246 1 : .isNotEmpty;
247 :
248 : // if model is loaded and any condition passes, notify update
249 3 : if (_notifier.data.isLoading == false &&
250 : (watchedRelationshipUpdate || watchedModelUpdate)) {
251 3 : log(label!, 'relationship update ${event.keys}');
252 1 : _notifier.updateWith(model: _model);
253 : }
254 : });
255 :
256 2 : _notifier.onDispose = () {
257 1 : log(label!, 'disposing');
258 1 : _dispose();
259 : };
260 : return _notifier;
261 : }
262 : }
|