diff --git a/.idea/flutter_parse_sdk.iml b/.idea/flutter_parse_sdk.iml new file mode 100644 index 000000000..c9d208a4e --- /dev/null +++ b/.idea/flutter_parse_sdk.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index 66de4becd..fdd01077d 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -1,162 +1,25 @@ - - - - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml index 128e0bcc2..b257e38dc 100644 --- a/.idea/libraries/Dart_SDK.xml +++ b/.idea/libraries/Dart_SDK.xml @@ -1,25 +1,25 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index b0f697111..b7751cdc9 100644 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -1,6 +1,8 @@ - - + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml index e5ed7a4dd..c749759a5 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,8 +2,7 @@ - - + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index ec2cb194f..ec30ad3fd 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -10,39 +10,154 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + + + + + + + + + + + + + @@ -53,6 +168,10 @@ User() import + BaseClient + bad + client + send part @@ -62,7 +181,7 @@ @@ -84,39 +203,43 @@ - @@ -138,228 +261,83 @@ - + + - + - + - - + + - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - @@ -489,83 +458,116 @@ + + + + + + + + + - + - - - - - - - - + - - - - - - - - - - - - - - - - - - - + + + + + + - + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + - + + + + - - - - - - - - + - - - - - + + + + + + + + + + + + + + + + @@ -574,128 +576,41 @@ - - - - - - file://$PROJECT_DIR$/lib/objects/parse_object.dart - 76 - - - + + + + _getUserFromLocalStore() + Dart + EXPRESSION + + + await _getUserFromLocalStore() + Dart + EXPRESSION + + + super.toJson(); + Dart + EXPRESSION + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - @@ -717,229 +632,301 @@ - + + + - - + + - - - + - + - + - - + + - + + + + + + + + + + - - + + - + - - + + - + - - + + - + - - + + - + + + + - - + + - + - - + + - + - - + + + + + + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - - - - + + - + - - + + - + - - - - - + + - + - - - - - + + - + - - + + - + - - + + + + + + + + + - - + + - + - - - - - + + - + - - + + + + + + + + + + + + + No facets are configured + + + + + + + + + + + + + + + 1.8 + + + + + + + + flutter_parse_sdk + + + + + 1.8 @@ -952,6 +939,18 @@ + + + Dart Packages + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f8a7f12ce..cabacd842 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.0.3 + +Added persistent storage. When a logged in user closes the app, then reopens, the data +will now be persistent. Best practice would be to Parse.init, then Parse.currentUser. This +will return the current user session and allow auto login. Can also pin data in storage. + ## 1.0.2 Fixed login diff --git a/README.md b/README.md index 207619a64..ad4b89437 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Want to get involved? Join our Slack channel and help out! (http://flutter-parse To install, either add to your pubspec.yaml ``` dependencies: - parse_server_sdk: ^1.0.2 + parse_server_sdk: ^1.0.3 ``` or clone this repository and add to your project. As this is an early development with multiple contributors, it is probably best to download/clone and keep updating as an when a new feature is added. @@ -115,6 +115,7 @@ The features available are:- * Query - By object Id * Delete * Complex queries as shown above + * Pin * Plenty more ## Custom Objects @@ -146,21 +147,21 @@ var user = ParseUser().create("TestFlutter", "TestPassword123", "TestFlutterSDK Then have the user sign up: ``` -user = await ParseUser().signUp(); +user = await user.signUp(); ``` You can also logout and login with the user: ``` -user = await ParseUser().login(); +user = await user.login(); ``` -Also, once logged in you can manage sessions tokens: +Also, once logged in you can manage sessions tokens. This feature can be called after Parse().init() on startup to check for a logged in user. ``` -user = await ParseUser().currentUser(fromServer: true); +user = ParseUser.currentUser(); ``` Other user features are:- * Request Password Reset -* Verification Email Request + * Verification Email Request * Get all users -* Save + * Save * Destroy user ## Other Features of this library @@ -171,6 +172,7 @@ Main: * Queries * LiveQueries * GeoPoints +* Persistent storage * Debug Mode - Logging API calls * Manage Session ID's tokens diff --git a/example/android/gradlew b/example/android/gradlew old mode 100644 new mode 100755 diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index 592ceee85..e8efba114 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index 592ceee85..399e9340e 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/lib/application_constants.dart b/example/lib/application_constants.dart index 7ddb84685..e2b7f38b0 100644 --- a/example/lib/application_constants.dart +++ b/example/lib/application_constants.dart @@ -1,6 +1,6 @@ -abstract class ApplicationConstants { +abstract class ApplicationConstants { // Start static const String APP_NAME = ""; static const String PARSE_APPLICATION_ID = ""; static const String PARSE_MASTER_KEY = ""; static const String PARSE_SERVER_URL = ""; -} \ No newline at end of file +} // End \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index a104d292d..492f7aa08 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -70,7 +70,7 @@ class _MyAppState extends State { print(ApplicationConstants.APP_NAME + ": " + (plan as DietPlan).name); } } else { - print(ApplicationConstants.APP_NAME + ": " + response.exception.message); + print(ApplicationConstants.APP_NAME + ": " + response.error.message); } } @@ -80,7 +80,7 @@ class _MyAppState extends State { if (response.success) { print(ApplicationConstants.APP_NAME + ": " + (response.result as DietPlan).toString()); } else { - print(ApplicationConstants.APP_NAME + ": " + response.exception.message); + print(ApplicationConstants.APP_NAME + ": " + response.error.message); } } @@ -94,20 +94,29 @@ class _MyAppState extends State { if (response.success) { print("Result: ${((response.result as List).first as DietPlan).toString()}"); } else { - print("Result: ${response.exception.message}"); + print("Result: ${response.error.message}"); } } initUser() async { - var user = ParseUser().create("TestFlutter", "TestPassword123", "TestFlutterSDK@gmail.com"); - user = await ParseUser().signUp(); - user = await ParseUser().login(); - user = await ParseUser().currentUser(fromServer: true); - user = await ParseUser().requestPasswordReset(); - user = await ParseUser().verificationEmailRequest(); - user = await ParseUser().all(); - user = await ParseUser().save(); - user = await ParseUser().destroy(); + + // All return type ParseUser except all + var user = ParseUser("TestFlutter", "TestPassword123", "TestFlutterSDK@gmail.com"); + user = await user.signUp(); + user = await user.login(); + user = null; + + // Best practice for starting the app. This will check for a + user = ParseUser.currentUser(); + user = await user.getCurrentUserFromServer(); + user = await user.requestPasswordReset(); + user = await user.verificationEmailRequest(); + + user = await user.save(); + await user.destroy(); + + // Returns type ParseResponse as its a query, not a single result + var response = await ParseUser.all(); } function() { diff --git a/lib/parse.dart b/lib/parse.dart index 67b87dca6..19062d354 100644 --- a/lib/parse.dart +++ b/lib/parse.dart @@ -4,22 +4,19 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart'; -import 'package:intl/intl.dart'; import 'package:meta/meta.dart'; import 'package:web_socket_channel/io.dart'; part 'src/base/parse_constants.dart'; -part 'src/data/parse_data_server.dart'; -part 'src/data/parse_data_user.dart'; -part 'src/enums/parse_enum_function_call.dart'; -part 'src/enums/parse_enum_object_call.dart'; -part 'src/enums/parse_enum_user_call.dart'; +part 'src/data/parse_core_data.dart'; +part 'src/enums/parse_enum_api_rq.dart'; part 'src/network/parse_http_client.dart'; part 'src/network/parse_livequery.dart'; part 'src/network/parse_query.dart'; part 'src/objects/parse_base.dart'; -part 'src/objects/parse_exception.dart'; +part 'src/objects/parse_error.dart'; part 'src/objects/parse_function.dart'; part 'src/objects/parse_geo_point.dart'; part 'src/objects/parse_object.dart'; @@ -28,9 +25,12 @@ part 'src/objects/parse_user.dart'; part 'src/utils/parse_utils_date.dart'; part 'src/utils/parse_utils_objects.dart'; part 'src/utils/parse_utils.dart'; +part 'src/utils/parse_encoder.dart'; +part 'src/utils/parse_decoder.dart'; +part 'src/utils/parse_logger.dart'; class Parse { - ParseDataServer data; + ParseCoreData data; final ParseHTTPClient client = new ParseHTTPClient(); /// To initialise Parse Server in your application @@ -47,21 +47,22 @@ class Parse { // ``` Parse initialize(appId, serverUrl, {debug, appName, liveQueryUrl, masterKey, sessionId}) { - ParseDataServer.init(appId, serverUrl, + ParseCoreData.init(appId, serverUrl, debug: debug, appName: appName, liveQueryUrl: liveQueryUrl, masterKey: masterKey, sessionId: sessionId); - return _newInstance(ParseDataServer()); + return _newInstance(ParseCoreData()); } - /// Creates a singleton instance of [ParseDataServer] that contains all the server information - Parse _newInstance(ParseDataServer data) { + /// Creates a singleton instance of [ParseCoreData] that contains all the server information + Parse _newInstance(ParseCoreData data) { var parse = Parse(); parse.data = data; parse.client.data = data; + data.initStorage(); return parse; } } diff --git a/lib/src/base/parse_constants.dart b/lib/src/base/parse_constants.dart index b1404df56..c6ea7893d 100644 --- a/lib/src/base/parse_constants.dart +++ b/lib/src/base/parse_constants.dart @@ -1,13 +1,17 @@ part of flutter_parse_sdk; -/// Class containing all constants for this library -class ParseConstants { +// Utils +const String PARSE_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm"; - static const String PARSE_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm"; - static const String OBJECT_ID = 'objectId'; - static const String CREATED_AT = 'createdAt'; - static const String UPDATED_AT = 'updatedAT'; +// ParseObject variables +const String OBJECT_ID = 'objectId'; +const String CREATED_AT = 'createdAt'; +const String UPDATED_AT = 'updatedAT'; - static const String HEADER_SESSION_TOKEN = 'X-Parse-Session-Token'; - static const String HEADER_REVOCABLE_SESSION = 'X-Parse-Revocable-Session'; -} \ No newline at end of file +// Headers +const String HEADER_SESSION_TOKEN = 'X-Parse-Session-Token'; +const String HEADER_REVOCABLE_SESSION = 'X-Parse-Revocable-Session'; + +// Storage +const String PARSE_STORE_BASE = 'flutter_parse_sdk_'; +const String PARSE_STORE_USER = "${PARSE_STORE_BASE}user"; \ No newline at end of file diff --git a/lib/src/data/parse_data_server.dart b/lib/src/data/parse_core_data.dart similarity index 74% rename from lib/src/data/parse_data_server.dart rename to lib/src/data/parse_core_data.dart index b796314db..516169610 100644 --- a/lib/src/data/parse_data_server.dart +++ b/lib/src/data/parse_core_data.dart @@ -1,16 +1,16 @@ part of flutter_parse_sdk; /// Singleton class that defines all user keys and data -class ParseDataServer { - static ParseDataServer _instance; - static ParseDataServer get instance => _instance; +class ParseCoreData { + static ParseCoreData _instance; + static ParseCoreData get instance => _instance; /// Creates an instance of Parse Server /// /// This class should not be user unless switching servers during the app, /// which is odd. Should only be user by Parse.init static void init(appId, serverUrl, {debug, appName, liveQueryUrl, masterKey, sessionId}){ - _instance = ParseDataServer._init(appId, serverUrl); + _instance = ParseCoreData._init(appId, serverUrl); if (debug != null) _instance.debug = debug; if (appName != null) _instance.appName = appName; @@ -26,12 +26,13 @@ class ParseDataServer { String masterKey; String sessionId; bool debug; + SharedPreferences storage; - ParseDataServer._init( + ParseCoreData._init( this.applicationId, this.serverUrl); - factory ParseDataServer() => _instance; + factory ParseCoreData() => _instance; /// Sets the current sessionId. /// @@ -41,6 +42,12 @@ class ParseDataServer { this.sessionId = sessionId; } + void initStorage() async { + storage = await SharedPreferences.getInstance(); + } + + SharedPreferences getStore() => storage; + @override String toString() => "$applicationId $masterKey"; } diff --git a/lib/src/data/parse_data_user.dart b/lib/src/data/parse_data_user.dart deleted file mode 100644 index b4238cbe9..000000000 --- a/lib/src/data/parse_data_user.dart +++ /dev/null @@ -1,61 +0,0 @@ -part of flutter_parse_sdk; - -/// User class that contains the current user data -class User extends ParseBase { - static User _instance; - - static User get instance => _instance; - - /// Creates an instance of a ParseUser - static void init(username, password, emailAddress) => - _instance ??= User._init(username, password, emailAddress); - - /// Clears user data to mimic logout - static void logout() => _instance = null; - - String acl; - String username; - String password; - String emailAddress; - - /// Creates a singleton instance of a user. - /// - /// There can only be one user - User._init(this.username, this.password, this.emailAddress): super(); - - factory User() => _instance; - - /// Returns a [User] from a [Map] object - fromJson(objectData) { - if (getObjectData() == null) setObjectData(objectData); - getObjectData().addAll(objectData); - if (getObjectData().containsKey(ParseConstants.OBJECT_ID)) objectId = getObjectData()[ParseConstants.OBJECT_ID]; - if (getObjectData().containsKey(ParseConstants.CREATED_AT)) createdAt = convertStringToDateTime(getObjectData()[ParseConstants.CREATED_AT]); - if (getObjectData().containsKey(ParseConstants.UPDATED_AT)) updatedAt = convertStringToDateTime(getObjectData()[ParseConstants.UPDATED_AT]); - if (getObjectData().containsKey(ACL)) acl = getObjectData()[ACL].toString(); - if (getObjectData().containsKey(USERNAME)) username = getObjectData()[USERNAME]; - if (getObjectData().containsKey(PASSWORD)) password = getObjectData()[PASSWORD]; - if (getObjectData().containsKey(EMAIL)) emailAddress = getObjectData()[EMAIL]; - - if (updatedAt == null) updatedAt = createdAt; - - return this; - } - - /// Returns a JSON string of the current user - Map toJson() => { - ACL: acl, - USERNAME: username, - PASSWORD: password, - EMAIL: emailAddress, - }; - - @override - String toString() => "Username: $username \n" - "Email Address:$emailAddress"; - - static const String USERNAME = 'Username'; - static const String EMAIL = 'Email'; - static const String PASSWORD = 'Password'; - static const String ACL = 'ACL'; -} diff --git a/lib/src/enums/parse_enum_api_rq.dart b/lib/src/enums/parse_enum_api_rq.dart new file mode 100644 index 000000000..51f04bfdf --- /dev/null +++ b/lib/src/enums/parse_enum_api_rq.dart @@ -0,0 +1,19 @@ +part of flutter_parse_sdk; + +/// Used to define the API calls made in ParseObject logs +enum ParseApiRQ { + get, + getAll, + create, + save, + query, + delete, + currentUser, + signUp, + login, + verificationEmailRequest, + requestPasswordReset, + destroy, + all, + execute +} diff --git a/lib/src/enums/parse_enum_function_call.dart b/lib/src/enums/parse_enum_function_call.dart deleted file mode 100644 index 9cd5ad2ea..000000000 --- a/lib/src/enums/parse_enum_function_call.dart +++ /dev/null @@ -1,4 +0,0 @@ -part of flutter_parse_sdk; - -/// Used to define the API calls made in ParseFunction logs -enum ParseApiFunction { execute } diff --git a/lib/src/enums/parse_enum_object_call.dart b/lib/src/enums/parse_enum_object_call.dart deleted file mode 100644 index 43e748549..000000000 --- a/lib/src/enums/parse_enum_object_call.dart +++ /dev/null @@ -1,4 +0,0 @@ -part of flutter_parse_sdk; - -/// Used to define the API calls made in ParseObject logs -enum ParseApiObject { get, getAll, create, save, query, delete } diff --git a/lib/src/enums/parse_enum_user_call.dart b/lib/src/enums/parse_enum_user_call.dart deleted file mode 100644 index a6e4530fa..000000000 --- a/lib/src/enums/parse_enum_user_call.dart +++ /dev/null @@ -1,13 +0,0 @@ -part of flutter_parse_sdk; - -/// Used to define the API calls made in ParseUser logs -enum ParseApiUser { - currentUser, - signUp, - login, - verificationEmailRequest, - requestPasswordReset, - save, - destroy, - all -} diff --git a/lib/src/network/parse_http_client.dart b/lib/src/network/parse_http_client.dart index 7b070df50..f47ed62b7 100644 --- a/lib/src/network/parse_http_client.dart +++ b/lib/src/network/parse_http_client.dart @@ -2,9 +2,9 @@ part of flutter_parse_sdk; /// Creates a custom version of HTTP Client that has Parse Data Preset class ParseHTTPClient extends BaseClient { - final Client _client = new Client(); - final String _userAgent = "Dart Parse SDK 0.1"; - ParseDataServer data = ParseDataServer(); + final Client _client = Client(); + final String _userAgent = "Flutter Parse SDK 1.0.3"; + ParseCoreData data = ParseCoreData(); ParseHTTPClient(); @@ -14,8 +14,7 @@ class ParseHTTPClient extends BaseClient { request.headers['user-agent'] = _userAgent; request.headers['X-Parse-Application-Id'] = data.applicationId; request.headers['Content-Type'] = 'application/json'; - if (data.masterKey != null) - request.headers['X-Parse-Master-Key'] = data.masterKey; + if (data.masterKey != null) request.headers['X-Parse-Master-Key'] = data.masterKey; return _client.send(request); } } diff --git a/lib/src/objects/parse_base.dart b/lib/src/objects/parse_base.dart index ed042f850..48c363f1c 100644 --- a/lib/src/objects/parse_base.dart +++ b/lib/src/objects/parse_base.dart @@ -2,23 +2,27 @@ part of flutter_parse_sdk; abstract class ParseBase { /// Stores all the values of a class - Map _objectData; + Map _objectData = Map(); /// Returns [String] objectId - get getObjectId => _objectData['objectId'] == null ? objectId : _objectData['objectId']; - String objectId; + String get objectId => _objectData['objectId']; + set objectId(String objectId) => _objectData[objectId]; /// Returns [DateTime] createdAt - get getCreatedAt => _objectData['createdAt'] == null ? createdAt : _objectData['createdAt']; - DateTime createdAt; + DateTime get createdAt => stringToDateTime(_objectData['createdAt']); + set createdAt(DateTime createdAt) => + _objectData['createdAt'] = dateTimeToString(createdAt); /// Returns [DateTime] updatedAt - get getUpdatedAt => _objectData['updatedAt'] == null ? updatedAt : _objectData['updatedAt']; - DateTime updatedAt; + DateTime get updatedAt => stringToDateTime(_objectData['updatedAt']); + set updatedAt(DateTime updatedAt) => + _objectData['updatedAt'] = dateTimeToString(updatedAt); + /// Converts object to [String] in JSON format @protected toJson() => JsonEncoder().convert(getObjectData()); + /// Creates a copy of this class @protected copy() => JsonDecoder().convert(fromJson(getObjectData())); @@ -30,6 +34,12 @@ abstract class ParseBase { @protected getObjectData() => _objectData; + /// Saves in storage + @protected + saveInStorage(String key) async { + await ParseCoreData().getStore().setString(key, toJson()); + } + @protected fromJson(Map objectData) { if (_objectData == null) _objectData = Map(); @@ -37,7 +47,7 @@ abstract class ParseBase { } /// Create a new variable for this object, [bool] forceUpdate is always true, - /// if unsure as to wether an item is needed or not, set to false + /// if unsure as to whether an item is needed or not, set to false set(String key, dynamic value, {bool forceUpdate: true}) { if (value != null) { if (getObjectData().containsKey(key)) { @@ -56,4 +66,45 @@ abstract class ParseBase { return defaultValue; } } + + /// Saves item to simple key pair value storage + /// + /// Replicates Android SDK pin process and saves object to storage + pin() async { + if (objectId != null) { + await ParseCoreData().getStore().setString(objectId, toJson()); + return true; + } else { + return false; + } + } + + /// Saves item to simple key pair value storage + /// + /// Replicates Android SDK pin process and saves object to storage + unpin() async { + if (objectId != null) { + var itemToSave = await ParseCoreData().getStore().setString(objectId, null); + return true; + } else { + return false; + } + } + + /// Saves item to simple key pair value storage + /// + /// Replicates Android SDK pin process and saves object to storage + fromPin() async { + if (objectId != null) { + var itemFromStore = await ParseCoreData().getStore().getString(objectId); + + if (itemFromStore != null) { + Map itemFromStoreMap = JsonDecoder().convert( + itemFromStore); + fromJson(itemFromStoreMap); + return this; + } + } + return null; + } } diff --git a/lib/src/objects/parse_error.dart b/lib/src/objects/parse_error.dart new file mode 100644 index 000000000..7cce784f8 --- /dev/null +++ b/lib/src/objects/parse_error.dart @@ -0,0 +1,78 @@ +part of flutter_parse_sdk; + +/// ParseException is used in [ParseResult] to inform the user of the exception +class ParseError { + + Map exceptions = { + -1 :'UnknownError', + + // SDK errors / Errors + 1: 'No Results', + 400: 'Bad Request', + + // Parse specific / Exceptions + 100: 'ConnectionFailed', + 101: 'ObjectNotFound', + 102: 'InvalidQuery', + 103: 'InvalidClassName', + 104: 'MissingObjectId', + 105: 'InvalidKeyName', + 106: 'InvalidPointer', + 107: 'InvalidJson', + 108: 'CommandUnavailable', + 109: 'NotInitialized', + 111: 'IncorrectType', + 112: 'InvalidChannelName', + 115: 'PushMisconfigured', + 116: 'ObjectTooLarge', + 119: 'OperationForbidden', + 120: 'CacheMiss', + 121: 'InvalidNestedKey', + 122: 'InvalidFileName', + 123: 'InvalidAcl', + 124: 'Timeout', + 125: 'InvalidEmailAddress', + 135: 'MissingRequiredFieldError', + 137: 'DuplicateValue', + 139: 'InvalidRoleName', + 140: 'ExceededQuota', + 141: 'ScriptError', + 142: 'ValidationError', + 153: 'FileDeleteError', + 155: 'RequestLimitExceeded', + 160: 'InvalidEventName', + 200: 'UsernameMissing', + 201: 'PasswordMissing', + 202: 'UsernameTaken', + 203: 'EmailTaken', + 204: 'EmailMissing', + 205: 'EmailNotFound', + 206: 'SessionMissing', + 207: 'MustCreateUserThroughSignup', + 208: 'AccountAlreadyLinked', + 209: 'InvalidSessionToken', + 250: 'LinkedIdMissing', + 251: 'InvalidLinkedSession', + 252: 'UnsupportedService'}; + + final int code; + final String message; + final bool isTypeOfException; + String type; + + ParseError({this.code = -1, this.message = "Unkown error", this.isTypeOfException = false, bool debug: false}){ + type = exceptions[code]; + if (debug) print(toString()); + } + + @override + String toString() { + var exceptionString = ' \n'; + exceptionString += "----"; + exceptionString +="\nParseException (Type: $type) :"; + exceptionString +="\nCode: $code"; + exceptionString +="\nMessage: $message"; + exceptionString += "----"; + return exceptionString; + } +} \ No newline at end of file diff --git a/lib/src/objects/parse_exception.dart b/lib/src/objects/parse_exception.dart deleted file mode 100644 index e349d83cd..000000000 --- a/lib/src/objects/parse_exception.dart +++ /dev/null @@ -1,9 +0,0 @@ -part of flutter_parse_sdk; - -/// ParseException is used in [ParseResult] to inform the user of the exception -class ParseException { - - String message; - - ParseException(); -} \ No newline at end of file diff --git a/lib/src/objects/parse_function.dart b/lib/src/objects/parse_function.dart index 4da8ddee7..578930446 100644 --- a/lib/src/objects/parse_function.dart +++ b/lib/src/objects/parse_function.dart @@ -1,19 +1,21 @@ part of flutter_parse_sdk; -class ParseCloudFunction extends ParseBase { +class ParseCloudFunction extends ParseObject { final String functionName; - String _path; bool _debug; ParseHTTPClient _client; + @override + String _path; + /// Creates a new cloud function object /// /// {https://docs.parseplatform.org/cloudcode/guide/} - ParseCloudFunction(this.functionName, {bool debug, ParseHTTPClient client}) { - client == null ? _client = ParseHTTPClient() : _client = client; - _debug = isDebugEnabled(debug, _client); + ParseCloudFunction(this.functionName, {bool debug, ParseHTTPClient client}) : super (functionName) { _path = "/functions/$functionName"; - setObjectData(Map()); + + if (debug != null) setDebug(debug); + if (client != null) setClient(client); } /// Executes a cloud function @@ -22,32 +24,6 @@ class ParseCloudFunction extends ParseBase { execute() async { var uri = _client.data.serverUrl + "$_path"; var result = await _client.post(uri, body: JsonEncoder().convert(getObjectData())); - return _handleResult(result, ParseApiFunction.execute); - } - - /// Handles an API response - ParseResponse _handleResult(Response response, ParseApiFunction type) { - ParseResponse parseResponse = ParseResponse.handleResponse(this, response); - Map responseData = JsonDecoder().convert(response.body); - - if (_client.data.debug || _debug) { - var responseString = ' \n'; - - responseString += "----" - "\n${_client.data.appName} API Response ($functionName : ${type.toString()}) :"; - - if (parseResponse.success && parseResponse.result != null) { - responseString += "\nStatus Code: ${parseResponse.statusCode}"; - responseString += "\nPayload: ${responseData.toString()}"; - } else if (!parseResponse.success) { - responseString += "\nStatus Code: ${responseData['code'] == null ? parseResponse.statusCode : responseData['code']}"; - responseString += "\nException: ${responseData['error'] == null ? responseData.toString() : responseData['error']}"; - } - - responseString += "\n----\n"; - print(responseString); - } - - return parseResponse; + return super.handleResponse(result, ParseApiRQ.execute); } } diff --git a/lib/src/objects/parse_geo_point.dart b/lib/src/objects/parse_geo_point.dart index c2769c68e..77f979b92 100644 --- a/lib/src/objects/parse_geo_point.dart +++ b/lib/src/objects/parse_geo_point.dart @@ -1,17 +1,17 @@ part of flutter_parse_sdk; -class ParseGeoPoint extends ParseBase { - final String className = 'GeoPoint'; +class ParseGeoPoint extends ParseObject { double _latitude; double _longitude; /// Creates a Parse Object of type GeoPoint - ParseGeoPoint({double latitude = 0.0, double longitude = 0.0}): - assert(latitude >= -90.0 || latitude <= 90.0), - assert(longitude >= -180.0 || longitude <= 180.0) { + ParseGeoPoint({double latitude = 0.0, double longitude = 0.0, bool debug, ParseHTTPClient client}): super ('GeoPoint') { _latitude = latitude; _longitude = longitude; + + if (debug != null) setDebug(debug); + if (client != null) setClient(client); } double get latitude => _latitude; diff --git a/lib/src/objects/parse_object.dart b/lib/src/objects/parse_object.dart index bbc4a3ed5..97ec7cbcf 100644 --- a/lib/src/objects/parse_object.dart +++ b/lib/src/objects/parse_object.dart @@ -11,35 +11,51 @@ class ParseObject extends ParseBase { /// [String] className refers to the Table Name in your Parse Server, /// [bool] debug will overwrite the current default debug settings and /// [ParseHttpClient] can be overwritten to create your own HTTP Client - ParseObject(this.className, {bool debug: false, ParseHTTPClient client}) { + ParseObject(this.className, {bool debug: false}): super() { + _path = "/classes/$className"; + setClient(ParseHTTPClient()); + setDebug(isDebugEnabled(_client, objectLevelDebug: debug)); + } - client == null ? _client = ParseHTTPClient() : _client = client; - _debug = isDebugEnabled(debug, _client); + setDebug(bool debug){ + _debug = debug; + } - _path = "/classes/$className"; - setObjectData(Map()); + setClient(ParseHTTPClient client){ + _client = client; } /// Gets an object from the server using it's [String] objectId getObject(String objectId) async { - var uri = _getBasePath(_path); - if (objectId != null) uri += "/$objectId"; - var result = await _client.get(uri); - return _handleResult(result, ParseApiObject.get); + try { + var uri = "${ParseCoreData().serverUrl}$_path"; + if (objectId != null) uri += "/$objectId"; + var result = await _client.get(uri); + return handleResponse(result, ParseApiRQ.get); + } on Exception catch (e) { + return handleException(e, ParseApiRQ.get); + } } /// Gets all objects from this table - Limited response at the moment getAll() async { - var result = await _client.get(_getBasePath(_path)); - return _handleResult(result, ParseApiObject.getAll); + try { + var result = await _client.get("${ParseCoreData().serverUrl}$_path"); + return handleResponse(result, ParseApiRQ.getAll); + } on Exception catch (e) { + return handleException(e, ParseApiRQ.getAll); + } } /// Creates a new object and saves it online create() async { - var uri = _client.data.serverUrl + "$_path"; - var result = - await _client.post(uri, body: JsonEncoder().convert(getObjectData())); - return _handleResult(result, ParseApiObject.create); + try { + var uri = _client.data.serverUrl + "$_path"; + var result = await _client.post(uri, body: JsonEncoder().convert(getObjectData())); + return handleResponse(result, ParseApiRQ.create); + } on Exception catch (e) { + return handleException(e, ParseApiRQ.create); + } } /// Saves the current object online @@ -47,51 +63,57 @@ class ParseObject extends ParseBase { if (getObjectData() == null) { return create(); } else { - var uri = "${_getBasePath(_path)}/$objectId"; - var result = - await _client.put(uri, body: JsonEncoder().convert(getObjectData())); - return _handleResult(result, ParseApiObject.save); + try { + var uri = "${ParseCoreData().serverUrl}$_path/$objectId"; + var result = await _client.put(uri, body: JsonEncoder().convert(getObjectData())); + return handleResponse(result, ParseApiRQ.save); + } on Exception catch (e) { + return handleException(e, ParseApiRQ.save); + } } } /// Can be used to create custom queries query(String query) async { - var uri = "${_getBasePath(_path)}?$query"; - var result = await _client.get(uri); - return _handleResult(result, ParseApiObject.query); + try { + var uri = "${ParseCoreData().serverUrl}$_path?$query"; + var result = await _client.get(uri); + return handleResponse(result, ParseApiRQ.query); + } on Exception catch (e) { + return handleException(e, ParseApiRQ.query); + } } /// Deletes the current object locally and online delete(String path, String objectId) async { - var uri = "${_getBasePath(path)}/$objectId"; - var result = await _client.delete(uri); - return _handleResult(result, ParseApiObject.delete); + try { + var uri = "${ParseCoreData().serverUrl}$_path/$objectId"; + var result = await _client.delete(uri); + return handleResponse(result, ParseApiRQ.delete); + } on Exception catch (e) { + return handleException(e, ParseApiRQ.delete); + } } - /// Generates the path for the object - _getBasePath(String path) => "${_client.data.serverUrl}$path"; - /// Handles an API response and logs data if [bool] debug is enabled - ParseResponse _handleResult(Response response, ParseApiObject type) { + @protected + ParseResponse handleResponse(Response response, ParseApiRQ type) { ParseResponse parseResponse = ParseResponse.handleResponse(this, response); - Map responseData = JsonDecoder().convert(response.body); - if (_client.data.debug || _debug) { - var responseString = ' \n'; + if (_debug) { + logger(ParseCoreData().appName, className, type.toString(), parseResponse); + } - responseString += "----" - "\n${_client.data.appName} API Response ($className : ${type.toString()}) :"; + return parseResponse; + } - if (parseResponse.success && parseResponse.result != null) { - responseString += "\nStatus Code: ${parseResponse.statusCode}"; - responseString += "\nPayload: ${responseData.toString()}"; - } else if (!parseResponse.success) { - responseString += "\nStatus Code: ${responseData['code'] == null ? parseResponse.statusCode : responseData['code']}"; - responseString += "\nException: ${responseData['error'] == null ? responseData.toString() : responseData['error']}"; - } + /// Handles an API response and logs data if [bool] debug is enabled + @protected + ParseResponse handleException(Exception exception, ParseApiRQ type) { + ParseResponse parseResponse = ParseResponse.handleException(this, exception); - responseString += "\n----\n"; - print(responseString); + if (_debug) { + logger(ParseCoreData().appName, className, type.toString(), parseResponse); } return parseResponse; diff --git a/lib/src/objects/parse_response.dart b/lib/src/objects/parse_response.dart index d1bfa81bc..daf450c34 100644 --- a/lib/src/objects/parse_response.dart +++ b/lib/src/objects/parse_response.dart @@ -4,7 +4,7 @@ class ParseResponse { bool success = false; int statusCode = -1; dynamic result; - ParseException exception; + ParseError error; /// Handles all the ParseObject responses /// @@ -12,43 +12,48 @@ class ParseResponse { /// 1. Fail - [ParseResponse] will be returned with further details /// 2. Success but no results. [ParseResponse] is returned. /// 3. Success with results. Again [ParseResponse] is returned - static handleResponse(ParseBase object, Response value) { - var response = ParseResponse(); + static handleResponse(ParseBase object, Response apiResponse) { + var parseResponse = ParseResponse(); - if (value != null) { - response.statusCode = value.statusCode; + if (apiResponse != null) { + parseResponse.statusCode = apiResponse.statusCode; - if (value.statusCode != 200) { - return _handleError(response, value); - } else if (value.body == "{\"results\":[]}"){ - return _handleSuccessWithNoResults(response, "Successful request, but no results found"); + if (apiResponse.statusCode != 200) { + return _handleError(parseResponse, apiResponse); + } else if (apiResponse.body == "{\"results\":[]}"){ + return _handleSuccessWithNoResults(parseResponse, "Successful request, but no results found"); } else { - return _handleSuccess(response, object, value.body); + return _handleSuccess(parseResponse, object, apiResponse.body); } } else { - response.exception = ParseException(); - response.exception.message = "Error reaching server, or server response was null"; - return response; + parseResponse.error = ParseError(message: "Error reaching server, or server response was null"); + return apiResponse; } } + /// Handles exception instead of throwing an exception + static handleException(ParseBase object, Exception exception) { + var response = ParseResponse(); + response.error = ParseError(message: exception.toString(), isTypeOfException: true); + return response; + } + /// Handles any errors returned in response - static ParseResponse _handleError(ParseResponse response, Response value) { - response.exception = ParseException(); - response.exception.message = value.reasonPhrase; + static ParseResponse _handleError(ParseResponse response, Response apiResponse) { + Map responseData = JsonDecoder().convert(apiResponse.body); + response.error = ParseError(code: responseData['code'], message: responseData['error']); return response; } /// Handles successful responses with no results static ParseResponse _handleSuccessWithNoResults(ParseResponse response, String value) { response.statusCode = 200; - response.exception = ParseException(); - response.exception.message = value; + response.error = ParseError(code: 1, message: value); return response; } - /// Handles succsful response with results - static ParseResponse _handleSuccess(ParseResponse response, ParseObject object, String responseBody) { + /// Handles successful response with results + static ParseResponse _handleSuccess(ParseResponse response, ParseBase object, String responseBody) { response.success = true; var map = JsonDecoder().convert(responseBody) as Map; @@ -63,7 +68,7 @@ class ParseResponse { } /// Handles a response with a multiple result object - static _handleMultipleResults(ParseObject object, dynamic map) { + static _handleMultipleResults(ParseBase object, dynamic map) { var resultsList = List(); for (var value in map) { @@ -74,7 +79,7 @@ class ParseResponse { } /// Handles a response with a single result object - static _handleSingleResult(ParseObject object, map) { + static _handleSingleResult(ParseBase object, map) { populateObjectBaseData(object, map); return object.fromJson(map); } diff --git a/lib/src/objects/parse_user.dart b/lib/src/objects/parse_user.dart index ce188d30c..384f08b0f 100644 --- a/lib/src/objects/parse_user.dart +++ b/lib/src/objects/parse_user.dart @@ -1,41 +1,77 @@ part of flutter_parse_sdk; -class ParseUser { - ParseHTTPClient _client; +class ParseUser extends ParseBase { static final String className = '_User'; - String path = "/classes/$className"; + static final String path = "/classes/$className"; bool _debug; + ParseHTTPClient _client; + + String acl; + String username; + String password; + String emailAddress; /// Creates an instance of ParseUser /// /// Users can set whether debug should be set on this class with a [bool], /// they can also create thier own custom version of [ParseHttpClient] - ParseUser({debug, ParseHTTPClient client}) { - client != null ? _client = client : _client = ParseHTTPClient(); - _debug = isDebugEnabled(debug, _client); - } - + /// /// Creates a new user locally /// /// Requires [String] username, [String] password. [String] email address /// is required as well to create a full new user object on ParseServer. Only /// username and password is required to login + ParseUser(this.username, this.password, this.emailAddress, {bool debug, ParseHTTPClient client}) : super() { + client == null ? _client = ParseHTTPClient() : _client = client; + _debug = isDebugEnabled(client, objectLevelDebug: debug); + } + + /// Returns a [User] from a [Map] object + fromJson(objectData) { + if (getObjectData() == null) { + setObjectData(objectData); + } else { + getObjectData().addAll(objectData); + } + + objectId = getObjectData()[OBJECT_ID]; + createdAt = stringToDateTime(getObjectData()[CREATED_AT]); + updatedAt = stringToDateTime(getObjectData()[UPDATED_AT]); + acl = getObjectData()[ACL].toString(); + username = getObjectData()[USERNAME]; + password = getObjectData()[PASSWORD]; + emailAddress = getObjectData()[EMAIL]; + + if (updatedAt == null) updatedAt = createdAt; + + saveInStorage(PARSE_STORE_USER); + + return getObjectData(); + } + + /// Returns a [String] that's human readable. Ideal for printing logs + @override + String toString() => "Username: $username \nEmail Address:$emailAddress"; + + static const String USERNAME = 'Username'; + static const String EMAIL = 'Email'; + static const String PASSWORD = 'Password'; + static const String ACL = 'ACL'; + create(String username, String password, [String emailAddress]) { - User.init(username, password, emailAddress); - return User.instance; + return ParseUser(username, password, emailAddress); } - /// Gets the current user + /// Gets the current user from the server /// /// Current user is stored locally, but in case of a server update [bool] /// fromServer can be called and an updated version of the [User] object will be /// returned - currentUser({bool fromServer: false}) async { - if (_client.data.sessionId == null) { - return null; - } else if (fromServer == false) { - return User.instance; - } else { + getCurrentUserFromServer() async { + // We can't get the current user and session without a sessionId + if (_client.data.sessionId == null) return null; + + try { Uri tempUri = Uri.parse(_client.data.serverUrl); Uri uri = Uri( @@ -43,41 +79,53 @@ class ParseUser { host: tempUri.host, path: "${tempUri.path}/users/me"); - final response = await _client.get(uri, headers: { - ParseConstants.HEADER_SESSION_TOKEN: _client.data.sessionId - }); - return _handleResponse(response, ParseApiUser.currentUser); + final response = await _client + .get(uri, headers: {HEADER_SESSION_TOKEN: _client.data.sessionId}); + return _handleResponse(response, ParseApiRQ.currentUser); + } on Exception catch (e) { + return _handleException(e, ParseApiRQ.currentUser); } } + /// Gets the current user from storage + /// + /// Current user is stored locally, but in case of a server update [bool] + /// fromServer can be called and an updated version of the [User] object will be + /// returned + static currentUser() { + return _getUserFromLocalStore(); + } /// Registers a user on Parse Server /// /// After creating a new user via [Parse.create] call this method to register /// that user on Parse signUp() async { + try { + if (emailAddress == null) return null; - if (User().emailAddress == null) return null; - - Map bodyData = {}; - bodyData["email"] = User().emailAddress; - bodyData["password"] = User().password; - bodyData["username"] = User().username; + Map bodyData = {}; + bodyData["email"] = emailAddress; + bodyData["password"] = password; + bodyData["username"] = username; - Uri tempUri = Uri.parse(_client.data.serverUrl); - - Uri url = Uri( - scheme: tempUri.scheme, - host: tempUri.host, - path: "${tempUri.path}$path"); - - final response = await _client.post(url, - headers: { - ParseConstants.HEADER_REVOCABLE_SESSION: "1", - }, - body: JsonEncoder().convert(bodyData)); + Uri tempUri = Uri.parse(_client.data.serverUrl); - _handleResponse(response, ParseApiUser.signUp); - return User.instance; + Uri url = Uri( + scheme: tempUri.scheme, + host: tempUri.host, + path: "${tempUri.path}$path"); + + final response = await _client.post(url, + headers: { + HEADER_REVOCABLE_SESSION: "1", + }, + body: JsonEncoder().convert(bodyData)); + + _handleResponse(response, ParseApiRQ.signUp); + return this; + } on Exception catch (e) { + return _handleException(e, ParseApiRQ.signUp); + } } /// Logs a user in via Parse @@ -85,48 +133,56 @@ class ParseUser { /// Once a user is created using [Parse.create] and a username and password is /// provided, call this method to login. login() async { - Uri tempUri = Uri.parse(_client.data.serverUrl); + try { + Uri tempUri = Uri.parse(_client.data.serverUrl); - Uri url = Uri( - scheme: tempUri.scheme, - host: tempUri.host, - path: "${tempUri.path}/login", - queryParameters: { - "username": User.instance.username, - "password": User.instance.password - }); + Uri url = Uri( + scheme: tempUri.scheme, + host: tempUri.host, + path: "${tempUri.path}/login", + queryParameters: {"username": username, "password": password}); - final response = await _client.post(url, headers: { - ParseConstants.HEADER_REVOCABLE_SESSION: "1", - }); + final response = await _client.post(url, headers: { + HEADER_REVOCABLE_SESSION: "1", + }); - _handleResponse(response, ParseApiUser.login); - return User.instance; + _handleResponse(response, ParseApiRQ.login); + return this; + } on Exception catch (e) { + return _handleException(e, ParseApiRQ.login); + } } /// Removes the current user from the session data - logout(){ + logout() { _client.data.sessionId = null; - User.logout(); + setObjectData(null); } /// Sends a verification email to the users email address verificationEmailRequest() async { - final response = await _client.post( - "${_client.data.serverUrl}/verificationEmailRequest", - body: JsonEncoder().convert({"email": User().emailAddress})); - - return _handleResponse( - response, ParseApiUser.verificationEmailRequest); + try { + final response = await _client.post( + "${_client.data.serverUrl}/verificationEmailRequest", + body: JsonEncoder().convert({"email": emailAddress})); + + return _handleResponse(response, ParseApiRQ.verificationEmailRequest); + } on Exception catch (e) { + return _handleException(e, ParseApiRQ.verificationEmailRequest); + } } /// Sends a password reset email to the users email address requestPasswordReset() async { - final response = await _client.post( - "${_client.data.serverUrl}/requestPasswordReset", - body: JsonEncoder().convert({"email": User().emailAddress})); - - return _handleResponse(response, ParseApiUser.requestPasswordReset); + try { + final response = await _client.post( + "${_client.data.serverUrl}/requestPasswordReset", + body: JsonEncoder().convert({"email": emailAddress})); + + return _handleResponse(response, ParseApiRQ.requestPasswordReset); + } on Exception catch (e) { + return _handleException(e, ParseApiRQ.requestPasswordReset); + } } /// Saves the current user @@ -134,59 +190,108 @@ class ParseUser { /// If changes are made to the current user, call save to sync them with /// Parse Server save() async { - if (User.instance.objectId == null) { + if (objectId == null) { return signUp(); } else { - final response = await _client.put( - _client.data.serverUrl + "$path/${User().objectId}", - body: JsonEncoder().convert(User().getObjectData())); - return _handleResponse(response, ParseApiUser.save); + try { + final response = await _client.put( + _client.data.serverUrl + "$path/$objectId", + body: JsonEncoder().convert(getObjectData())); + return _handleResponse(response, ParseApiRQ.save); + } on Exception catch (e) { + return _handleException(e, ParseApiRQ.save); + } } } /// Removes a user from Parse Server locally and online destroy() async { - final response = await _client.delete( - _client.data.serverUrl + "$path/${User().objectId}", - headers: {"X-Parse-Session-Token": _client.data.sessionId}); - - _handleResponse(response, ParseApiUser.destroy); - - return User.instance.objectId; + if (objectId != null) { + try { + final response = await _client.delete( + _client.data.serverUrl + "$path/$objectId", + headers: {"X-Parse-Session-Token": _client.data.sessionId}); + _handleResponse(response, ParseApiRQ.destroy); + return objectId; + } on Exception catch (e) { + return _handleException(e, ParseApiRQ.destroy); + } + } } /// Gets a list of all users (limited return) - all() async { - final response = await _client.get(_client.data.serverUrl + "$path"); - return _handleResponse(response, ParseApiUser.all); + static all() async { + + var emptyUser = ParseUser(null, null, null); + + try { + final response = await ParseHTTPClient().get( + "${ParseCoreData().serverUrl}/$path"); + + ParseResponse parseResponse = ParseResponse.handleResponse(emptyUser, response); + + if (ParseCoreData().debug) { + logger(ParseCoreData().appName, className, ParseApiRQ.getAll.toString(), parseResponse); + } + + return parseResponse; + } on Exception catch (e) { + return ParseResponse.handleException(emptyUser, e); + } } - /// Handles all the reponse data for this class - _handleResponse(Response response, ParseApiUser type) { - Map responseData = JsonDecoder().convert(response.body); + static _getUserFromLocalStore() { + var userJson = ParseCoreData().getStore().getString(PARSE_STORE_USER); - var responseString = ' \n'; + if (userJson != null) { + var userMap = JsonDecoder().convert(userJson); - responseString += "----""\n${_client.data.appName} API Response ($className : ${type.toString()}) :"; + if (userMap != null) { + ParseCoreData().sessionId = userMap['sessionToken']; - if (response.statusCode == 200 || response.statusCode == 201) { - responseString += "\nStatus Code: ${response.statusCode}"; - responseString += "\nPayload: ${responseData.toString()}"; + var user = ParseUser( + userMap['username'], + userMap['password'], + userMap['emailAddress']); - if (responseData.containsKey('objectId')) { - User.instance.fromJson(responseData); - _client.data.sessionId = responseData['sessionToken']; + user.fromJson(userMap); + return user; } - } else { - responseString += "\nStatus Code: ${responseData['code']}"; - responseString += "\nException: ${responseData['error']}"; } - if (_client.data.debug || _debug) { - responseString += "\n----\n"; - print(responseString); + return null; + } + + /// Handles an API response and logs data if [bool] debug is enabled + @protected + ParseResponse _handleException(Exception exception, ParseApiRQ type) { + ParseResponse parseResponse = ParseResponse.handleException(this, exception); + + if (_debug) { + logger(ParseCoreData().appName, className, type.toString(), parseResponse); } - return User.instance; + return parseResponse; + } + + /// Handles all the response data for this class + _handleResponse(Response response, ParseApiRQ type) { + + ParseResponse parseResponse = ParseResponse.handleResponse(this, response); + if (_debug) { + logger(ParseCoreData().appName, className, type.toString(), parseResponse); + } + + Map responseData = JsonDecoder().convert(response.body); + if (responseData.containsKey('objectId')) { + fromJson(responseData); + _client.data.sessionId = responseData['sessionToken']; + } + + if (type == ParseApiRQ.getAll) { + return parseResponse; + } else { + return this; + } } } diff --git a/lib/src/utils/parse_decoder.dart b/lib/src/utils/parse_decoder.dart new file mode 100644 index 000000000..353b7b4c6 --- /dev/null +++ b/lib/src/utils/parse_decoder.dart @@ -0,0 +1,76 @@ +part of flutter_parse_sdk; + +class ParseDecoder { + + List _convertJSONArrayToList(List array) { + List list = new List(); + array.forEach((value) { + list.add(decode(value)); + }); + return list; + } + + Map _convertJSONObjectToMap(Map object) { + Map map = new Map(); + object.forEach((key, value) { + map.putIfAbsent(key, () => decode(value)); + }); + return map; + } + + /// Decode any type value + dynamic decode(dynamic value) { + if (value is List) { + return _convertJSONArrayToList(value); + } + + if (value is bool) { + return value; + } + + if (value is int) { + return value.toInt(); + } + + if (value is double) { + return value.toDouble(); + } + + if (value is num) { + return value; + } + + if (!(value is Map)) { + return value; + } + + Map map = value; + if (!map.containsKey("__type")) { + return _convertJSONObjectToMap(map); + } + + switch (map["__type"]) { + case "Date": + String iso = map["iso"]; + return stringToDateTime(iso); + case "Bytes": + String val = map["base64"]; + return base64.decode(val); + case "Pointer": + String className = map["className"]; + return new ParseObject(className)..fromJson(map); + case "Object": + String className = map["className"]; + if (className == '_User') { + return new ParseUser(map['username'], map['password'], map['emailaddress'])..fromJson(map); + } + return new ParseObject(className)..fromJson(map); + case "GeoPoint": + num latitude = map["latitude"] ?? 0.0; + num longitude = map["longitude"] ?? 0.0; + return new ParseGeoPoint(latitude: latitude.toDouble(), longitude: longitude.toDouble()); + } + + return null; + } +} \ No newline at end of file diff --git a/lib/src/utils/parse_encoder.dart b/lib/src/utils/parse_encoder.dart new file mode 100644 index 000000000..e1c41727f --- /dev/null +++ b/lib/src/utils/parse_encoder.dart @@ -0,0 +1,49 @@ +part of flutter_parse_sdk; + +class ParseEncoder { + + bool isValidType(dynamic value) { + return value == null || + value is String || + value is num || + value is bool || + value is DateTime || + value is List || + value is Map || + value is ParseObject || + value is ParseGeoPoint || + value is ParseUser; + } + + /// Custom json encoder for types related to parse + dynamic encode(dynamic value) { + if (value is DateTime) return _encodeDate(value); + + if (value is List) { + return value.map((v){ + return encode(v); + }).toList(); + } + + if (value is ParseObject) { + return value.toJson; + } + + if (value is ParseUser) { + return value.toJson; + } + + if (value is ParseGeoPoint) { + return value.toJson; + } + + return value; + } + + Map _encodeDate(DateTime date) { + return { + "__type": "Date", + "iso": dateTimeToString(date) + }; + } +} \ No newline at end of file diff --git a/lib/src/utils/parse_logger.dart b/lib/src/utils/parse_logger.dart new file mode 100644 index 000000000..b35df4129 --- /dev/null +++ b/lib/src/utils/parse_logger.dart @@ -0,0 +1,26 @@ +part of flutter_parse_sdk; + +void logger( + String appName, + String className, + String type, + ParseResponse parseResponse) { + var responseString = ' \n'; + + responseString += "----\n$appName API Response ($className : $type) :"; + + if (parseResponse.success && parseResponse.result != null) { + responseString += "\nStatus Code: ${parseResponse.statusCode}"; + responseString += "\nPayload: ${parseResponse.result.toString()}"; + } else if (!parseResponse.success) { + responseString += "\nStatus Code: ${parseResponse.error.code}"; + responseString += "\nType: ${parseResponse.error.type}"; + + String errorOrException = parseResponse.error.isTypeOfException ? "Exception" : "Error"; + + responseString += "\n$errorOrException: ${parseResponse.error.message}"; + } + + responseString += "\n----\n"; + print(responseString); +} \ No newline at end of file diff --git a/lib/src/utils/parse_utils.dart b/lib/src/utils/parse_utils.dart index 3ab8c88b8..7a448b6db 100644 --- a/lib/src/utils/parse_utils.dart +++ b/lib/src/utils/parse_utils.dart @@ -1,15 +1,11 @@ part of flutter_parse_sdk; -/// Checks wether debug is enabled +/// Checks whether debug is enabled /// /// Debug can be set in 2 places, one global param in the Parse.initialise, and -/// then can be overriden class by class -bool isDebugEnabled(bool debug, ParseHTTPClient _client) { - if (debug == null) { - _client.data.debug != null ? debug = _client.data.debug : debug = false; - } else { - return debug; - } - +/// then can be overidden class by class +bool isDebugEnabled(ParseHTTPClient _client, {objectLevelDebug: false}) { + bool debug = objectLevelDebug; + if (ParseCoreData().debug != null) debug = ParseCoreData().debug; return debug; } \ No newline at end of file diff --git a/lib/src/utils/parse_utils_date.dart b/lib/src/utils/parse_utils_date.dart index 3f8561188..c0073abbd 100644 --- a/lib/src/utils/parse_utils_date.dart +++ b/lib/src/utils/parse_utils_date.dart @@ -1,23 +1,51 @@ part of flutter_parse_sdk; /// Converts a [String] into a [DateTime] from a Parse Server format -DateTime convertStringToDateTime(String date) { +DateTime stringToDateTime(String date) { if (date == null) return null; try { - var formatter = DateFormat(ParseConstants.PARSE_DATE_FORMAT); - return formatter.parse(_removeTimeZones(date)); + return DateTime.parse(date); } on FormatException { return null; } } -/// Removes timezones as our current implementation does work -String _removeTimeZones(String date) { - // TODO - library doesn't support timezones. Monitor this - if (date.contains('zzzZ')) { - return date.replaceRange(date.length - 4, date.length, ''); - } else { - return date; +/// Serialize [DateTime] into an ISO-8601 full-precision extended format representation. +String dateTimeToString(DateTime datetime) { + if (datetime == null) return null; + + if (!datetime.isUtc) { + datetime = datetime.toUtc(); } + + String y = _fourDigits(datetime.year); + String m = _twoDigits(datetime.month); + String d = _twoDigits(datetime.day); + String h = _twoDigits(datetime.hour); + String min = _twoDigits(datetime.minute); + String sec = _twoDigits(datetime.second); + String ms = _threeDigits(datetime.millisecond); + + return "$y-$m-${d}T$h:$min:$sec.${ms}Z"; +} + +String _fourDigits(int n) { + int absN = n.abs(); + String sign = n < 0 ? "-" : ""; + if (absN >= 1000) return "$n"; + if (absN >= 100) return "${sign}0$absN"; + if (absN >= 10) return "${sign}00$absN"; + return "${sign}000$absN"; +} + +String _threeDigits(int n) { + if (n >= 100) return "$n"; + if (n >= 10) return "0$n"; + return "00$n"; } + +String _twoDigits(int n) { + if (n >= 10) return "$n"; +return "0$n"; +} \ No newline at end of file diff --git a/lib/src/utils/parse_utils_objects.dart b/lib/src/utils/parse_utils_objects.dart index 8d218e62b..2c59d29e0 100644 --- a/lib/src/utils/parse_utils_objects.dart +++ b/lib/src/utils/parse_utils_objects.dart @@ -1,9 +1,9 @@ part of flutter_parse_sdk; /// Populates the base object data from a server response -populateObjectBaseData(ParseObject object, Map objectData) { - object.set(ParseConstants.OBJECT_ID, objectData[ParseConstants.OBJECT_ID]); - object.set(ParseConstants.CREATED_AT, convertStringToDateTime(objectData[ParseConstants.CREATED_AT])); - object.set(ParseConstants.OBJECT_ID, convertStringToDateTime(objectData[ParseConstants.UPDATED_AT])); +populateObjectBaseData(ParseBase object, Map objectData) { + object.set(OBJECT_ID, objectData[OBJECT_ID]); + object.set(CREATED_AT, stringToDateTime(objectData[CREATED_AT])); + object.set(OBJECT_ID, stringToDateTime(objectData[UPDATED_AT])); return object; } diff --git a/pubspec.yaml b/pubspec.yaml index add1ec577..9c7ef5f04 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: parse_server_sdk -description: This is a Flutter plugin that allows communication with a Parse Server, (https://parseplatform.org) -version: 1.0.2 +description: Flutter plugin for Parse Server, (https://parseplatform.org), (https://back4app.com) +version: 1.0.3 homepage: https://github.com/phillwiggins/flutter_parse_sdk author: PhillWiggins @@ -16,4 +16,4 @@ dependencies: http: ^0.12.0 # Utils - intl: ^0.15.7 \ No newline at end of file + shared_preferences: ^0.4.3 \ No newline at end of file