Line data Source code
1 : part of flutter_data;
2 :
3 : /// A mixin to "tag" and ensure the implementation of an [id] getter
4 : /// in data classes managed through Flutter Data.
5 : ///
6 : /// It contains private state and methods to track the model's identity.
7 : abstract class DataModel<T extends DataModel<T>> {
8 : Object? get id;
9 :
10 1 : DataModel() {
11 3 : final _isRepoInitialized = internalRepositories.containsKey(_internalType);
12 3 : if (_isRepoInitialized && remoteAdapter.autoInitializeModels) init();
13 : }
14 :
15 : late String _key;
16 2 : String get _internalType => DataHelpers.getType<T>();
17 : DataStateNotifier<T?>? _notifier;
18 1 : T get _this => this as T;
19 :
20 : /// Exposes this type's [RemoteAdapter]
21 1 : RemoteAdapter<T> get remoteAdapter =>
22 4 : internalRepositories[_internalType]!.remoteAdapter as RemoteAdapter<T>;
23 :
24 : /// Exposes the [DataStateNotifier] that fetched this model;
25 : /// typically used to access `notifier.reload()`.
26 : /// ONLY available if loaded via [Repository.watchOneNotifier].
27 2 : DataStateNotifier<T?>? get notifier => _notifier;
28 :
29 : // methods
30 :
31 1 : T saveLocal() {
32 5 : remoteAdapter.localAdapter.save(_key, _this);
33 1 : return _this;
34 : }
35 :
36 : // privately set the notifier
37 1 : void _updateNotifier(DataStateNotifier<T?>? value) {
38 1 : _notifier = value;
39 : }
40 : }
41 :
42 : /// Extension that adds syntax-sugar to data classes,
43 : /// linking them to common [Repository] methods such as
44 : /// [save] and [delete].
45 : extension DataModelExtension<T extends DataModel<T>> on DataModel<T> {
46 : /// Copy identity (internal key) from an old model to a new one
47 : /// to signal they are the same.
48 1 : T was(T model) {
49 3 : if (model._key != _key) {
50 : T _old;
51 : T _new;
52 :
53 : // if the passed-in model has no ID
54 : // then treat the original as prevalent
55 2 : if (model.id == null && id != null) {
56 : _old = model;
57 1 : _new = _this;
58 : } else {
59 : // in all other cases, treat the passed-in
60 : // model as prevalent
61 1 : _old = _this;
62 : _new = model;
63 : }
64 :
65 1 : final _oldKey = _old._key;
66 3 : if (_key != _new._key) {
67 2 : _key = _new._key;
68 : }
69 3 : if (_key != _old._key) {
70 2 : _old._key = _key;
71 3 : remoteAdapter.graph.removeKey(_oldKey);
72 : }
73 :
74 1 : if (_old.id != null) {
75 5 : remoteAdapter.graph.removeId(_internalType, _old.id!);
76 2 : remoteAdapter.graph
77 4 : .getKeyForId(_internalType, _old.id, keyIfAbsent: _key);
78 : }
79 : }
80 1 : return _this;
81 : }
82 :
83 : /// Saves this model through a call equivalent to [Repository.save].
84 : ///
85 : /// Usage: `await post.save()`, `author.save(remote: false, params: {'a': 'x'})`.
86 1 : Future<T> save({
87 : bool? remote,
88 : Map<String, dynamic>? params,
89 : Map<String, String>? headers,
90 : OnSuccessOne<T>? onSuccess,
91 : OnErrorOne<T>? onError,
92 : }) async {
93 3 : return await remoteAdapter.save(
94 1 : _this,
95 : remote: remote,
96 : params: params,
97 : headers: headers,
98 : onSuccess: onSuccess,
99 : onError: onError,
100 : );
101 : }
102 :
103 : /// Deletes this model through a call equivalent to [Repository.delete].
104 : ///
105 : /// Usage: `await post.delete()`
106 1 : Future<T?> delete({
107 : bool? remote,
108 : Map<String, dynamic>? params,
109 : Map<String, String>? headers,
110 : OnSuccessOne<T>? onSuccess,
111 : OnErrorOne<T>? onError,
112 : }) async {
113 3 : return await remoteAdapter.delete(
114 : this,
115 : remote: remote,
116 : params: params,
117 : headers: headers,
118 : onSuccess: onSuccess,
119 : onError: onError,
120 : );
121 : }
122 :
123 : /// Get the refreshed version from local storage.
124 0 : T? refresh() {
125 0 : return remoteAdapter.localAdapter.findOne(_key);
126 : }
127 :
128 : /// Re-fetch this model through a call equivalent to [Repository.findOne].
129 : /// with the current object/[id]
130 1 : Future<T?> reload({
131 : Map<String, dynamic>? params,
132 : Map<String, String>? headers,
133 : bool? background,
134 : DataRequestLabel? label,
135 : }) async {
136 3 : return await remoteAdapter.findOne(
137 : this,
138 : remote: true,
139 : params: params,
140 : headers: headers,
141 : background: background,
142 : label: label,
143 : );
144 : }
145 :
146 : /// Get all non-null [Relationship]s for this model.
147 1 : Iterable<Relationship> getRelationships() {
148 5 : final metadatas = remoteAdapter.localAdapter.relationshipsFor(_this).values;
149 :
150 : return metadatas
151 2 : .map((metadata) {
152 1 : final relationship = metadata['instance'] as Relationship?;
153 1 : return relationship?.initialize(
154 : owner: this,
155 1 : name: metadata['name'] as String,
156 1 : inverseName: metadata['inverse'] as String?,
157 : );
158 : })
159 1 : .toList()
160 1 : .filterNulls;
161 : }
162 :
163 1 : T init({bool save = true}) {
164 6 : _key = remoteAdapter.graph.getKeyForId(_internalType, id,
165 1 : keyIfAbsent: DataHelpers.generateKey<T>())!;
166 : if (save) {
167 5 : remoteAdapter.localAdapter.save(_key, _this, notify: false);
168 : }
169 1 : getRelationships();
170 1 : return _this;
171 : }
172 : }
173 :
174 : /// Returns a model's `_key` private attribute.
175 : ///
176 : /// Useful for testing, debugging or usage in [RemoteAdapter] subclasses.
177 2 : String? keyFor<T extends DataModel<T>>(T model) => model._key;
178 :
179 1 : @visibleForTesting
180 : @protected
181 : RemoteAdapter? adapterFor<T extends DataModel<T>>(T model) =>
182 1 : model.remoteAdapter;
|