watchOneNotifier method
- @protected
- String key, {
- bool? remote,
- Map<
String, dynamic> ? params, - Map<
String, String> ? headers, - AlsoWatch<
T> ? alsoWatch, - String? finder,
- DataRequestLabel? label,
inherited
Implementation
@protected
DataStateNotifier<T?> watchOneNotifier(
String key, {
bool? remote,
Map<String, dynamic>? params,
Map<String, String>? headers,
AlsoWatch<T>? alsoWatch,
String? finder,
DataRequestLabel? label,
}) {
final id = graph.getIdForKey(key);
remote ??= _remote;
final maybeFinder = _internalHolder?.finders[finder]?.call(this);
final finderFn = maybeFinder is DataFinderOne<T> ? maybeFinder : findOne;
// we can't use `findOne`'s default internal label
// because we need a label to handle events
label ??=
DataRequestLabel('watchOne', id: key.detypify(), type: internalType);
var alsoWatchPairs = <List<String>>{};
// closure to get latest model and watchable relationship pairs
T? _getUpdatedModel() {
final model = localAdapter.findOne(key);
if (model != null) {
// get all metas provided via `alsoWatch`
final metas = alsoWatch
?.call(RelationshipGraphNode<T>())
.whereType<RelationshipMeta>();
// recursively get applicable watch key pairs for each meta -
// from top to bottom (e.g. `p`, `p.familia`, `p.familia.cottage`)
alsoWatchPairs = {
...?metas
?.map((meta) => _getPairsForMeta(meta._top, model))
.filterNulls
.expand((_) => _)
};
} else {
// if there is no model nothing should be watched, reset pairs
alsoWatchPairs = {};
}
return model;
}
final notifier = DataStateNotifier<T?>(
data: DataState(_getUpdatedModel(), isLoading: remote!),
);
final alsoWatchNames = alsoWatch
?.call(RelationshipGraphNode<T>())
.whereType<RelationshipMeta>()
.map((m) => m.name) ??
{};
log(label,
'initializing${alsoWatchNames.isNotEmpty ? ' (and also watching: ${alsoWatchNames.join(', ')})' : ''}');
notifier._reloadFn = () async {
if (!notifier.mounted || id == null) return;
if (remote!) {
notifier.updateWith(isLoading: true);
}
final model = await finderFn(
id,
remote: remote,
params: params,
headers: headers,
label: label,
onError: (e, label, _) async {
try {
await onError<T>(e, label);
} on DataException catch (err) {
e = err;
} catch (_) {
rethrow;
}
if (notifier.mounted) {
notifier.updateWith(isLoading: false, exception: e);
}
return null;
},
);
// trigger doneLoading to ensure state is updated with isLoading=false
final modelKey = model?._key;
if (remote && modelKey != null) {
graph._notify([modelKey, label.toString()],
type: DataGraphEventType.doneLoading);
}
};
// trigger local + async loading
notifier.reload();
// local buffer useful to reduce amount of notifier updates
var bufferModel = notifier.data.model;
final throttleDuration = ref.read(graphNotifierThrottleDurationProvider);
final throttledGraph = graph.throttle(() => throttleDuration);
final states = <DataState<T?>>[];
// start listening to graph for further changes
final dispose = throttledGraph.addListener((events) {
if (!notifier.mounted) return;
// get the latest updated model with watchable relationships
// (_alsoWatchPairs) in order to determine whether there is
// something that will cause an event (with the introduction
// of `andEach` even seemingly unrelated models could trigger)
bufferModel = _getUpdatedModel();
key = bufferModel?._key ?? key;
for (final event in events) {
if (event.keys.contains(key)) {
// handle done loading
if (notifier.data.isLoading &&
event.keys.last == label.toString() &&
event.type == DataGraphEventType.doneLoading) {
states
.add(DataState(bufferModel, isLoading: false, exception: null));
}
// add/update
if (event.type == DataGraphEventType.addNode ||
event.type == DataGraphEventType.updateNode) {
if (notifier.data.isLoading == false) {
log(label!, 'added/updated node ${event.keys}', logLevel: 2);
states.add(DataState(
bufferModel,
isLoading: notifier.data.isLoading,
exception: notifier.data.exception,
stackTrace: notifier.data.stackTrace,
));
}
}
// temporarily restore removed pair so that watchedRelationshipUpdate
// has a chance to apply the update
if (event.type == DataGraphEventType.removeEdge &&
!event.keys.first.startsWith('id:')) {
alsoWatchPairs.add(event.keys);
}
}
// handle deletion
if ([DataGraphEventType.removeNode, DataGraphEventType.clear]
.contains(event.type) &&
bufferModel == null) {
log(label!, 'removed node ${event.keys}', logLevel: 2);
states.add(DataState(
null,
isLoading: notifier.data.isLoading,
exception: notifier.data.exception,
stackTrace: notifier.data.stackTrace,
));
}
// updates on watched relationships condition
final watchedRelationshipUpdate = event.type.isEdge &&
alsoWatchPairs
.where((pair) =>
pair.sorted().toString() == event.keys.sorted().toString())
.isNotEmpty;
// updates on watched models (of relationships) condition
final watchedModelUpdate = event.type.isNode &&
alsoWatchPairs
.where((pair) => pair.contains(event.keys.first))
.isNotEmpty;
// if model is loaded and any condition passes, notify update
if (notifier.data.isLoading == false &&
(watchedRelationshipUpdate || watchedModelUpdate)) {
log(label!, 'relationship update ${event.keys}', logLevel: 2);
states.add(DataState(
bufferModel,
isLoading: notifier.data.isLoading,
exception: notifier.data.exception,
stackTrace: notifier.data.stackTrace,
));
}
}
_updateFromStates(states, notifier);
});
notifier.onDispose = () {
log(label!, 'disposing');
dispose();
};
return notifier;
}