Skip to content

Optimize Parse object #195

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/lib/data/base/api_response.dart
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -304,7 +304,7 @@ class _MyAppState extends State<MyApp> {
if (result.success) {
if (result.result is ParseObject) {
final ParseObject parseObject = result.result;
print(parseObject.className);
print(parseObject.parseClassName);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<DietPlan> actual = List<DietPlan>();
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<DietPlan> actual = List<DietPlan>();

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<DietPlan> actual = List<DietPlan>();

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);
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion lib/src/network/parse_live_query.dart
Original file line number Diff line number Diff line change
@@ -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=', '');

4 changes: 2 additions & 2 deletions lib/src/network/parse_query.dart
Original file line number Diff line number Diff line change
@@ -246,7 +246,7 @@ class QueryBuilder<T extends ParseObject> {
// 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<String, dynamic>(
_SINGLE_QUERY, '\"$column\":{\"\$inQuery\":$inQuery}'));
@@ -255,7 +255,7 @@ class QueryBuilder<T extends ParseObject> {
//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<String, dynamic>(
_SINGLE_QUERY, '\"$column\":{\"\$notInQuery\":$inQuery}'));
121 changes: 94 additions & 27 deletions lib/src/objects/parse_base.dart
Original file line number Diff line number Diff line change
@@ -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<String, dynamic> _unsavedChanges = Map<String, dynamic>();
final Map<String, dynamic> _savingChanges = Map<String, dynamic>();

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

set objectId(String objectId) => set<String>(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<dynamic>());
}
return false;
}

bool _areChildrenDirty(Set<dynamic> 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<dynamic>(keyVarCreatedAt) is String) {
@@ -40,7 +73,7 @@ abstract class ParseBase {
@protected
Map<String, dynamic> toJson({bool full, bool forApiRQ = false}) {
final Map<String, dynamic> map = <String, dynamic>{
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<String, dynamic> 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<DateTime>(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<String, dynamic> objectData) =>
void _setObjectData(Map<String, dynamic> objectData) =>
_objectData = objectData;

/// Returns the objects variables
@protected
Map<String, dynamic> getObjectData() => _objectData ?? Map<String, dynamic>();
Map<String, dynamic> _getObjectData() => _objectData ?? Map<String, dynamic>();

bool containsValue(Object value) {
return _getObjectData().containsValue(value);
}

bool containsKey(Object key) {
return _getObjectData().containsKey(key);
}

dynamic operator [](Object key) {
get<dynamic>(key);
}

void operator []=(String key, dynamic value) {
set<dynamic>(key, value);
}
/// Saves in storage
Future<void> 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<T>(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>(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<int> and an int will be returned, null, or a defaultValue if
/// provided
dynamic get<T>(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<String, dynamic> 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<bool> 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<String, dynamic> toPointer() => encodeObject(className, objectId);
Map<String, dynamic> 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<String, dynamic> getObjectData() => _getObjectData();

@protected @Deprecated('Prefer to use _setObjectData method, or operator [] for certain key.')
void setObjectData(Map<String, dynamic> objectData) =>
_setObjectData(objectData);
}
8 changes: 4 additions & 4 deletions lib/src/objects/parse_config.dart
Original file line number Diff line number Diff line change
@@ -18,9 +18,9 @@ class ParseConfig extends ParseObject {
final String uri = '${ParseCoreData().serverUrl}/config';
final Response result = await _client.get(uri);
return handleResponse<ParseConfig>(
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<ParseConfig>(
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);
}
}
}
6 changes: 3 additions & 3 deletions lib/src/objects/parse_file.dart
Original file line number Diff line number Diff line change
@@ -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<ParseFile>(
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);
}
}
}
12 changes: 6 additions & 6 deletions lib/src/objects/parse_function.dart
Original file line number Diff line number Diff line change
@@ -30,13 +30,13 @@ class ParseCloudFunction extends ParseObject {
{Map<String, dynamic> parameters, Map<String, dynamic> 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<ParseCloudFunction>(
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<String, dynamic> parameters, Map<String, dynamic> 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<T>(
this, result, ParseApiRQ.executeObjectionFunction, _debug, className);
this, result, ParseApiRQ.executeObjectionFunction, _debug, parseClassName);
}
}
14 changes: 7 additions & 7 deletions lib/src/objects/parse_installation.dart
Original file line number Diff line number Diff line change
@@ -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,30 +175,30 @@ class ParseInstallation extends ParseObject {
}

return handleResponse<ParseInstallation>(
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<ParseResponse> _save() async {
if (getObjectData()[keyVarObjectId] == null) {
if (objectId== null) {
return create();
} else {
try {
final String uri =
'${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<ParseInstallation>(
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);
}
}
}
112 changes: 78 additions & 34 deletions lib/src/objects/parse_object.dart
Original file line number Diff line number Diff line change
@@ -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<String, dynamic> 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<ParseObject>(
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<ParseObject>(
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,39 +67,50 @@ 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<ParseObject>(
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);
}
}

Future<ParseResponse> update() async {
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<ParseObject>(
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<ParseResponse> 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<ParseResponse> _saveChildren(dynamic object) async {
@@ -149,15 +160,30 @@ class ParseObject extends ParseBase implements ParseCloneable {

for (List<ParseObject> chunk in chunks) {
final List<dynamic> requests = chunk.map<dynamic>((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 = <String, dynamic>{
@@ -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<ParseObject> uniqueObjects,
Set<ParseFile> uniqueFiles, Set<ParseObject> seen, Set<ParseObject> seenNew) {
bool _collectionDirtyChildren(
dynamic object,
Set<ParseObject> uniqueObjects,
Set<ParseFile> uniqueFiles,
Set<ParseObject> seen,
Set<ParseObject> 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, <dynamic>[value]);
}

/// Add a multiple elements to an array of an object
void setAddAllUnique(String key, List<dynamic> 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<ParseObject>(
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<ParseObject>(
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<ParseObject>(
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<ParseObject>(
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);
}
}
}
6 changes: 3 additions & 3 deletions lib/src/objects/parse_relation.dart
Original file line number Diff line number Diff line change
@@ -18,22 +18,22 @@ class ParseRelation<T extends ParseObject> {

void add(T object) {
if (object != null) {
_targetClass = object.getClassName();
_targetClass = object.parseClassName;
_objects.add(object);
_parent.addRelation(_key, _objects.toList());
}
}

void remove(T object) {
if (object != null) {
_targetClass = object.getClassName();
_targetClass = object.parseClassName;
_objects.remove(object);
_parent.removeRelation(_key, _objects.toList());
}
}

Map<String, dynamic> toJson() =>
<String, String>{'__type': keyRelation, 'className': _objects?.first?.className, 'objects': parseEncode(_objects?.toList())};
<String, String>{'__type': keyRelation, 'className': _objects?.first?.parseClassName, 'objects': parseEncode(_objects?.toList())};

ParseRelation<T> fromJson(Map<String, dynamic> map) => ParseRelation<T>()
.._objects = parseDecode(map['objects'])
4 changes: 2 additions & 2 deletions lib/src/objects/parse_session.dart
Original file line number Diff line number Diff line change
@@ -39,9 +39,9 @@ class ParseSession extends ParseObject implements ParseCloneable {
final Response response = await _client.get(url);

return handleResponse<ParseSession>(
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);
}
}
}
44 changes: 24 additions & 20 deletions lib/src/objects/parse_user.dart
Original file line number Diff line number Diff line change
@@ -26,10 +26,14 @@ class ParseUser extends ParseObject implements ParseCloneable {
this.sessionToken = sessionToken;
}

ParseUser.forQuery() : super(keyClassUser);

ParseUser.clone(Map<String, dynamic> map)
: this(map[keyVarUsername], map[keyVarPassword], map[keyVarEmail]);

ParseUser.forQuery() : super(keyClassUser);
@override
dynamic clone(Map<String, dynamic> 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: <String, String>{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(<String, dynamic>{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(<String, dynamic>{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);
}
}

26 changes: 20 additions & 6 deletions lib/src/objects/response/parse_response_builder.dart
Original file line number Diff line number Diff line change
@@ -74,10 +74,13 @@ class _ParseResponseBuilder {
for (int i = 0; i < object.length; i++) {
final Map<String, dynamic> objectResult = list[i];
if (objectResult.containsKey('success')) {
final T item = _handleSingleResult<T>(object[i], objectResult['success'], false);
final T item = _handleSingleResult<T>(
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<T> _handleMultipleResults<T>(dynamic object, List<dynamic> data) {
List<T> _handleMultipleResults<T>(T object, List<dynamic> data) {
final List<T> resultsList = List<T>();
for (dynamic value in data) {
resultsList.add(_handleSingleResult<T>(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<String, dynamic> unsaved = Map<String, dynamic>();
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\"}';
}
}
}