From 95a31c6b318855739facd9881adc2bb22750e5ff Mon Sep 17 00:00:00 2001 From: rodrigosmarques Date: Fri, 5 Apr 2019 23:48:36 -0300 Subject: [PATCH 01/12] Add Support to Relational Queries Add Support to Relational Queries --- lib/src/network/parse_query.dart | 51 ++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/lib/src/network/parse_query.dart b/lib/src/network/parse_query.dart index d06536b07..c7dd58e85 100644 --- a/lib/src/network/parse_query.dart +++ b/lib/src/network/parse_query.dart @@ -237,6 +237,22 @@ class QueryBuilder { '\"$column\":{\"\$within\":{\"\$box\": [{\"__type\": \"GeoPoint\",\"latitude\":$latitudeS,\"longitude\":$longitudeS},{\"__type\": \"GeoPoint\",\"latitude\":$latitudeN,\"longitude\":$longitudeN}]}}')); } + // Add a constraint to the query that requires a particular key's value match another QueryBuilder + void whereMatchesQuery(String column, QueryBuilder query) { + String inQuery = query._buildQueryRelational(query.object.className); + + queries.add(MapEntry( + _SINGLE_QUERY, '\"$column\":{\"\$inQuery\":$inQuery}')); + } + + //Add a constraint to the query that requires a particular key's value does not match another QueryBuilder + void whereDoesNotMatchQuery(String column, QueryBuilder query) { + String inQuery = query._buildQueryRelational(query.object.className); + + queries.add(MapEntry( + _SINGLE_QUERY, '\"$column\":{\"\$notInQuery\":$inQuery}')); + } + /// Finishes the query and calls the server /// /// Make sure to call this after defining your queries @@ -250,6 +266,12 @@ class QueryBuilder { return 'where={${buildQueries(queries)}}${getLimiters(limiters)}'; } + /// Builds the query relational for Parse + String _buildQueryRelational(String className) { + queries = _checkForMultipleColumnInstances(queries); + return '{\"where\":{${buildQueries(queries)}},\"className\":\"$className\"${getLimitersRelational(limiters)}}'; + } + /// Runs through all queries and adds them to a query string String buildQueries(List> queries) { String queryBuilder = ''; @@ -284,17 +306,20 @@ class QueryBuilder { /// that the column and value are being queried against MapEntry _buildQueryWithColumnValueAndOperator( MapEntry columnAndValue, String queryOperator) { - final String key = columnAndValue.key; - final dynamic value = convertValueToCorrectType(parseEncode(columnAndValue.value)); + final dynamic value = + convertValueToCorrectType(parseEncode(columnAndValue.value)); if (queryOperator == _NO_OPERATOR_NEEDED) { - return MapEntry(_NO_OPERATOR_NEEDED, '\"$key\": ${jsonEncode(value)}'); + return MapEntry( + _NO_OPERATOR_NEEDED, '\"$key\": ${jsonEncode(value)}'); } else { String queryString = '\"$key\":'; - final Map queryOperatorAndValueMap = Map(); + final Map queryOperatorAndValueMap = + Map(); queryOperatorAndValueMap[queryOperator] = parseEncode(value); - final String formattedQueryOperatorAndValue = jsonEncode(queryOperatorAndValueMap); + final String formattedQueryOperatorAndValue = + jsonEncode(queryOperatorAndValueMap); queryString += '$formattedQueryOperatorAndValue'; return MapEntry(key, queryString); } @@ -336,7 +361,8 @@ class QueryBuilder { for (MapEntry queryToCompact in listOfQueriesCompact) { var queryToCompactValue = queryToCompact.value.toString(); queryToCompactValue = queryToCompactValue.replaceFirst("{", ""); - queryToCompactValue = queryToCompactValue.replaceRange(queryToCompactValue.length - 1, queryToCompactValue.length, ""); + queryToCompactValue = queryToCompactValue.replaceRange( + queryToCompactValue.length - 1, queryToCompactValue.length, ""); if (listOfQueriesCompact.first == queryToCompact) { queryEnd += queryToCompactValue.replaceAll(queryStart, ' '); } else { @@ -364,4 +390,17 @@ class QueryBuilder { }); return result; } + + /// Adds the limiters to the query relational, i.e. skip=10, limit=10 + String getLimitersRelational(Map map) { + String result = ''; + map.forEach((String key, dynamic value) { + if (result != null) { + result = result + ',\"$key":$value'; + } else { + result = '\"$key\":$value'; + } + }); + return result; + } } From 50a6ef9075b956b062efba2dadcc41683f65ffee Mon Sep 17 00:00:00 2001 From: rodrigosmarques Date: Sat, 6 Apr 2019 01:50:03 -0300 Subject: [PATCH 02/12] Add Support to Counting Objects Add Support to Counting Objects https://docs.parseplatform.org/rest/guide/#counting-objects --- lib/src/network/parse_query.dart | 11 +++++++++++ lib/src/objects/response/parse_response_builder.dart | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/lib/src/network/parse_query.dart b/lib/src/network/parse_query.dart index c7dd58e85..d32a5c1ea 100644 --- a/lib/src/network/parse_query.dart +++ b/lib/src/network/parse_query.dart @@ -260,6 +260,11 @@ class QueryBuilder { return object.query(_buildQuery()); } + ///Counts the number of objects that match this query + Future count() async { + return object.query(_buildQueryCount()); + } + /// Builds the query for Parse String _buildQuery() { queries = _checkForMultipleColumnInstances(queries); @@ -272,6 +277,12 @@ class QueryBuilder { return '{\"where\":{${buildQueries(queries)}},\"className\":\"$className\"${getLimitersRelational(limiters)}}'; } + /// Builds the query for Parse + String _buildQueryCount() { + queries = _checkForMultipleColumnInstances(queries); + return 'where={${buildQueries(queries)}}&count=1'; + } + /// Runs through all queries and adds them to a query string String buildQueries(List> queries) { String queryBuilder = ''; diff --git a/lib/src/objects/response/parse_response_builder.dart b/lib/src/objects/response/parse_response_builder.dart index 74075ca03..b7e06767b 100644 --- a/lib/src/objects/response/parse_response_builder.dart +++ b/lib/src/objects/response/parse_response_builder.dart @@ -71,6 +71,11 @@ class _ParseResponseBuilder { response.results = items; response.result = items; response.count = items.length; + } else if (map != null && map.length == 2 && map.containsKey('count')) { + final List results = [map['count']]; + response.results = results; + response.result = results; + response.count = map['count']; } else { final T item = _handleSingleResult(object, map, false); response.count = 1; From 086e036fe68ed682e2deab5526a6b7f3ba059b75 Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Sat, 6 Apr 2019 09:02:21 -0300 Subject: [PATCH 03/12] Update README.md Update documentation with example for Relational Queries and Counting Objects --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/README.md b/README.md index 37f1b35c6..37c1cc601 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,54 @@ The features available are:- * Ascending * Descending * Plenty more! + +## Relational queries +If you want to retrieve objects where a field contains an object that matches another query, you can use the +__whereMatchesQuery__ condition. +For example, imagine you vave Post class and a Comment class, where each Comment has a pointer to its parent Post. +You can find comments on posts with images by doing: + +```dart + QueryBuilder queryPost = + QueryBuilder(ParseObject('Post')) + ..whereValueExists('image', true); + + QueryBuilder queryComment = + QueryBuilder(ParseObject('Comment')) + ..whereMatchesQuery('post', queryPost); + + var apiResponse = await queryComment.query(); +``` + +If you want to retrieve objects where a field contains an object that does not match another query, you can use the +__whereDoesNotMatchQuery__ condition. +Imagine you have Post class and a Comment class, where each Comment has a pointer to its parent Post. +You can find comments on posts without images by doing: + +```dart + QueryBuilder queryPost = + QueryBuilder(ParseObject('Post')) + ..whereValueExists('image', true); + + QueryBuilder queryComment = + QueryBuilder(ParseObject('Comment')) + ..whereDoesNotMatchQuery('post', queryPost); + + var apiResponse = await queryComment.query(); +``` + +## Counting Objects +If you only care about the number of games played by a particular player: + +```dart + QueryBuilder queryPlayers = + QueryBuilder(ParseObject('GameScore')) + ..whereEqualTo('playerName', 'Jonathan Walsh'); + var apiResponse = await queryPlayers.count(); + if (apiResponse.success && apiResponse.result != null) { + int countPlayers = apiResponse.count; + } +``` ## Objects From 319030a69b4261759f8cbb705c372f52180986f9 Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Sat, 6 Apr 2019 09:03:14 -0300 Subject: [PATCH 04/12] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 37c1cc601..157a13d12 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ If you only care about the number of games played by a particular player: ..whereEqualTo('playerName', 'Jonathan Walsh'); var apiResponse = await queryPlayers.count(); if (apiResponse.success && apiResponse.result != null) { - int countPlayers = apiResponse.count; + int countGames = apiResponse.count; } ``` From 8a8234bc2d0d3f2f6d7a918fcc2e916a31f7b04d Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Sat, 6 Apr 2019 11:15:41 -0300 Subject: [PATCH 05/12] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 157a13d12..aeaefdc27 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ The features available are:- ## Relational queries If you want to retrieve objects where a field contains an object that matches another query, you can use the __whereMatchesQuery__ condition. -For example, imagine you vave Post class and a Comment class, where each Comment has a pointer to its parent Post. +For example, imagine you have Post class and a Comment class, where each Comment has a pointer to its parent Post. You can find comments on posts with images by doing: ```dart From 435941303699a16ab6abb897899e8e4c5cf6c425 Mon Sep 17 00:00:00 2001 From: rodrigosmarques Date: Thu, 11 Apr 2019 02:31:33 -0300 Subject: [PATCH 06/12] BugFix LiveQuery BugFix LiveQuery --- lib/src/enums/parse_enum_api_rq.dart | 3 +- lib/src/network/parse_live_query.dart | 180 ++++++++++++++++++++------ 2 files changed, 146 insertions(+), 37 deletions(-) diff --git a/lib/src/enums/parse_enum_api_rq.dart b/lib/src/enums/parse_enum_api_rq.dart index ea0b9b15d..73eafd895 100644 --- a/lib/src/enums/parse_enum_api_rq.dart +++ b/lib/src/enums/parse_enum_api_rq.dart @@ -30,5 +30,6 @@ enum ParseApiRQ { increment, decrement, getConfigs, - addConfig + addConfig, + liveQuery } diff --git a/lib/src/network/parse_live_query.dart b/lib/src/network/parse_live_query.dart index d04988682..a6213c459 100644 --- a/lib/src/network/parse_live_query.dart +++ b/lib/src/network/parse_live_query.dart @@ -1,52 +1,160 @@ part of flutter_parse_sdk; -/// Still under development +enum LiveQueryEvent { create, enter, update, leave, delete, error } + class LiveQuery { + LiveQuery({bool debug, ParseHTTPClient client, bool autoSendSessionId}) { + _client = client ?? + ParseHTTPClient( + sendSessionId: + autoSendSessionId ?? ParseCoreData().autoSendSessionId, + securityContext: ParseCoreData().securityContext); + + _debug = isDebugEnabled(objectLevelDebug: debug); + _sendSessionId = autoSendSessionId ?? ParseCoreData().autoSendSessionId; - LiveQuery(ParseHTTPClient client) : client = client { - connectMessage = { + _connectMessage = { 'op': 'connect', - 'applicationId': client.data.applicationId, + 'applicationId': _client.data.applicationId, + 'clientKey': _client.data.clientKey }; + if (_sendSessionId) { + _connectMessage['sessionToken'] = _client.data.sessionId; + } + } - final Map whereMap = Map(); + WebSocket _webSocket; + ParseHTTPClient _client; + bool _debug; + bool _sendSessionId; + IOWebSocketChannel _channel; + Map _connectMessage; + Map _disconnectMessage; + Map _subscribeMessage; + Map eventCallbacks = {}; + Map _whereMap = Map(); + int _requestIdCount = 1; + List _liveQueryEvent = [ + 'create', + 'enter', + 'update', + 'leave', + 'delete', + 'error' + ]; - subscribeMessage = { - 'op': 'subscribe', - 'requestId': 1, - 'query': { - 'className': null, - 'where': whereMap, - } - }; + int _requestIdGenerator() { + return _requestIdCount++; } - final ParseHTTPClient client; - IOWebSocketChannel channel; - Map connectMessage; - Map subscribeMessage; - Map eventCallbacks = {}; + Future subscribe(QueryBuilder query) async { + String _liveQueryURL = _client.data.liveQueryURL; + if (_liveQueryURL.contains('https')) { + _liveQueryURL = _liveQueryURL.replaceAll('https', 'wws'); + } else if (_liveQueryURL.contains('http')) { + _liveQueryURL = _liveQueryURL.replaceAll('http', 'ww'); + } + + final String _className = query.object.className; + //Remove limites in LiveQuery + query.limiters.clear(); + final String _where = query._buildQuery().replaceAll('where=', ''); + if (_where != '') { + _whereMap = json.decode(_where); + } + + final int requestId = _requestIdGenerator(); + + try { + _webSocket = await WebSocket.connect( + _liveQueryURL, + ); - Future subscribe(String className) async { - final WebSocket webSocket = await WebSocket.connect(client.data.liveQueryURL); - channel = IOWebSocketChannel(webSocket); - channel.sink.add(jsonEncode(connectMessage)); - final Map classNameMap = subscribeMessage['query']; - classNameMap['className'] = className; - channel.sink.add(jsonEncode(subscribeMessage)); - - channel.stream.listen((dynamic message) { - final Map actionData = jsonDecode(message); - if (eventCallbacks.containsKey(actionData['op'])) - eventCallbacks[actionData['op']](actionData); - }); + if (_webSocket != null && _webSocket.readyState == WebSocket.OPEN) { + if (_debug) { + print('Livequery: Socket opened'); + } + } else { + if (_debug) { + print('Livequery: Error when connection client'); + return; + } + } + _channel = IOWebSocketChannel(_webSocket); + + _channel.stream.listen((dynamic message) { + if (_debug) { + print('Livequery: Listen: ${message}'); + } + + final Map actionData = jsonDecode(message); + + if (eventCallbacks.containsKey(actionData['op'])) { + if (actionData.containsKey('object')) { + final Map map = actionData['object']; + final String className = map['className']; + if (className == '_User') { + eventCallbacks[actionData['op']]( + ParseUser._getEmptyUser().fromJson(map)); + } else { + eventCallbacks[actionData['op']]( + ParseObject(className).fromJson(map)); + } + } else { + eventCallbacks[actionData['op']](actionData); + } + } + }, onDone: () { + if (_debug) { + print("Livequery: Task Done"); + } + }, onError: (error) { + return handleException(error, ParseApiRQ.liveQuery, _debug, _className); + }); + + //The connect message is sent from a client to the LiveQuery server. + //It should be the first message sent from a client after the WebSocket connection is established. + _channel.sink.add(jsonEncode(_connectMessage)); + + _subscribeMessage = { + 'op': 'subscribe', + 'requestId': requestId, + 'query': { + 'className': _className, + 'where': _whereMap, + } + }; + if (_sendSessionId) { + _subscribeMessage['sessionToken'] = _client.data.sessionId; + } + + //After a client connects to the LiveQuery server, + //it can send a subscribe message to subscribe a ParseQuery. + _channel.sink.add(jsonEncode(_subscribeMessage)); + + _disconnectMessage = { + 'op': 'unsubscribe', + 'requestId': requestId, + }; + } on Exception catch (e) { + print('Error: ${e.toString()}'); + return handleException(e, ParseApiRQ.liveQuery, _debug, _className); + } } - void on(String op, Function callback) { - eventCallbacks[op] = callback; + void on(LiveQueryEvent op, Function callback) { + eventCallbacks[_liveQueryEvent[op.index]] = callback; } - Future close() async { - await channel.sink.close(); + Future unSubscribe() async { + if (_channel != null) { + if (_channel.sink != null) { + await _channel.sink.add(jsonEncode(_disconnectMessage)); + await _channel.sink.close(); + } + } + if (_webSocket != null && _webSocket.readyState == WebSocket.OPEN) { + await _webSocket.close(); + } } -} +} \ No newline at end of file From 30bd9d60709281af919d8db327ca19018cdc92c0 Mon Sep 17 00:00:00 2001 From: rodrigosmarques Date: Fri, 12 Apr 2019 21:29:39 -0300 Subject: [PATCH 07/12] Bugfix LiveQuery Bugfix LiveQuery --- lib/src/network/parse_live_query.dart | 89 +++++++++++++++++---------- 1 file changed, 55 insertions(+), 34 deletions(-) diff --git a/lib/src/network/parse_live_query.dart b/lib/src/network/parse_live_query.dart index a6213c459..2068ea5fd 100644 --- a/lib/src/network/parse_live_query.dart +++ b/lib/src/network/parse_live_query.dart @@ -12,15 +12,6 @@ class LiveQuery { _debug = isDebugEnabled(objectLevelDebug: debug); _sendSessionId = autoSendSessionId ?? ParseCoreData().autoSendSessionId; - - _connectMessage = { - 'op': 'connect', - 'applicationId': _client.data.applicationId, - 'clientKey': _client.data.clientKey - }; - if (_sendSessionId) { - _connectMessage['sessionToken'] = _client.data.sessionId; - } } WebSocket _webSocket; @@ -29,12 +20,11 @@ class LiveQuery { bool _sendSessionId; IOWebSocketChannel _channel; Map _connectMessage; - Map _disconnectMessage; Map _subscribeMessage; + Map _unsubscribeMessage; Map eventCallbacks = {}; - Map _whereMap = Map(); int _requestIdCount = 1; - List _liveQueryEvent = [ + final List _liveQueryEvent = [ 'create', 'enter', 'update', @@ -42,6 +32,7 @@ class LiveQuery { 'delete', 'error' ]; + final String _printConstLiveQuery = 'LiveQuery: '; int _requestIdGenerator() { return _requestIdCount++; @@ -50,15 +41,16 @@ class LiveQuery { Future subscribe(QueryBuilder query) async { String _liveQueryURL = _client.data.liveQueryURL; if (_liveQueryURL.contains('https')) { - _liveQueryURL = _liveQueryURL.replaceAll('https', 'wws'); + _liveQueryURL = _liveQueryURL.replaceAll('https', 'wss'); } else if (_liveQueryURL.contains('http')) { - _liveQueryURL = _liveQueryURL.replaceAll('http', 'ww'); + _liveQueryURL = _liveQueryURL.replaceAll('http', 'ws'); } final String _className = query.object.className; - //Remove limites in LiveQuery - query.limiters.clear(); + query.limiters.clear(); //Remove limites in LiveQuery final String _where = query._buildQuery().replaceAll('where=', ''); + //Convert where condition to Map + Map _whereMap = Map(); if (_where != '') { _whereMap = json.decode(_where); } @@ -66,25 +58,23 @@ class LiveQuery { final int requestId = _requestIdGenerator(); try { - _webSocket = await WebSocket.connect( - _liveQueryURL, - ); + _webSocket = await WebSocket.connect(_liveQueryURL); - if (_webSocket != null && _webSocket.readyState == WebSocket.OPEN) { + if (_webSocket != null && _webSocket.readyState == WebSocket.open) { if (_debug) { - print('Livequery: Socket opened'); + print('$_printConstLiveQuery: Socket opened'); } } else { if (_debug) { - print('Livequery: Error when connection client'); - return; + print('$_printConstLiveQuery: Error when connection client'); + return Future.value(null); } } - _channel = IOWebSocketChannel(_webSocket); + _channel = IOWebSocketChannel(_webSocket); _channel.stream.listen((dynamic message) { if (_debug) { - print('Livequery: Listen: ${message}'); + print('$_printConstLiveQuery: Listen: ${message}'); } final Map actionData = jsonDecode(message); @@ -106,16 +96,35 @@ class LiveQuery { } }, onDone: () { if (_debug) { - print("Livequery: Task Done"); + print('$_printConstLiveQuery: Done'); } }, onError: (error) { - return handleException(error, ParseApiRQ.liveQuery, _debug, _className); + if (_debug) { + print( + '$_printConstLiveQuery: Error: ${error.runtimeType.toString()}'); + } + return Future.value( + handleException(error, ParseApiRQ.liveQuery, _debug, _className)); }); //The connect message is sent from a client to the LiveQuery server. //It should be the first message sent from a client after the WebSocket connection is established. + _connectMessage = { + 'op': 'connect', + 'applicationId': _client.data.applicationId, + 'clientKey': _client.data.clientKey + }; + if (_sendSessionId) { + _connectMessage['sessionToken'] = _client.data.sessionId; + } + + if (_debug) { + print('$_printConstLiveQuery: ConnectMessage: $_connectMessage'); + } _channel.sink.add(jsonEncode(_connectMessage)); + //After a client connects to the LiveQuery server, + //it can send a subscribe message to subscribe a ParseQuery. _subscribeMessage = { 'op': 'subscribe', 'requestId': requestId, @@ -128,16 +137,21 @@ class LiveQuery { _subscribeMessage['sessionToken'] = _client.data.sessionId; } - //After a client connects to the LiveQuery server, - //it can send a subscribe message to subscribe a ParseQuery. + if (_debug) { + print('$_printConstLiveQuery: SubscribeMessage: $_subscribeMessage'); + } + _channel.sink.add(jsonEncode(_subscribeMessage)); - _disconnectMessage = { + //Mount message for Unsubscribe + _unsubscribeMessage = { 'op': 'unsubscribe', 'requestId': requestId, }; } on Exception catch (e) { - print('Error: ${e.toString()}'); + if (_debug) { + print('$_printConstLiveQuery: Error: ${e.toString()}'); + } return handleException(e, ParseApiRQ.liveQuery, _debug, _className); } } @@ -149,12 +163,19 @@ class LiveQuery { Future unSubscribe() async { if (_channel != null) { if (_channel.sink != null) { - await _channel.sink.add(jsonEncode(_disconnectMessage)); + if (_debug) { + print( + '$_printConstLiveQuery: UnsubscribeMessage: $_unsubscribeMessage'); + } + await _channel.sink.add(jsonEncode(_unsubscribeMessage)); await _channel.sink.close(); } } - if (_webSocket != null && _webSocket.readyState == WebSocket.OPEN) { + if (_webSocket != null && _webSocket.readyState == WebSocket.open) { + if (_debug) { + print('$_printConstLiveQuery: Socket closed'); + } await _webSocket.close(); } } -} \ No newline at end of file +} From 600bd6caaf4f856006dcb7717861a17378c42a8d Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Fri, 12 Apr 2019 22:16:53 -0300 Subject: [PATCH 08/12] Update README.md Reorganization of sessions and inclusion of documentation on LiveQuery --- README.md | 260 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 187 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index aeaefdc27..4b110fb5a 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,95 @@ Parse().initialize( masterKey: ApplicationConstants.keyParseMasterKey, clientKey: ApplicationConstants.keyParseClientKey, debug: true, - liveQuery: true, + liveQueryUrl: ApplicationConstants.keyLiveQueryUrl, autoSendSessionId: true, securityContext: securityContext); ``` +## Objects +You can create custom objects by calling: +```dart +var dietPlan = ParseObject('DietPlan') + ..set('Name', 'Ketogenic') + ..set('Fat', 65); +``` +You then have the ability to do the following with that object: +The features available are:- + * Get + * GetAll + * Create + * Save + * Query - By object Id + * Delete + * Complex queries as shown above + * Pin + * Plenty more + * Counters + * Array Operators + +## Custom Objects +You can create your own ParseObjects or convert your existing objects into Parse Objects by doing the following: + +```dart +class DietPlan extends ParseObject implements ParseCloneable { + + DietPlan() : super(_keyTableName); + DietPlan.clone(): this(); + + /// Looks strangely hacky but due to Flutter not using reflection, we have to + /// mimic a clone + @override clone(Map map) => DietPlan.clone()..fromJson(map); + + static const String _keyTableName = 'Diet_Plans'; + static const String keyName = 'Name'; + + String get name => get(keyName); + set name(String name) => set(keyName, name); +} + +``` + +## Add new values to objects +To add a variable to an object call and retrieve it, call + +```dart +dietPlan.set('RandomInt', 8); +var randomInt = dietPlan.get('RandomInt'); +``` + +## Save objects using pins +You can now save an object by calling .pin() on an instance of an object + +```dart +dietPlan.pin(); +``` + +and to retrieve it + +```dart +var dietPlan = DietPlan().fromPin('OBJECT ID OF OBJECT'); +``` + +## Increment Counter values in objects +Retrieve it, call + +```dart +var response = await dietPlan.increment("count", 1); + +``` + +## Array Operator in objects +Retrieve it, call + +```dart +var response = await dietPlan.add("listKeywords", ["a", "a","d"]); + +var response = await dietPlan.addUnique("listKeywords", ["a", "a","d"]); + +var response = await dietPlan.remove("listKeywords", ["a"]); + +``` + ## Queries Once you have setup the project and initialised the instance, you can then retreive data from your server by calling: ```dart @@ -63,7 +147,6 @@ var dietPlan = await DietPlan().getObject('R5EonpUDWy'); } ``` - ## Complex queries You can create complex queries to really put your database to the test: @@ -154,99 +237,134 @@ If you only care about the number of games played by a particular player: int countGames = apiResponse.count; } ``` +## Live Queries +This tool allows you to subscribe to a Parse.Query you are interested in. Once subscribed, the server will notify clients whenever a Parse.Object that matches the Parse.Query is created or updated, in real-time. -## Objects +Parse LiveQuery contains two parts, the LiveQuery server and the LiveQuery clients. In order to use live queries, you need to set up both of them. -You can create custom objects by calling: +The Parse Server configuration guide on the server is found here https://docs.parseplatform.org/parse-server/guide/#live-queries and is not part of this documentation. + +Initialize the Parse Query server by entering the parameter liveQueryUrl in Parse().initialize: ```dart -var dietPlan = ParseObject('DietPlan') - ..set('Name', 'Ketogenic') - ..set('Fat', 65); + Parse().initialize( + ApplicationConstants.keyApplicationId, + ApplicationConstants.keyParseServerUrl, + clientKey: ApplicationConstants.keyParseClientKey, + debug: true, + liveQueryUrl: ApplicationConstants.keyLiveQueryUrl, + autoSendSessionId: true); ``` -You then have the ability to do the following with that object: -The features available are:- - * Get - * GetAll - * Create - * Save - * Query - By object Id - * Delete - * Complex queries as shown above - * Pin - * Plenty more - * Counters - * Array Operators - -## Custom Objects -You can create your own ParseObjects or convert your existing objects into Parse Objects by doing the following: +Declare LiveQuery: ```dart -class DietPlan extends ParseObject implements ParseCloneable { - - DietPlan() : super(_keyTableName); - DietPlan.clone(): this(); - - /// Looks strangely hacky but due to Flutter not using reflection, we have to - /// mimic a clone - @override clone(Map map) => DietPlan.clone()..fromJson(map); - - static const String _keyTableName = 'Diet_Plans'; - static const String keyName = 'Name'; - - String get name => get(keyName); - set name(String name) => set(keyName, name); -} - + final LiveQuery liveQuery = LiveQuery(); ``` -## Add new values to objects - -To add a variable to an object call and retrieve it, call - +Set the query that will be monitored by LiveQuery: ```dart -dietPlan.set('RandomInt', 8); -var randomInt = dietPlan.get('RandomInt'); + QueryBuilder query = + QueryBuilder(ParseObject('TestAPI')) + ..whereEqualTo('intNumber', 1); ``` - -## Save objects using pins - -You can now save an object by calling .pin() on an instance of an object +__Create a subscription__ +You’ll get the LiveQuery events through this subscription. +The first time you call subscribe, we’ll try to open the WebSocket connection to the LiveQuery server for you. ```dart -dietPlan.pin(); + await liveQuery.subscribe(query); ``` -and to retrieve it +__Event Handling__ +We define several types of events you’ll get through a subscription object: +__Create event__ +When a new ParseObject is created and it fulfills the ParseQuery you subscribe, you’ll get this event. +The object is the ParseObject which was created. ```dart -var dietPlan = DietPlan().fromPin('OBJECT ID OF OBJECT'); + liveQuery.on(LiveQueryEvent.create, (value) { + print('*** CREATE ***: ${DateTime.now().toString()}\n $value '); + print((value as ParseObject).objectId); + print((value as ParseObject).updatedAt); + print((value as ParseObject).createdAt); + print((value as ParseObject).get('objectId')); + print((value as ParseObject).get('updatedAt')); + print((value as ParseObject).get('createdAt')); + }); ``` -## Increment Counter values in objects - -Retrieve it, call - +__Update event__ +When an existing ParseObject which fulfills the ParseQuery you subscribe is updated (The ParseObject fulfills the +ParseQuery before and after changes), you’ll get this event. +The object is the ParseObject which was updated. Its content is the latest value of the ParseObject. ```dart -var response = await dietPlan.increment("count", 1); - + liveQuery.on(LiveQueryEvent.update, (value) { + print('*** UPDATE ***: ${DateTime.now().toString()}\n $value '); + print((value as ParseObject).objectId); + print((value as ParseObject).updatedAt); + print((value as ParseObject).createdAt); + print((value as ParseObject).get('objectId')); + print((value as ParseObject).get('updatedAt')); + print((value as ParseObject).get('createdAt')); + }); ``` -## Array Operator in objects - -Retrieve it, call +__Enter event__ +When an existing ParseObject’s old value does not fulfill the ParseQuery but its new value fulfills the ParseQuery, +you’ll get this event. The object is the ParseObject which enters the ParseQuery. +Its content is the latest value of the ParseObject. +```dart + liveQuery.on(LiveQueryEvent.enter, (value) { + print('*** ENTER ***: ${DateTime.now().toString()}\n $value '); + print((value as ParseObject).objectId); + print((value as ParseObject).updatedAt); + print((value as ParseObject).createdAt); + print((value as ParseObject).get('objectId')); + print((value as ParseObject).get('updatedAt')); + print((value as ParseObject).get('createdAt')); + }); +``` +__Leave event__ +When an existing ParseObject’s old value fulfills the ParseQuery but its new value doesn’t fulfill the ParseQuery, +you’ll get this event. The object is the ParseObject which leaves the ParseQuery. +Its content is the latest value of the ParseObject. ```dart -var response = await dietPlan.add("listKeywords", ["a", "a","d"]); + liveQuery.on(LiveQueryEvent.leave, (value) { + print('*** LEAVE ***: ${DateTime.now().toString()}\n $value '); + print((value as ParseObject).objectId); + print((value as ParseObject).updatedAt); + print((value as ParseObject).createdAt); + print((value as ParseObject).get('objectId')); + print((value as ParseObject).get('updatedAt')); + print((value as ParseObject).get('createdAt')); + }); +``` -var response = await dietPlan.addUnique("listKeywords", ["a", "a","d"]); +__Delete event__ +When an existing ParseObject which fulfills the ParseQuery is deleted, you’ll get this event. +The object is the ParseObject which is deleted +```dart + liveQuery.on(LiveQueryEvent.delete, (value) { + print('*** DELETE ***: ${DateTime.now().toString()}\n $value '); + print((value as ParseObject).objectId); + print((value as ParseObject).updatedAt); + print((value as ParseObject).createdAt); + print((value as ParseObject).get('objectId')); + print((value as ParseObject).get('updatedAt')); + print((value as ParseObject).get('createdAt')); + }); +``` -var response = await dietPlan.remove("listKeywords", ["a"]); +__Unsubscribe__ +If you would like to stop receiving events from a ParseQuery, you can just unsubscribe the subscription. +After that, you won’t get any events from the subscription object and will close the WebSocket connection to the +LiveQuery server. +```dart + await liveQuery.unSubscribe(); ``` - ## Users - You can create and control users just as normal using this SDK. To register a user, first create one : @@ -277,7 +395,6 @@ Other user features are:- * Queries ## Config - The SDK now supports Parse Config. A map of all configs can be grabbed from the server by calling : ```dart var response = await ParseConfig().getConfigs(); @@ -289,13 +406,8 @@ ParseConfig().addConfig('TestConfig', 'testing'); ``` ## Other Features of this library - Main: -* Users * Installation -* Objects -* Queries -* LiveQueries * GeoPoints * Files * Persistent storage @@ -313,11 +425,13 @@ User: * Save * Destroy * Queries +* Anonymous +* 3rd Party Authentication Objects: * Create new object * Extend Parse Object and create local objects that can be saved and retreived -* Queries: +* Queries ## Author:- This project was authored by Phill Wiggins. You can contact me at phill.wiggins@gmail.com From 88df5bd225dc9a1ace82f30cd6c132b0f85c1207 Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Fri, 12 Apr 2019 22:20:21 -0300 Subject: [PATCH 09/12] Update README.md --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4b110fb5a..d219c9800 100644 --- a/README.md +++ b/README.md @@ -237,14 +237,17 @@ If you only care about the number of games played by a particular player: int countGames = apiResponse.count; } ``` + ## Live Queries -This tool allows you to subscribe to a Parse.Query you are interested in. Once subscribed, the server will notify clients whenever a Parse.Object that matches the Parse.Query is created or updated, in real-time. +This tool allows you to subscribe to a QueryBuilder you are interested in. Once subscribed, the server will notify clients +whenever a ParseObject that matches the QueryBuilder is created or updated, in real-time. -Parse LiveQuery contains two parts, the LiveQuery server and the LiveQuery clients. In order to use live queries, you need to set up both of them. +Parse LiveQuery contains two parts, the LiveQuery server and the LiveQuery clients. In order to use live queries, you need +to set up both of them. The Parse Server configuration guide on the server is found here https://docs.parseplatform.org/parse-server/guide/#live-queries and is not part of this documentation. -Initialize the Parse Query server by entering the parameter liveQueryUrl in Parse().initialize: +Initialize the Parse Live Query by entering the parameter liveQueryUrl in Parse().initialize: ```dart Parse().initialize( ApplicationConstants.keyApplicationId, @@ -260,7 +263,7 @@ Declare LiveQuery: final LiveQuery liveQuery = LiveQuery(); ``` -Set the query that will be monitored by LiveQuery: +Set the QueryBuilder that will be monitored by LiveQuery: ```dart QueryBuilder query = QueryBuilder(ParseObject('TestAPI')) From ae0c08f8efa17f672825d3c19561ca8f8d4f7395 Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Fri, 12 Apr 2019 22:21:56 -0300 Subject: [PATCH 10/12] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d219c9800..2320c7f04 100644 --- a/README.md +++ b/README.md @@ -281,7 +281,7 @@ __Event Handling__ We define several types of events you’ll get through a subscription object: __Create event__ -When a new ParseObject is created and it fulfills the ParseQuery you subscribe, you’ll get this event. +When a new ParseObject is created and it fulfills the QueryBuilder you subscribe, you’ll get this event. The object is the ParseObject which was created. ```dart liveQuery.on(LiveQueryEvent.create, (value) { @@ -296,8 +296,8 @@ The object is the ParseObject which was created. ``` __Update event__ -When an existing ParseObject which fulfills the ParseQuery you subscribe is updated (The ParseObject fulfills the -ParseQuery before and after changes), you’ll get this event. +When an existing ParseObject which fulfills the QueryBuilder you subscribe is updated (The ParseObject fulfills the +QueryBuilder before and after changes), you’ll get this event. The object is the ParseObject which was updated. Its content is the latest value of the ParseObject. ```dart liveQuery.on(LiveQueryEvent.update, (value) { @@ -312,8 +312,8 @@ The object is the ParseObject which was updated. Its content is the latest value ``` __Enter event__ -When an existing ParseObject’s old value does not fulfill the ParseQuery but its new value fulfills the ParseQuery, -you’ll get this event. The object is the ParseObject which enters the ParseQuery. +When an existing ParseObject’s old value does not fulfill the QueryBuilder but its new value fulfills the QueryBuilder, +you’ll get this event. The object is the ParseObject which enters the QueryBuilder. Its content is the latest value of the ParseObject. ```dart liveQuery.on(LiveQueryEvent.enter, (value) { @@ -328,8 +328,8 @@ Its content is the latest value of the ParseObject. ``` __Leave event__ -When an existing ParseObject’s old value fulfills the ParseQuery but its new value doesn’t fulfill the ParseQuery, -you’ll get this event. The object is the ParseObject which leaves the ParseQuery. +When an existing ParseObject’s old value fulfills the QueryBuilder but its new value doesn’t fulfill the QueryBuilder, +you’ll get this event. The object is the ParseObject which leaves the QueryBuilder. Its content is the latest value of the ParseObject. ```dart liveQuery.on(LiveQueryEvent.leave, (value) { @@ -344,7 +344,7 @@ Its content is the latest value of the ParseObject. ``` __Delete event__ -When an existing ParseObject which fulfills the ParseQuery is deleted, you’ll get this event. +When an existing ParseObject which fulfills the QueryBuilder is deleted, you’ll get this event. The object is the ParseObject which is deleted ```dart liveQuery.on(LiveQueryEvent.delete, (value) { @@ -359,7 +359,7 @@ The object is the ParseObject which is deleted ``` __Unsubscribe__ -If you would like to stop receiving events from a ParseQuery, you can just unsubscribe the subscription. +If you would like to stop receiving events from a QueryBuilder, you can just unsubscribe the subscription. After that, you won’t get any events from the subscription object and will close the WebSocket connection to the LiveQuery server. From fed8276ef71ee51c55bbd18cf94f6c7f6f985fda Mon Sep 17 00:00:00 2001 From: rodrigosmarques Date: Fri, 12 Apr 2019 23:14:09 -0300 Subject: [PATCH 11/12] Bugfix LiveQuery Bugfix LiveQuery --- lib/src/network/parse_live_query.dart | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/src/network/parse_live_query.dart b/lib/src/network/parse_live_query.dart index 2068ea5fd..1fb749973 100644 --- a/lib/src/network/parse_live_query.dart +++ b/lib/src/network/parse_live_query.dart @@ -98,13 +98,11 @@ class LiveQuery { if (_debug) { print('$_printConstLiveQuery: Done'); } - }, onError: (error) { + }, onError: (error, StackTrace stackTrace) { if (_debug) { - print( - '$_printConstLiveQuery: Error: ${error.runtimeType.toString()}'); + print('$_printConstLiveQuery: Error: ${error.runtimeType.toString()}'); } - return Future.value( - handleException(error, ParseApiRQ.liveQuery, _debug, _className)); + return Future.value(handleException(error, ParseApiRQ.liveQuery, _debug, _className)); }); //The connect message is sent from a client to the LiveQuery server. @@ -172,9 +170,10 @@ class LiveQuery { } } if (_webSocket != null && _webSocket.readyState == WebSocket.open) { - if (_debug) { - print('$_printConstLiveQuery: Socket closed'); - } + if (_debug) { + print( + '$_printConstLiveQuery: Socket closed'); + } await _webSocket.close(); } } From e1437f932822765fc15ffe51a01bdab371a90314 Mon Sep 17 00:00:00 2001 From: rodrigosmarques Date: Sat, 13 Apr 2019 13:12:37 -0300 Subject: [PATCH 12/12] Changed to make optional clientKey Changed to make optional clientKey --- lib/src/network/parse_live_query.dart | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/src/network/parse_live_query.dart b/lib/src/network/parse_live_query.dart index 1fb749973..fde65d862 100644 --- a/lib/src/network/parse_live_query.dart +++ b/lib/src/network/parse_live_query.dart @@ -100,9 +100,11 @@ class LiveQuery { } }, onError: (error, StackTrace stackTrace) { if (_debug) { - print('$_printConstLiveQuery: Error: ${error.runtimeType.toString()}'); + print( + '$_printConstLiveQuery: Error: ${error.runtimeType.toString()}'); } - return Future.value(handleException(error, ParseApiRQ.liveQuery, _debug, _className)); + return Future.value( + handleException(error, ParseApiRQ.liveQuery, _debug, _className)); }); //The connect message is sent from a client to the LiveQuery server. @@ -110,7 +112,7 @@ class LiveQuery { _connectMessage = { 'op': 'connect', 'applicationId': _client.data.applicationId, - 'clientKey': _client.data.clientKey + 'clientKey': _client.data.clientKey ?? '' }; if (_sendSessionId) { _connectMessage['sessionToken'] = _client.data.sessionId; @@ -170,10 +172,9 @@ class LiveQuery { } } if (_webSocket != null && _webSocket.readyState == WebSocket.open) { - if (_debug) { - print( - '$_printConstLiveQuery: Socket closed'); - } + if (_debug) { + print('$_printConstLiveQuery: Socket closed'); + } await _webSocket.close(); } }