diff --git a/example/lib/data/base/api_response.dart b/example/lib/data/base/api_response.dart index 5f6fdf336..41e4067ab 100644 --- a/example/lib/data/base/api_response.dart +++ b/example/lib/data/base/api_response.dart @@ -5,7 +5,7 @@ import 'api_error.dart'; class ApiResponse { ApiResponse(this.success, this.statusCode, this.results, this.error) : count = results?.length ?? 0, - result = results?.first; + result = results?.isNotEmpty ?? false ? results.first : null; final bool success; final int statusCode; diff --git a/example/lib/main.dart b/example/lib/main.dart index 6a6f48435..c16dbb45a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -304,7 +304,7 @@ class _MyAppState extends State { if (result.success) { if (result.result is ParseObject) { final ParseObject parseObject = result.result; - print(parseObject.className); + print(parseObject.parseClassName); } } } diff --git a/example/test/data/repository/diet_plan/repository_diet_plan_api_test.dart b/example/test/data/repository/diet_plan/repository_diet_plan_api_test.dart index 9536a8ea8..e6c18926f 100644 --- a/example/test/data/repository/diet_plan/repository_diet_plan_api_test.dart +++ b/example/test/data/repository/diet_plan/repository_diet_plan_api_test.dart @@ -34,7 +34,7 @@ void main() { test('add DietPlan from API', () async { // Given final DietPlan expected = getDummyDietPlan(); - expected.getObjectData()['objectId'] = null; + expected['objectId'] = null; // When final ApiResponse response = await repository.add(expected); @@ -51,11 +51,11 @@ void main() { // Given final List actual = List(); final DietPlan item1 = getDummyDietPlan(); - item1.getObjectData()['objectId'] = null; + item1['objectId'] = null; item1.protein = 5; actual.add(item1); final DietPlan item2 = getDummyDietPlan(); - item2.getObjectData()['objectId'] = null; + item2['objectId'] = null; item2.protein = 6; actual.add(item2); @@ -74,7 +74,7 @@ void main() { test('getById DietPlan from API', () async { // Given final DietPlan dummy = getDummyDietPlan(); - dummy.getObjectData()['objectId'] = null; + dummy['objectId'] = null; // When final ApiResponse response = await repository.add(dummy); @@ -95,7 +95,7 @@ void main() { test('getNewerThan DietPlan from API', () async { // Given final DietPlan dummy = getDummyDietPlan(); - dummy.getObjectData()['objectId'] = null; + dummy['objectId'] = null; // When final ApiResponse baseResponse = await repository.add(dummy); @@ -120,11 +120,11 @@ void main() { final List actual = List(); final DietPlan item1 = getDummyDietPlan(); - item1.getObjectData()['objectId'] = null; + item1['objectId'] = null; item1.protein = 5; actual.add(item1); final DietPlan item2 = getDummyDietPlan(); - item2.getObjectData()['objectId'] = null; + item2['objectId'] = null; item2.protein = 6; actual.add(item2); @@ -142,7 +142,7 @@ void main() { test('update DietPlan from API', () async { // Given final DietPlan expected = getDummyDietPlan(); - expected.getObjectData()['objectId'] = null; + expected['objectId'] = null; final ApiResponse response = await repository.add(expected); final DietPlan initialResponse = response.result; @@ -165,11 +165,11 @@ void main() { final List actual = List(); final DietPlan item1 = getDummyDietPlan(); - item1.getObjectData()['objectId'] = null; + item1['objectId'] = null; item1.protein = 7; actual.add(item1); final DietPlan item2 = getDummyDietPlan(); - item2.getObjectData()['objectId'] = null; + item2['objectId'] = null; item2.protein = 8; actual.add(item2); await repository.addAll(actual); diff --git a/example/test/data/repository/diet_plan/repository_diet_plan_db_test.dart b/example/test/data/repository/diet_plan/repository_diet_plan_db_test.dart index f60dff301..d29564a73 100644 --- a/example/test/data/repository/diet_plan/repository_diet_plan_db_test.dart +++ b/example/test/data/repository/diet_plan/repository_diet_plan_db_test.dart @@ -126,7 +126,7 @@ void main() { // Given final DietPlan expected = getDummyDietPlan(); // ignore: invalid_use_of_protected_member - expected.getObjectData()[keyVarUpdatedAt] = DateTime.now(); + expected[keyVarUpdatedAt] = DateTime.now(); final ApiResponse response = await repository.add(expected); // When diff --git a/lib/src/network/parse_live_query.dart b/lib/src/network/parse_live_query.dart index 5b11aadd9..5c4a48f46 100644 --- a/lib/src/network/parse_live_query.dart +++ b/lib/src/network/parse_live_query.dart @@ -48,7 +48,7 @@ class LiveQuery { _liveQueryURL = _liveQueryURL.replaceAll('http', 'ws'); } - final String _className = query.object.className; + final String _className = query.object.parseClassName; query.limiters.clear(); //Remove limits in LiveQuery final String _where = query._buildQuery().replaceAll('where=', ''); diff --git a/lib/src/network/parse_query.dart b/lib/src/network/parse_query.dart index 749527e96..797fc4182 100644 --- a/lib/src/network/parse_query.dart +++ b/lib/src/network/parse_query.dart @@ -246,7 +246,7 @@ class QueryBuilder { // Add a constraint to the query that requires a particular key's value match another QueryBuilder // ignore: always_specify_types void whereMatchesQuery(String column, QueryBuilder query) { - final String inQuery = query._buildQueryRelational(query.object.className); + final String inQuery = query._buildQueryRelational(query.object.parseClassName); queries.add(MapEntry( _SINGLE_QUERY, '\"$column\":{\"\$inQuery\":$inQuery}')); @@ -255,7 +255,7 @@ class QueryBuilder { //Add a constraint to the query that requires a particular key's value does not match another QueryBuilder // ignore: always_specify_types void whereDoesNotMatchQuery(String column, QueryBuilder query) { - final String inQuery = query._buildQueryRelational(query.object.className); + final String inQuery = query._buildQueryRelational(query.object.parseClassName); queries.add(MapEntry( _SINGLE_QUERY, '\"$column\":{\"\$notInQuery\":$inQuery}')); diff --git a/lib/src/objects/parse_base.dart b/lib/src/objects/parse_base.dart index 71e01e0c0..c0c133298 100644 --- a/lib/src/objects/parse_base.dart +++ b/lib/src/objects/parse_base.dart @@ -1,12 +1,11 @@ part of flutter_parse_sdk; abstract class ParseBase { - String className; + String parseClassName; Type type; - - String setClassName(String className) => this.className = className; - - String getClassName() => className; + bool _dirty = false; // reserved property + final Map _unsavedChanges = Map(); + final Map _savingChanges = Map(); /// Stores all the values of a class Map _objectData = Map(); @@ -16,6 +15,40 @@ abstract class ParseBase { set objectId(String objectId) => set(keyVarObjectId, objectId); + bool isDirty({String key}) { + if (key != null) { + return _unsavedChanges[key] != null; + } + return _isDirty(true); + } + + bool _isDirty(bool considerChildren) { + if (_dirty || _unsavedChanges.isNotEmpty) { + return true; + } + + if (considerChildren) { + return _areChildrenDirty(Set()); + } + return false; + } + + bool _areChildrenDirty(Set seenObjects) { + if (seenObjects.contains(this)) { + return false; + } + seenObjects.add(this); + if (_dirty || _unsavedChanges.isNotEmpty) { + return true; + } + _getObjectData().forEach((String key, dynamic value) { + if (value is ParseObject && value._areChildrenDirty(seenObjects)) { + return true; + } + }); + return false; + } + /// Returns [DateTime] createdAt DateTime get createdAt { if (get(keyVarCreatedAt) is String) { @@ -40,7 +73,7 @@ abstract class ParseBase { @protected Map toJson({bool full, bool forApiRQ = false}) { final Map map = { - keyVarClassName: className, + keyVarClassName: parseClassName, }; if (objectId != null) { @@ -55,7 +88,8 @@ abstract class ParseBase { map[keyVarUpdatedAt] = _parseDateFormat.format(updatedAt); } - getObjectData().forEach((String key, dynamic value) { + final Map target = forApiRQ ? _unsavedChanges : _getObjectData(); + target.forEach((String key, dynamic value) { if (!map.containsKey(key)) { map[key] = parseEncode(value, full: full); } @@ -81,7 +115,7 @@ abstract class ParseBase { } objectData.forEach((String key, dynamic value) { - if (key == className || key == '__type') { + if (key == parseClassName || key == '__type') { // NO OP } else if (key == keyVarObjectId) { objectId = value; @@ -98,9 +132,9 @@ abstract class ParseBase { set(keyVarUpdatedAt, value); } } else if (key == keyVarAcl) { - getObjectData()[keyVarAcl] = ParseACL().fromJson(value); + this[keyVarAcl] = ParseACL().fromJson(value); } else { - getObjectData()[key] = parseDecode(value); + this[key] = parseDecode(value); } }); @@ -113,17 +147,32 @@ abstract class ParseBase { /// Sets all the objects variables @protected - void setObjectData(Map objectData) => + void _setObjectData(Map objectData) => _objectData = objectData; /// Returns the objects variables @protected - Map getObjectData() => _objectData ?? Map(); + Map _getObjectData() => _objectData ?? Map(); + + bool containsValue(Object value) { + return _getObjectData().containsValue(value); + } + bool containsKey(Object key) { + return _getObjectData().containsKey(key); + } + + dynamic operator [](Object key) { + get(key); + } + + void operator []=(String key, dynamic value) { + set(key, value); + } /// Saves in storage Future saveInStorage(String key) async { final String objectJson = json.encode(toJson(full: true)); - await ParseCoreData().getStore() + ParseCoreData().getStore() ..setString(key, objectJson); } @@ -134,25 +183,29 @@ abstract class ParseBase { /// needed or not, set to false void set(String key, T value, {bool forceUpdate = true}) { if (value != null) { - if (getObjectData().containsKey(key)) { + if (_getObjectData().containsKey(key)) { + if (_getObjectData()[key] == value) { + return; + } if (forceUpdate) { - getObjectData()[key] = value; + _getObjectData()[key] = value; } } else { - getObjectData()[key] = value; + _getObjectData()[key] = value; } + _unsavedChanges[key] = value; } } ///Set the [ParseACL] governing this object. void setACL(ParseACL acl) { - getObjectData()[keyVarAcl] = acl; + _getObjectData()[keyVarAcl] = acl; } ///Access the [ParseACL] governing this object. ParseACL getACL() { - if (getObjectData().containsKey(keyVarAcl)) { - return getObjectData()[keyVarAcl]; + if (_getObjectData().containsKey(keyVarAcl)) { + return _getObjectData()[keyVarAcl]; } else { return ParseACL(); } @@ -164,12 +217,12 @@ abstract class ParseBase { /// getType and an int will be returned, null, or a defaultValue if /// provided dynamic get(String key, {T defaultValue}) { - if (getObjectData().containsKey(key)) { - if (T != null && getObjectData()[key] is T) { - final T data = getObjectData()[key]; + if (_getObjectData().containsKey(key)) { + if (T != null && _getObjectData()[key] is T) { + final T data = _getObjectData()[key]; return data; } else { - return getObjectData()[key]; + return _getObjectData()[key]; } } else { return defaultValue; @@ -184,7 +237,7 @@ abstract class ParseBase { await unpin(); final Map objectMap = parseEncode(this, full: true); final String json = jsonEncode(objectMap); - await ParseCoreData().getStore() + ParseCoreData().getStore() ..setString(objectId, json); return true; } else { @@ -197,7 +250,7 @@ abstract class ParseBase { /// Replicates Android SDK pin process and saves object to storage Future unpin({String key}) async { if (objectId != null) { - await ParseCoreData().getStore() + ParseCoreData().getStore() ..remove(key ?? objectId); return true; } @@ -210,7 +263,7 @@ abstract class ParseBase { /// Replicates Android SDK pin process and saves object to storage dynamic fromPin(String objectId) async { if (objectId != null) { - final CoreStore coreStore = await ParseCoreData().getStore(); + final CoreStore coreStore = ParseCoreData().getStore(); final String itemFromStore = await coreStore.getString(objectId); if (itemFromStore != null) { @@ -220,5 +273,19 @@ abstract class ParseBase { return null; } - Map toPointer() => encodeObject(className, objectId); + Map toPointer() => encodeObject(parseClassName, objectId); + + /// Deprecated + @Deprecated('Prefer to use parseClassName') + String className; + @Deprecated('Prefer to use parseClassName') + String getClassName() => parseClassName; + @Deprecated('Prefer to use parseClassName') + String setClassName(String className) => parseClassName = className; + @protected @Deprecated('Prefer to use _getObjectData method, or operator [] for certain key.') + Map getObjectData() => _getObjectData(); + + @protected @Deprecated('Prefer to use _setObjectData method, or operator [] for certain key.') + void setObjectData(Map objectData) => + _setObjectData(objectData); } diff --git a/lib/src/objects/parse_config.dart b/lib/src/objects/parse_config.dart index 86e3e3292..8f7dedf16 100644 --- a/lib/src/objects/parse_config.dart +++ b/lib/src/objects/parse_config.dart @@ -18,9 +18,9 @@ class ParseConfig extends ParseObject { final String uri = '${ParseCoreData().serverUrl}/config'; final Response result = await _client.get(uri); return handleResponse( - this, result, ParseApiRQ.getConfigs, _debug, className); + this, result, ParseApiRQ.getConfigs, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.getConfigs, _debug, className); + return handleException(e, ParseApiRQ.getConfigs, _debug, parseClassName); } } @@ -31,9 +31,9 @@ class ParseConfig extends ParseObject { final String body = '{\"params\":{\"$key\": \"${parseEncode(value)}\"}}'; final Response result = await _client.put(uri, body: body); return handleResponse( - this, result, ParseApiRQ.addConfig, _debug, className); + this, result, ParseApiRQ.addConfig, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.addConfig, _debug, className); + return handleException(e, ParseApiRQ.addConfig, _debug, parseClassName); } } } diff --git a/lib/src/objects/parse_file.dart b/lib/src/objects/parse_file.dart index ee93e3f40..7bef32891 100644 --- a/lib/src/objects/parse_file.dart +++ b/lib/src/objects/parse_file.dart @@ -102,7 +102,7 @@ class ParseFile extends ParseObject { Response(json.encode(response), 201), ParseApiRQ.upload, _debug, - className); + parseClassName); } final String ext = path.extension(file.path).replaceAll('.', ''); @@ -120,9 +120,9 @@ class ParseFile extends ParseObject { name = map['name'].toString(); } return handleResponse( - this, response, ParseApiRQ.upload, _debug, className); + this, response, ParseApiRQ.upload, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.upload, _debug, className); + return handleException(e, ParseApiRQ.upload, _debug, parseClassName); } } } diff --git a/lib/src/objects/parse_function.dart b/lib/src/objects/parse_function.dart index 8f80d6a3d..d8e9e883f 100644 --- a/lib/src/objects/parse_function.dart +++ b/lib/src/objects/parse_function.dart @@ -30,13 +30,13 @@ class ParseCloudFunction extends ParseObject { {Map parameters, Map headers}) async { final String uri = '${_client.data.serverUrl}$_path'; if (parameters != null) { - setObjectData(parameters); + _setObjectData(parameters); } final Response result = - await _client.post(uri, body: json.encode(getObjectData())); + await _client.post(uri, body: json.encode(_getObjectData())); return handleResponse( - this, result, ParseApiRQ.execute, _debug, className); + this, result, ParseApiRQ.execute, _debug, parseClassName); } /// Executes a cloud function that returns a ParseObject type @@ -46,11 +46,11 @@ class ParseCloudFunction extends ParseObject { {Map parameters, Map headers}) async { final String uri = '${_client.data.serverUrl}$_path'; if (parameters != null) { - setObjectData(parameters); + _setObjectData(parameters); } final Response result = - await _client.post(uri, body: json.encode(getObjectData())); + await _client.post(uri, body: json.encode(_getObjectData())); return handleResponse( - this, result, ParseApiRQ.executeObjectionFunction, _debug, className); + this, result, ParseApiRQ.executeObjectionFunction, _debug, parseClassName); } } diff --git a/lib/src/objects/parse_installation.dart b/lib/src/objects/parse_installation.dart index 4aa0f5130..4950ba460 100644 --- a/lib/src/objects/parse_installation.dart +++ b/lib/src/objects/parse_installation.dart @@ -162,7 +162,7 @@ class ParseInstallation extends ParseObject { final String uri = '${_client.data.serverUrl}$keyEndPointInstallations'; final String body = json.encode(toJson(forApiRQ: true)); if (_debug) { - logRequest(ParseCoreData().appName, className, + logRequest(ParseCoreData().appName, parseClassName, ParseApiRQ.create.toString(), uri, body); } final Response result = await _client.post(uri, body: body); @@ -175,15 +175,15 @@ class ParseInstallation extends ParseObject { } return handleResponse( - this, result, ParseApiRQ.create, _debug, className); + this, result, ParseApiRQ.create, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.create, _debug, className); + return handleException(e, ParseApiRQ.create, _debug, parseClassName); } } /// Saves the current object online Future _save() async { - if (getObjectData()[keyVarObjectId] == null) { + if (objectId== null) { return create(); } else { try { @@ -191,14 +191,14 @@ class ParseInstallation extends ParseObject { '${ParseCoreData().serverUrl}$keyEndPointInstallations/$objectId'; final String body = json.encode(toJson(forApiRQ: true)); if (_debug) { - logRequest(ParseCoreData().appName, className, + logRequest(ParseCoreData().appName, parseClassName, ParseApiRQ.save.toString(), uri, body); } final Response result = await _client.put(uri, body: body); return handleResponse( - this, result, ParseApiRQ.save, _debug, className); + this, result, ParseApiRQ.save, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.save, _debug, className); + return handleException(e, ParseApiRQ.save, _debug, parseClassName); } } } diff --git a/lib/src/objects/parse_object.dart b/lib/src/objects/parse_object.dart index 592ae8aa8..d23d6127a 100644 --- a/lib/src/objects/parse_object.dart +++ b/lib/src/objects/parse_object.dart @@ -10,7 +10,7 @@ class ParseObject extends ParseBase implements ParseCloneable { ParseObject(String className, {bool debug, ParseHTTPClient client, bool autoSendSessionId}) : super() { - setClassName(className); + parseClassName = className; _path = '$keyEndPointClasses$className'; _debug = isDebugEnabled(objectLevelDebug: debug); @@ -25,7 +25,7 @@ class ParseObject extends ParseBase implements ParseCloneable { @override dynamic clone(Map map) => - ParseObject.clone(className)..fromJson(map); + ParseObject.clone(parseClassName)..fromJson(map); String _path; bool _debug; @@ -44,9 +44,9 @@ class ParseObject extends ParseBase implements ParseCloneable { final Response result = await _client.get(url); return handleResponse( - this, result, ParseApiRQ.get, _debug, className); + this, result, ParseApiRQ.get, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.get, _debug, className); + return handleException(e, ParseApiRQ.get, _debug, parseClassName); } } @@ -56,9 +56,9 @@ class ParseObject extends ParseBase implements ParseCloneable { final Uri url = getSanitisedUri(_client, '$_path'); final Response result = await _client.get(url); return handleResponse( - this, result, ParseApiRQ.getAll, _debug, className); + this, result, ParseApiRQ.getAll, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.getAll, _debug, className); + return handleException(e, ParseApiRQ.getAll, _debug, parseClassName); } } @@ -67,12 +67,13 @@ class ParseObject extends ParseBase implements ParseCloneable { try { final Uri url = getSanitisedUri(_client, '$_path'); final String body = json.encode(toJson(forApiRQ: true)); + _saveChanges(); final Response result = await _client.post(url, body: body); return handleResponse( - this, result, ParseApiRQ.create, _debug, className); + this, result, ParseApiRQ.create, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.create, _debug, className); + return handleException(e, ParseApiRQ.create, _debug, parseClassName); } } @@ -80,26 +81,36 @@ class ParseObject extends ParseBase implements ParseCloneable { try { final Uri url = getSanitisedUri(_client, '$_path/$objectId'); final String body = json.encode(toJson(forApiRQ: true)); + _saveChanges(); final Response result = await _client.put(url, body: body); return handleResponse( - this, result, ParseApiRQ.save, _debug, className); + this, result, ParseApiRQ.save, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.save, _debug, className); + return handleException(e, ParseApiRQ.save, _debug, parseClassName); } } /// Saves the current object online Future save() async { - final ParseResponse response = await _saveChildren(this); - if (response.success) { + final ParseResponse childrenResponse = await _saveChildren(this); + if (childrenResponse.success) { + ParseResponse response; if (objectId == null) { - return create(); - } else { - return update(); + response = await create(); + } else if (_isDirty(false)) { + response = await update(); + } + + if (response != null) { + if (response.success) { + _savingChanges.clear(); + } + else { + _revertSavingChanges(); + } } - } else { - return response; } + return childrenResponse; } Future _saveChildren(dynamic object) async { @@ -149,15 +160,30 @@ class ParseObject extends ParseBase implements ParseCloneable { for (List chunk in chunks) { final List requests = chunk.map((ParseObject obj) { - return obj.getRequestJson(obj.objectId == null ? 'POST' : 'PUT'); + return obj._getRequestJson(obj.objectId == null ? 'POST' : 'PUT'); }).toList(); + chunk.forEach((ParseObject obj) { + obj._saveChanges(); + }); final ParseResponse response = await batchRequest(requests, chunk); totalResponse.success &= response.success; if (response.success) { totalResponse.results.addAll(response.results); totalResponse.count += response.count; + for (int i = 0; i < response.count; i++) { + if (response.results[i] is ParseError) { + // Batch request succeed, but part of batch failed. + chunk[i]._revertSavingChanges(); + } + else { + chunk[i]._savingChanges.clear(); + } + } } else { - // TODO(yulingtianxia): If there was an error, we want to roll forward the save changes before rethrowing. + // If there was an error, we want to roll forward the save changes before rethrowing. + chunk.forEach((ParseObject obj) { + obj._revertSavingChanges(); + }); totalResponse.statusCode = response.statusCode; totalResponse.error = response.error; } @@ -167,7 +193,19 @@ class ParseObject extends ParseBase implements ParseCloneable { return totalResponse; } - dynamic getRequestJson(String method) { + void _saveChanges() { + _savingChanges.clear(); + _savingChanges.addAll(_unsavedChanges); + _unsavedChanges.clear(); + } + + void _revertSavingChanges() { + _savingChanges.addAll(_unsavedChanges); + _unsavedChanges.addAll(_savingChanges); + _savingChanges.clear(); + } + + dynamic _getRequestJson(String method) { final Uri tempUri = Uri.parse(_client.data.serverUrl); final String parsePath = tempUri.path; final dynamic request = { @@ -197,15 +235,19 @@ class ParseObject extends ParseBase implements ParseCloneable { } } } - } else if (!_canbeSerialized(aftersaving, value: getObjectData())) { + } else if (!_canbeSerialized(aftersaving, value: _getObjectData())) { return false; } // TODO(yulingtianxia): handle ACL return true; } - bool _collectionDirtyChildren(dynamic object, Set uniqueObjects, - Set uniqueFiles, Set seen, Set seenNew) { + bool _collectionDirtyChildren( + dynamic object, + Set uniqueObjects, + Set uniqueFiles, + Set seen, + Set seenNew) { if (object is List) { for (dynamic child in object) { if (!_collectionDirtyChildren( @@ -248,12 +290,13 @@ class ParseObject extends ParseBase implements ParseCloneable { seen.add(object); if (!_collectionDirtyChildren( - object.getObjectData(), uniqueObjects, uniqueFiles, seen, seenNew)) { + object._getObjectData(), uniqueObjects, uniqueFiles, seen, seenNew)) { return false; } - // TODO(yulingtianxia): Check Dirty - uniqueObjects.add(object); + if (object._isDirty(false)) { + uniqueObjects.add(object); + } } return true; } @@ -321,6 +364,7 @@ class ParseObject extends ParseBase implements ParseCloneable { void setAddUnique(String key, dynamic value) { _arrayOperation('AddUnique', key, [value]); } + /// Add a multiple elements to an array of an object void setAddAllUnique(String key, List values) { _arrayOperation('AddUnique', key, values); @@ -359,12 +403,12 @@ class ParseObject extends ParseBase implements ParseCloneable { '{\"$key\":{\"__op\":\"$arrayAction\",\"objects\":${json.encode(parseEncode(values))}}}'; final Response result = await _client.put(url, body: body); return handleResponse( - this, result, apiRQType, _debug, className); + this, result, apiRQType, _debug, parseClassName); } else { return null; } } on Exception catch (e) { - return handleException(e, apiRQType, _debug, className); + return handleException(e, apiRQType, _debug, parseClassName); } } @@ -417,12 +461,12 @@ class ParseObject extends ParseBase implements ParseCloneable { '{\"$key\":{\"__op\":\"$countAction\",\"amount\":$amount}}'; final Response result = await _client.put(url, body: body); return handleResponse( - this, result, apiRQType, _debug, className); + this, result, apiRQType, _debug, parseClassName); } else { return null; } } on Exception catch (e) { - return handleException(e, apiRQType, _debug, className); + return handleException(e, apiRQType, _debug, parseClassName); } } @@ -432,9 +476,9 @@ class ParseObject extends ParseBase implements ParseCloneable { final Uri url = getSanitisedUri(_client, '$_path', query: query); final Response result = await _client.get(url); return handleResponse( - this, result, ParseApiRQ.query, _debug, className); + this, result, ParseApiRQ.query, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.query, _debug, className); + return handleException(e, ParseApiRQ.query, _debug, parseClassName); } } @@ -446,9 +490,9 @@ class ParseObject extends ParseBase implements ParseCloneable { final Uri url = getSanitisedUri(_client, '$_path/$id'); final Response result = await _client.delete(url); return handleResponse( - this, result, ParseApiRQ.delete, _debug, className); + this, result, ParseApiRQ.delete, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.delete, _debug, className); + return handleException(e, ParseApiRQ.delete, _debug, parseClassName); } } } diff --git a/lib/src/objects/parse_relation.dart b/lib/src/objects/parse_relation.dart index 50aa8584e..307e244ca 100644 --- a/lib/src/objects/parse_relation.dart +++ b/lib/src/objects/parse_relation.dart @@ -18,7 +18,7 @@ class ParseRelation { void add(T object) { if (object != null) { - _targetClass = object.getClassName(); + _targetClass = object.parseClassName; _objects.add(object); _parent.addRelation(_key, _objects.toList()); } @@ -26,14 +26,14 @@ class ParseRelation { void remove(T object) { if (object != null) { - _targetClass = object.getClassName(); + _targetClass = object.parseClassName; _objects.remove(object); _parent.removeRelation(_key, _objects.toList()); } } Map toJson() => - {'__type': keyRelation, 'className': _objects?.first?.className, 'objects': parseEncode(_objects?.toList())}; + {'__type': keyRelation, 'className': _objects?.first?.parseClassName, 'objects': parseEncode(_objects?.toList())}; ParseRelation fromJson(Map map) => ParseRelation() .._objects = parseDecode(map['objects']) diff --git a/lib/src/objects/parse_session.dart b/lib/src/objects/parse_session.dart index 61f20ba2f..642b89129 100644 --- a/lib/src/objects/parse_session.dart +++ b/lib/src/objects/parse_session.dart @@ -39,9 +39,9 @@ class ParseSession extends ParseObject implements ParseCloneable { final Response response = await _client.get(url); return handleResponse( - this, response, ParseApiRQ.logout, _debug, className); + this, response, ParseApiRQ.logout, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.logout, _debug, className); + return handleException(e, ParseApiRQ.logout, _debug, parseClassName); } } } diff --git a/lib/src/objects/parse_user.dart b/lib/src/objects/parse_user.dart index 67a4adc3f..fec62403c 100644 --- a/lib/src/objects/parse_user.dart +++ b/lib/src/objects/parse_user.dart @@ -26,10 +26,14 @@ class ParseUser extends ParseObject implements ParseCloneable { this.sessionToken = sessionToken; } + ParseUser.forQuery() : super(keyClassUser); + ParseUser.clone(Map map) : this(map[keyVarUsername], map[keyVarPassword], map[keyVarEmail]); - ParseUser.forQuery() : super(keyClassUser); + @override + dynamic clone(Map map) => + ParseUser.clone(map)..fromJson(map); static const String keyEmailVerified = 'emailVerified'; static const String keyUsername = 'username'; @@ -96,10 +100,10 @@ class ParseUser extends ParseObject implements ParseCloneable { final Uri url = getSanitisedUri(_client, '$keyEndPointUserName'); final Response response = await _client.get(url, headers: headers); return _handleResponse(_getEmptyUser(), response, ParseApiRQ.currentUser, - _debug, _getEmptyUser().className); + _debug, _getEmptyUser().parseClassName); } on Exception catch (e) { return handleException( - e, ParseApiRQ.currentUser, _debug, _getEmptyUser().className); + e, ParseApiRQ.currentUser, _debug, _getEmptyUser().parseClassName); } } @@ -138,9 +142,9 @@ class ParseUser extends ParseObject implements ParseCloneable { body: json.encode(bodyData)); return _handleResponse( - this, response, ParseApiRQ.signUp, _debug, className); + this, response, ParseApiRQ.signUp, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.signUp, _debug, className); + return handleException(e, ParseApiRQ.signUp, _debug, parseClassName); } } @@ -164,9 +168,9 @@ class ParseUser extends ParseObject implements ParseCloneable { }); return _handleResponse( - this, response, ParseApiRQ.login, _debug, className); + this, response, ParseApiRQ.login, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.login, _debug, className); + return handleException(e, ParseApiRQ.login, _debug, parseClassName); } } @@ -187,9 +191,9 @@ class ParseUser extends ParseObject implements ParseCloneable { })); return _handleResponse( - this, response, ParseApiRQ.loginAnonymous, _debug, className); + this, response, ParseApiRQ.loginAnonymous, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.loginAnonymous, _debug, className); + return handleException(e, ParseApiRQ.loginAnonymous, _debug, parseClassName); } } @@ -213,9 +217,9 @@ class ParseUser extends ParseObject implements ParseCloneable { })); return _handleResponse( - this, response, ParseApiRQ.loginWith, _debug, className); + this, response, ParseApiRQ.loginWith, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.loginWith, _debug, className); + return handleException(e, ParseApiRQ.loginWith, _debug, parseClassName); } } @@ -230,7 +234,7 @@ class ParseUser extends ParseObject implements ParseCloneable { if (deleteLocalUserData == true) { unpin(key: keyParseStoreUser); - setObjectData(null); + _setObjectData(null); } try { @@ -239,9 +243,9 @@ class ParseUser extends ParseObject implements ParseCloneable { headers: {keyHeaderSessionToken: sessionId}); return _handleResponse( - this, response, ParseApiRQ.logout, _debug, className); + this, response, ParseApiRQ.logout, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.logout, _debug, className); + return handleException(e, ParseApiRQ.logout, _debug, parseClassName); } } @@ -252,10 +256,10 @@ class ParseUser extends ParseObject implements ParseCloneable { '${_client.data.serverUrl}$keyEndPointVerificationEmail', body: json.encode({keyVarEmail: emailAddress})); return _handleResponse(this, response, - ParseApiRQ.verificationEmailRequest, _debug, className); + ParseApiRQ.verificationEmailRequest, _debug, parseClassName); } on Exception catch (e) { return handleException( - e, ParseApiRQ.verificationEmailRequest, _debug, className); + e, ParseApiRQ.verificationEmailRequest, _debug, parseClassName); } } @@ -266,10 +270,10 @@ class ParseUser extends ParseObject implements ParseCloneable { '${_client.data.serverUrl}$keyEndPointRequestPasswordReset', body: json.encode({keyVarEmail: emailAddress})); return _handleResponse( - this, response, ParseApiRQ.requestPasswordReset, _debug, className); + this, response, ParseApiRQ.requestPasswordReset, _debug, parseClassName); } on Exception catch (e) { return handleException( - e, ParseApiRQ.requestPasswordReset, _debug, className); + e, ParseApiRQ.requestPasswordReset, _debug, parseClassName); } } @@ -293,9 +297,9 @@ class ParseUser extends ParseObject implements ParseCloneable { final Uri url = getSanitisedUri(_client, '$_path/$objectId'); final Response response = await _client.delete(url); return _handleResponse( - this, response, ParseApiRQ.destroy, _debug, className); + this, response, ParseApiRQ.destroy, _debug, parseClassName); } on Exception catch (e) { - return handleException(e, ParseApiRQ.destroy, _debug, className); + return handleException(e, ParseApiRQ.destroy, _debug, parseClassName); } } diff --git a/lib/src/objects/response/parse_response_builder.dart b/lib/src/objects/response/parse_response_builder.dart index 1a783eba9..782de2673 100644 --- a/lib/src/objects/response/parse_response_builder.dart +++ b/lib/src/objects/response/parse_response_builder.dart @@ -74,10 +74,13 @@ class _ParseResponseBuilder { for (int i = 0; i < object.length; i++) { final Map objectResult = list[i]; if (objectResult.containsKey('success')) { - final T item = _handleSingleResult(object[i], objectResult['success'], false); + final T item = _handleSingleResult( + object[i], objectResult['success'], false); response.results.add(item); } else { - final ParseError error = ParseError(code: objectResult[keyCode], message: objectResult[keyError].toString()); + final ParseError error = ParseError( + code: objectResult[keyCode], + message: objectResult[keyError].toString()); response.results.add(error); } } @@ -109,12 +112,11 @@ class _ParseResponseBuilder { } /// Handles a response with a multiple result object - List _handleMultipleResults(dynamic object, List data) { + List _handleMultipleResults(T object, List data) { final List resultsList = List(); for (dynamic value in data) { resultsList.add(_handleSingleResult(object, value, true)); } - return resultsList; } @@ -124,7 +126,19 @@ class _ParseResponseBuilder { if (createNewObject && object is ParseCloneable) { return object.clone(map); } else if (object is ParseObject) { - return object..fromJson(map); + // Merge unsaved changes and response. + final Map unsaved = Map(); + unsaved.addAll(object._unsavedChanges); + unsaved.forEach((String k, dynamic v) { + if (map[k] != null && map[k] != v) { + // Changes after save & before response. Keep it. + map.remove(k); + } + }); + return object + ..fromJson(map) + .._unsavedChanges.clear() + .._unsavedChanges.addAll(unsaved); } else { return null; } @@ -133,4 +147,4 @@ class _ParseResponseBuilder { bool isHealthCheck(Response apiResponse) { return apiResponse.body == '{\"status\":\"ok\"}'; } -} \ No newline at end of file +}