Skip to content

Commit c565fa7

Browse files
yulingtianxiaphillwiggins
authored andcommitted
Optimize Parse object (parse-community#195)
* 1. Support dirty and unsavedChanges 2. Optimize some API. * Fix bugs about unsaved changes. * Mard Deprecated API. Fixed:parse-community#195 (comment)
1 parent 9177625 commit c565fa7

16 files changed

+257
-128
lines changed

example/lib/data/base/api_response.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import 'api_error.dart';
55
class ApiResponse {
66
ApiResponse(this.success, this.statusCode, this.results, this.error)
77
: count = results?.length ?? 0,
8-
result = results?.first;
8+
result = results?.isNotEmpty ?? false ? results.first : null;
99

1010
final bool success;
1111
final int statusCode;

example/lib/main.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ class _MyAppState extends State<MyApp> {
304304
if (result.success) {
305305
if (result.result is ParseObject) {
306306
final ParseObject parseObject = result.result;
307-
print(parseObject.className);
307+
print(parseObject.parseClassName);
308308
}
309309
}
310310
}

example/test/data/repository/diet_plan/repository_diet_plan_api_test.dart

+10-10
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ void main() {
3434
test('add DietPlan from API', () async {
3535
// Given
3636
final DietPlan expected = getDummyDietPlan();
37-
expected.getObjectData()['objectId'] = null;
37+
expected['objectId'] = null;
3838

3939
// When
4040
final ApiResponse response = await repository.add(expected);
@@ -51,11 +51,11 @@ void main() {
5151
// Given
5252
final List<DietPlan> actual = List<DietPlan>();
5353
final DietPlan item1 = getDummyDietPlan();
54-
item1.getObjectData()['objectId'] = null;
54+
item1['objectId'] = null;
5555
item1.protein = 5;
5656
actual.add(item1);
5757
final DietPlan item2 = getDummyDietPlan();
58-
item2.getObjectData()['objectId'] = null;
58+
item2['objectId'] = null;
5959
item2.protein = 6;
6060
actual.add(item2);
6161

@@ -74,7 +74,7 @@ void main() {
7474
test('getById DietPlan from API', () async {
7575
// Given
7676
final DietPlan dummy = getDummyDietPlan();
77-
dummy.getObjectData()['objectId'] = null;
77+
dummy['objectId'] = null;
7878

7979
// When
8080
final ApiResponse response = await repository.add(dummy);
@@ -95,7 +95,7 @@ void main() {
9595
test('getNewerThan DietPlan from API', () async {
9696
// Given
9797
final DietPlan dummy = getDummyDietPlan();
98-
dummy.getObjectData()['objectId'] = null;
98+
dummy['objectId'] = null;
9999

100100
// When
101101
final ApiResponse baseResponse = await repository.add(dummy);
@@ -120,11 +120,11 @@ void main() {
120120
final List<DietPlan> actual = List<DietPlan>();
121121

122122
final DietPlan item1 = getDummyDietPlan();
123-
item1.getObjectData()['objectId'] = null;
123+
item1['objectId'] = null;
124124
item1.protein = 5;
125125
actual.add(item1);
126126
final DietPlan item2 = getDummyDietPlan();
127-
item2.getObjectData()['objectId'] = null;
127+
item2['objectId'] = null;
128128
item2.protein = 6;
129129
actual.add(item2);
130130

@@ -142,7 +142,7 @@ void main() {
142142
test('update DietPlan from API', () async {
143143
// Given
144144
final DietPlan expected = getDummyDietPlan();
145-
expected.getObjectData()['objectId'] = null;
145+
expected['objectId'] = null;
146146
final ApiResponse response = await repository.add(expected);
147147
final DietPlan initialResponse = response.result;
148148

@@ -165,11 +165,11 @@ void main() {
165165
final List<DietPlan> actual = List<DietPlan>();
166166

167167
final DietPlan item1 = getDummyDietPlan();
168-
item1.getObjectData()['objectId'] = null;
168+
item1['objectId'] = null;
169169
item1.protein = 7;
170170
actual.add(item1);
171171
final DietPlan item2 = getDummyDietPlan();
172-
item2.getObjectData()['objectId'] = null;
172+
item2['objectId'] = null;
173173
item2.protein = 8;
174174
actual.add(item2);
175175
await repository.addAll(actual);

example/test/data/repository/diet_plan/repository_diet_plan_db_test.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ void main() {
126126
// Given
127127
final DietPlan expected = getDummyDietPlan();
128128
// ignore: invalid_use_of_protected_member
129-
expected.getObjectData()[keyVarUpdatedAt] = DateTime.now();
129+
expected[keyVarUpdatedAt] = DateTime.now();
130130
final ApiResponse response = await repository.add(expected);
131131

132132
// When

lib/src/network/parse_live_query.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class LiveQuery {
4848
_liveQueryURL = _liveQueryURL.replaceAll('http', 'ws');
4949
}
5050

51-
final String _className = query.object.className;
51+
final String _className = query.object.parseClassName;
5252
query.limiters.clear(); //Remove limits in LiveQuery
5353
final String _where = query._buildQuery().replaceAll('where=', '');
5454

lib/src/network/parse_query.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ class QueryBuilder<T extends ParseObject> {
246246
// Add a constraint to the query that requires a particular key's value match another QueryBuilder
247247
// ignore: always_specify_types
248248
void whereMatchesQuery(String column, QueryBuilder query) {
249-
final String inQuery = query._buildQueryRelational(query.object.className);
249+
final String inQuery = query._buildQueryRelational(query.object.parseClassName);
250250

251251
queries.add(MapEntry<String, dynamic>(
252252
_SINGLE_QUERY, '\"$column\":{\"\$inQuery\":$inQuery}'));
@@ -255,7 +255,7 @@ class QueryBuilder<T extends ParseObject> {
255255
//Add a constraint to the query that requires a particular key's value does not match another QueryBuilder
256256
// ignore: always_specify_types
257257
void whereDoesNotMatchQuery(String column, QueryBuilder query) {
258-
final String inQuery = query._buildQueryRelational(query.object.className);
258+
final String inQuery = query._buildQueryRelational(query.object.parseClassName);
259259

260260
queries.add(MapEntry<String, dynamic>(
261261
_SINGLE_QUERY, '\"$column\":{\"\$notInQuery\":$inQuery}'));

lib/src/objects/parse_base.dart

+94-27
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
part of flutter_parse_sdk;
22

33
abstract class ParseBase {
4-
String className;
4+
String parseClassName;
55
Type type;
6-
7-
String setClassName(String className) => this.className = className;
8-
9-
String getClassName() => className;
6+
bool _dirty = false; // reserved property
7+
final Map<String, dynamic> _unsavedChanges = Map<String, dynamic>();
8+
final Map<String, dynamic> _savingChanges = Map<String, dynamic>();
109

1110
/// Stores all the values of a class
1211
Map<String, dynamic> _objectData = Map<String, dynamic>();
@@ -16,6 +15,40 @@ abstract class ParseBase {
1615

1716
set objectId(String objectId) => set<String>(keyVarObjectId, objectId);
1817

18+
bool isDirty({String key}) {
19+
if (key != null) {
20+
return _unsavedChanges[key] != null;
21+
}
22+
return _isDirty(true);
23+
}
24+
25+
bool _isDirty(bool considerChildren) {
26+
if (_dirty || _unsavedChanges.isNotEmpty) {
27+
return true;
28+
}
29+
30+
if (considerChildren) {
31+
return _areChildrenDirty(Set<dynamic>());
32+
}
33+
return false;
34+
}
35+
36+
bool _areChildrenDirty(Set<dynamic> seenObjects) {
37+
if (seenObjects.contains(this)) {
38+
return false;
39+
}
40+
seenObjects.add(this);
41+
if (_dirty || _unsavedChanges.isNotEmpty) {
42+
return true;
43+
}
44+
_getObjectData().forEach((String key, dynamic value) {
45+
if (value is ParseObject && value._areChildrenDirty(seenObjects)) {
46+
return true;
47+
}
48+
});
49+
return false;
50+
}
51+
1952
/// Returns [DateTime] createdAt
2053
DateTime get createdAt {
2154
if (get<dynamic>(keyVarCreatedAt) is String) {
@@ -40,7 +73,7 @@ abstract class ParseBase {
4073
@protected
4174
Map<String, dynamic> toJson({bool full, bool forApiRQ = false}) {
4275
final Map<String, dynamic> map = <String, dynamic>{
43-
keyVarClassName: className,
76+
keyVarClassName: parseClassName,
4477
};
4578

4679
if (objectId != null) {
@@ -55,7 +88,8 @@ abstract class ParseBase {
5588
map[keyVarUpdatedAt] = _parseDateFormat.format(updatedAt);
5689
}
5790

58-
getObjectData().forEach((String key, dynamic value) {
91+
final Map<String, dynamic> target = forApiRQ ? _unsavedChanges : _getObjectData();
92+
target.forEach((String key, dynamic value) {
5993
if (!map.containsKey(key)) {
6094
map[key] = parseEncode(value, full: full);
6195
}
@@ -81,7 +115,7 @@ abstract class ParseBase {
81115
}
82116

83117
objectData.forEach((String key, dynamic value) {
84-
if (key == className || key == '__type') {
118+
if (key == parseClassName || key == '__type') {
85119
// NO OP
86120
} else if (key == keyVarObjectId) {
87121
objectId = value;
@@ -98,9 +132,9 @@ abstract class ParseBase {
98132
set<DateTime>(keyVarUpdatedAt, value);
99133
}
100134
} else if (key == keyVarAcl) {
101-
getObjectData()[keyVarAcl] = ParseACL().fromJson(value);
135+
this[keyVarAcl] = ParseACL().fromJson(value);
102136
} else {
103-
getObjectData()[key] = parseDecode(value);
137+
this[key] = parseDecode(value);
104138
}
105139
});
106140

@@ -113,17 +147,32 @@ abstract class ParseBase {
113147

114148
/// Sets all the objects variables
115149
@protected
116-
void setObjectData(Map<String, dynamic> objectData) =>
150+
void _setObjectData(Map<String, dynamic> objectData) =>
117151
_objectData = objectData;
118152

119153
/// Returns the objects variables
120154
@protected
121-
Map<String, dynamic> getObjectData() => _objectData ?? Map<String, dynamic>();
155+
Map<String, dynamic> _getObjectData() => _objectData ?? Map<String, dynamic>();
156+
157+
bool containsValue(Object value) {
158+
return _getObjectData().containsValue(value);
159+
}
122160

161+
bool containsKey(Object key) {
162+
return _getObjectData().containsKey(key);
163+
}
164+
165+
dynamic operator [](Object key) {
166+
get<dynamic>(key);
167+
}
168+
169+
void operator []=(String key, dynamic value) {
170+
set<dynamic>(key, value);
171+
}
123172
/// Saves in storage
124173
Future<void> saveInStorage(String key) async {
125174
final String objectJson = json.encode(toJson(full: true));
126-
await ParseCoreData().getStore()
175+
ParseCoreData().getStore()
127176
..setString(key, objectJson);
128177
}
129178

@@ -134,25 +183,29 @@ abstract class ParseBase {
134183
/// needed or not, set to false
135184
void set<T>(String key, T value, {bool forceUpdate = true}) {
136185
if (value != null) {
137-
if (getObjectData().containsKey(key)) {
186+
if (_getObjectData().containsKey(key)) {
187+
if (_getObjectData()[key] == value) {
188+
return;
189+
}
138190
if (forceUpdate) {
139-
getObjectData()[key] = value;
191+
_getObjectData()[key] = value;
140192
}
141193
} else {
142-
getObjectData()[key] = value;
194+
_getObjectData()[key] = value;
143195
}
196+
_unsavedChanges[key] = value;
144197
}
145198
}
146199

147200
///Set the [ParseACL] governing this object.
148201
void setACL<ParseACL>(ParseACL acl) {
149-
getObjectData()[keyVarAcl] = acl;
202+
_getObjectData()[keyVarAcl] = acl;
150203
}
151204

152205
///Access the [ParseACL] governing this object.
153206
ParseACL getACL() {
154-
if (getObjectData().containsKey(keyVarAcl)) {
155-
return getObjectData()[keyVarAcl];
207+
if (_getObjectData().containsKey(keyVarAcl)) {
208+
return _getObjectData()[keyVarAcl];
156209
} else {
157210
return ParseACL();
158211
}
@@ -164,12 +217,12 @@ abstract class ParseBase {
164217
/// getType<int> and an int will be returned, null, or a defaultValue if
165218
/// provided
166219
dynamic get<T>(String key, {T defaultValue}) {
167-
if (getObjectData().containsKey(key)) {
168-
if (T != null && getObjectData()[key] is T) {
169-
final T data = getObjectData()[key];
220+
if (_getObjectData().containsKey(key)) {
221+
if (T != null && _getObjectData()[key] is T) {
222+
final T data = _getObjectData()[key];
170223
return data;
171224
} else {
172-
return getObjectData()[key];
225+
return _getObjectData()[key];
173226
}
174227
} else {
175228
return defaultValue;
@@ -184,7 +237,7 @@ abstract class ParseBase {
184237
await unpin();
185238
final Map<String, dynamic> objectMap = parseEncode(this, full: true);
186239
final String json = jsonEncode(objectMap);
187-
await ParseCoreData().getStore()
240+
ParseCoreData().getStore()
188241
..setString(objectId, json);
189242
return true;
190243
} else {
@@ -197,7 +250,7 @@ abstract class ParseBase {
197250
/// Replicates Android SDK pin process and saves object to storage
198251
Future<bool> unpin({String key}) async {
199252
if (objectId != null) {
200-
await ParseCoreData().getStore()
253+
ParseCoreData().getStore()
201254
..remove(key ?? objectId);
202255
return true;
203256
}
@@ -210,7 +263,7 @@ abstract class ParseBase {
210263
/// Replicates Android SDK pin process and saves object to storage
211264
dynamic fromPin(String objectId) async {
212265
if (objectId != null) {
213-
final CoreStore coreStore = await ParseCoreData().getStore();
266+
final CoreStore coreStore = ParseCoreData().getStore();
214267
final String itemFromStore = await coreStore.getString(objectId);
215268

216269
if (itemFromStore != null) {
@@ -220,5 +273,19 @@ abstract class ParseBase {
220273
return null;
221274
}
222275

223-
Map<String, dynamic> toPointer() => encodeObject(className, objectId);
276+
Map<String, dynamic> toPointer() => encodeObject(parseClassName, objectId);
277+
278+
/// Deprecated
279+
@Deprecated('Prefer to use parseClassName')
280+
String className;
281+
@Deprecated('Prefer to use parseClassName')
282+
String getClassName() => parseClassName;
283+
@Deprecated('Prefer to use parseClassName')
284+
String setClassName(String className) => parseClassName = className;
285+
@protected @Deprecated('Prefer to use _getObjectData method, or operator [] for certain key.')
286+
Map<String, dynamic> getObjectData() => _getObjectData();
287+
288+
@protected @Deprecated('Prefer to use _setObjectData method, or operator [] for certain key.')
289+
void setObjectData(Map<String, dynamic> objectData) =>
290+
_setObjectData(objectData);
224291
}

lib/src/objects/parse_config.dart

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ class ParseConfig extends ParseObject {
1818
final String uri = '${ParseCoreData().serverUrl}/config';
1919
final Response result = await _client.get(uri);
2020
return handleResponse<ParseConfig>(
21-
this, result, ParseApiRQ.getConfigs, _debug, className);
21+
this, result, ParseApiRQ.getConfigs, _debug, parseClassName);
2222
} on Exception catch (e) {
23-
return handleException(e, ParseApiRQ.getConfigs, _debug, className);
23+
return handleException(e, ParseApiRQ.getConfigs, _debug, parseClassName);
2424
}
2525
}
2626

@@ -31,9 +31,9 @@ class ParseConfig extends ParseObject {
3131
final String body = '{\"params\":{\"$key\": \"${parseEncode(value)}\"}}';
3232
final Response result = await _client.put(uri, body: body);
3333
return handleResponse<ParseConfig>(
34-
this, result, ParseApiRQ.addConfig, _debug, className);
34+
this, result, ParseApiRQ.addConfig, _debug, parseClassName);
3535
} on Exception catch (e) {
36-
return handleException(e, ParseApiRQ.addConfig, _debug, className);
36+
return handleException(e, ParseApiRQ.addConfig, _debug, parseClassName);
3737
}
3838
}
3939
}

0 commit comments

Comments
 (0)