diff --git a/.gitignore b/.gitignore index e8ac88703..c4f26e041 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ build/ .idea example/ios/Frameworks/ example/lib/ui/ + +.vscode/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 37975d799..eef1ece34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.0.22 + ## 1.0.21 LiveQuery fix Logout fix diff --git a/README.md b/README.md index 7643287bf..e199c25ae 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 ```yml dependencies: - parse_server_sdk: ^1.0.21 + parse_server_sdk: ^1.0.22 ``` 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. @@ -537,6 +537,21 @@ final Map params = {'plan': 'paid'}; function.execute(parameters: params); ``` +## Relation +The SDK supports Relation. + +To Retrive a relation instance for user, call: +```dart +final relation = user.getRelation('dietPlans'); +``` + +and then you can add a relation to the passed in object. + +```dart +relation.add(dietPlan); +final result = await user.save(); +``` + ## Other Features of this library Main: * Installation (View the example application) diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index d815bf063..ca2c16c33 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -44,9 +44,11 @@ 24DF2572E6AEEB9F7CE180C9 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 5804EFBD11740E02FC51BC3E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 96499D95196B10F296043703 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; diff --git a/example/lib/data/repositories/user/provider_db_user.dart b/example/lib/data/repositories/user/provider_db_user.dart index d135418c8..a7e5380e9 100644 --- a/example/lib/data/repositories/user/provider_db_user.dart +++ b/example/lib/data/repositories/user/provider_db_user.dart @@ -79,6 +79,7 @@ class UserProviderDB implements UserProviderContract { Map convertItemToStorageMap(User item) { final Map values = Map(); + // ignore: invalid_use_of_protected_member values['value'] = json.jsonEncode(item.toJson(full: true)); values[keyVarObjectId] = item.objectId; item.updatedAt != null @@ -90,8 +91,7 @@ class UserProviderDB implements UserProviderContract { User convertRecordToItem({Record record, Map values}) { try { values ??= record.value; - final User item = - User.clone().fromJson(json.jsonDecode(values['value'])); + final User item = User.clone().fromJson(json.jsonDecode(values['value'])); return item; } catch (e) { return null; diff --git a/example/lib/main.dart b/example/lib/main.dart index 3e26e8e18..d18963a20 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -10,7 +10,6 @@ import 'package:flutter_plugin_example/data/repositories/diet_plan/repository_di import 'package:flutter_plugin_example/data/repositories/user/repository_user.dart'; import 'package:flutter_plugin_example/domain/constants/application_constants.dart'; import 'package:flutter_plugin_example/domain/utils/db_utils.dart'; -import 'package:flutter_plugin_example/pages/decision_page.dart'; import 'package:flutter_stetho/flutter_stetho.dart'; import 'package:parse_server_sdk/parse_server_sdk.dart'; @@ -41,69 +40,74 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { DietPlanRepository dietPlanRepo; UserRepository userRepo; + + String text = ''; + @override void initState() { super.initState(); - // initData(); + initData(); } @override Widget build(BuildContext context) { return MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData( - primarySwatch: Colors.blue, + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: Center( + child: Text(text), ), - title: 'Parse Server Example', - home: DecisionPage()); + ), + ); } Future initData() async { // Initialize repository - // await initRepository(); + await initRepository(); // Initialize parse Parse().initialize(keyParseApplicationId, keyParseServerUrl, masterKey: keyParseMasterKey, debug: true); - //parse serve with secure store and desktop support - -// Parse().initialize(keyParseApplicationId, keyParseServerUrl, -// masterKey: keyParseMasterKey, -// debug: true, -// coreStore: CoreStoreImp.getInstance()); - // Check server is healthy and live - Debug is on in this instance so check logs for result final ParseResponse response = await Parse().healthCheck(); if (response.success) { await runTestQueries(); - print('runTestQueries'); + text += 'runTestQueries\n'; } else { + text += 'Server health check failed'; print('Server health check failed'); } } Future runTestQueries() async { // Basic repository example - //await repositoryAddUser(); - //await repositoryAddItems(); - //await repositoryGetAllItems(); + await repositoryAddUser(); + await repositoryAddItems(); + await repositoryGetAllItems(); //Basic usage - // createItem(); - // getAllItems(); - // getAllItemsByName(); - // getSingleItem(); - // getConfigs(); - // query(); - // initUser(); -// var instalattion = await ParseInstallation.currentInstallation(); -// var rees = instalattion.create(); -// print(rees); - //function(); - //functionWithParameters(); - // test(); + await createItem(); + await getAllItems(); + await getAllItemsByName(); + await getSingleItem(); + await getConfigs(); + await query(); + await initUser(); + await initInstallation(); + await function(); + await functionWithParameters(); + await test(); + } + + Future initInstallation() async { + final ParseInstallation installation = + await ParseInstallation.currentInstallation(); + final ParseResponse response = await installation.create(); + print(response); } Future test() async { @@ -359,13 +363,13 @@ class _MyAppState extends State { dietPlanRepo ??= DietPlanRepository.init(await getDB()); userRepo ??= UserRepository.init(await getDB()); } - - String dietPlansToAdd = - '[{"className":"Diet_Plans","Name":"Textbook","Description":"For an active lifestyle and a straight forward macro plan, we suggest this plan.","Fat":25,"Carbs":50,"Protein":25,"Status":0},' - '{"className":"Diet_Plans","Name":"Body Builder","Description":"Default Body Builders Diet","Fat":20,"Carbs":40,"Protein":40,"Status":0},' - '{"className":"Diet_Plans","Name":"Zone Diet","Description":"Popular with CrossFit users. Zone Diet targets similar macros.","Fat":30,"Carbs":40,"Protein":30,"Status":0},' - '{"className":"Diet_Plans","Name":"Low Fat","Description":"Low fat diet.","Fat":15,"Carbs":60,"Protein":25,"Status":0},' - '{"className":"Diet_Plans","Name":"Low Carb","Description":"Low Carb diet, main focus on quality fats and protein.","Fat":35,"Carbs":25,"Protein":40,"Status":0},' - '{"className":"Diet_Plans","Name":"Paleo","Description":"Paleo diet.","Fat":60,"Carbs":25,"Protein":10,"Status":0},' - '{"className":"Diet_Plans","Name":"Ketogenic","Description":"High quality fats, low carbs.","Fat":65,"Carbs":5,"Protein":30,"Status":0}]'; } + +const String dietPlansToAdd = + '[{"className":"Diet_Plans","Name":"Textbook","Description":"For an active lifestyle and a straight forward macro plan, we suggest this plan.","Fat":25,"Carbs":50,"Protein":25,"Status":0},' + '{"className":"Diet_Plans","Name":"Body Builder","Description":"Default Body Builders Diet","Fat":20,"Carbs":40,"Protein":40,"Status":0},' + '{"className":"Diet_Plans","Name":"Zone Diet","Description":"Popular with CrossFit users. Zone Diet targets similar macros.","Fat":30,"Carbs":40,"Protein":30,"Status":0},' + '{"className":"Diet_Plans","Name":"Low Fat","Description":"Low fat diet.","Fat":15,"Carbs":60,"Protein":25,"Status":0},' + '{"className":"Diet_Plans","Name":"Low Carb","Description":"Low Carb diet, main focus on quality fats and protein.","Fat":35,"Carbs":25,"Protein":40,"Status":0},' + '{"className":"Diet_Plans","Name":"Paleo","Description":"Paleo diet.","Fat":60,"Carbs":25,"Protein":10,"Status":0},' + '{"className":"Diet_Plans","Name":"Ketogenic","Description":"High quality fats, low carbs.","Fat":65,"Carbs":5,"Protein":30,"Status":0}]'; diff --git a/example/windows/find_vcvars.dart b/example/windows/find_vcvars.dart index 375651e0a..bbbe33adb 100644 --- a/example/windows/find_vcvars.dart +++ b/example/windows/find_vcvars.dart @@ -17,14 +17,15 @@ import 'dart:io'; int main() { - final programDir = Platform.environment['PROGRAMFILES(X86)']; - final pathPrefix = '$programDir\\Microsoft Visual Studio'; - const pathSuffix = 'VC\\Auxiliary\\Build\\vcvars64.bat'; - final years = ['2017', '2019']; - final flavors = ['Community', 'Professional', 'Enterprise', 'Preview']; - for (final year in years) { - for (final flavor in flavors) { - final testPath = '$pathPrefix\\$year\\$flavor\\$pathSuffix'; + final String programDir = Platform.environment['PROGRAMFILES(X86)']; + final String pathPrefix = '$programDir\\Microsoft Visual Studio'; + const String pathSuffix = 'VC\\Auxiliary\\Build\\vcvars64.bat'; + final List years = ['2017', '2019']; + final List flavors = [ + 'Community', 'Professional', 'Enterprise', 'Preview']; + for (final String year in years) { + for (final String flavor in flavors) { + final String testPath = '$pathPrefix\\$year\\$flavor\\$pathSuffix'; if (File(testPath).existsSync()) { print(testPath); return 0; diff --git a/example/windows/generate_props.dart b/example/windows/generate_props.dart index 5a918942e..d895b0298 100644 --- a/example/windows/generate_props.dart +++ b/example/windows/generate_props.dart @@ -18,8 +18,8 @@ import 'dart:io'; void main(List arguments) { - final outputPath = arguments[0]; - final settings = { + final String outputPath = arguments[0]; + final Map settings = { 'FLUTTER_ROOT': arguments[1], 'EXTRA_BUNDLE_FLAGS': arguments[2], }; @@ -39,16 +39,16 @@ ${getItemGroupContent(settings)} } String getUserMacrosContent(Map settings) { - final macroList = StringBuffer(); - for (final setting in settings.entries) { + final StringBuffer macroList = StringBuffer(); + for (final MapEntry setting in settings.entries) { macroList.writeln(' <${setting.key}>${setting.value}'); } return macroList.toString(); } String getItemGroupContent(Map settings) { - final macroList = StringBuffer(); - for (final name in settings.keys) { + final StringBuffer macroList = StringBuffer(); + for (final String name in settings.keys) { macroList.writeln(''' \$($name) true diff --git a/lib/generated/i18n.dart b/lib/generated/i18n.dart index db983ef03..11205df28 100644 --- a/lib/generated/i18n.dart +++ b/lib/generated/i18n.dart @@ -1,4 +1,3 @@ - import 'dart:async'; import 'package:flutter/foundation.dart'; @@ -13,35 +12,32 @@ class S implements WidgetsLocalizations { const S(); static const GeneratedLocalizationsDelegate delegate = - const GeneratedLocalizationsDelegate(); + GeneratedLocalizationsDelegate(); static S of(BuildContext context) => Localizations.of(context, WidgetsLocalizations); @override TextDirection get textDirection => TextDirection.ltr; - } class en extends S { const en(); } - -class GeneratedLocalizationsDelegate extends LocalizationsDelegate { +class GeneratedLocalizationsDelegate + extends LocalizationsDelegate { const GeneratedLocalizationsDelegate(); List get supportedLocales { return const [ - - const Locale("en", ""), - + Locale("en", ""), ]; } LocaleResolutionCallback resolution({Locale fallback}) { return (Locale locale, Iterable supported) { - final Locale languageLocale = new Locale(locale.languageCode, ""); + final Locale languageLocale = Locale(locale.languageCode, ""); if (supported.contains(locale)) return locale; else if (supported.contains(languageLocale)) @@ -57,12 +53,11 @@ class GeneratedLocalizationsDelegate extends LocalizationsDelegate load(Locale locale) { final String lang = getLang(locale); switch (lang) { - case "en": - return new SynchronousFuture(const en()); + return SynchronousFuture(const en()); default: - return new SynchronousFuture(const S()); + return SynchronousFuture(const S()); } } diff --git a/lib/parse_server_sdk.dart b/lib/parse_server_sdk.dart index 865958e86..b52dbe5b5 100644 --- a/lib/parse_server_sdk.dart +++ b/lib/parse_server_sdk.dart @@ -4,7 +4,8 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; - +import 'package:sembast/sembast.dart'; +import 'package:sembast/sembast_io.dart'; import 'package:devicelocale/devicelocale.dart'; import 'package:http/http.dart'; import 'package:http/io_client.dart'; @@ -12,13 +13,11 @@ import 'package:meta/meta.dart'; import 'package:package_info/package_info.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; -import 'package:sembast/sembast.dart'; -import 'package:sembast/sembast_io.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:uuid/uuid.dart'; import 'package:web_socket_channel/io.dart'; import 'package:xxtea/xxtea.dart'; +part 'package:parse_server_sdk/src/objects/response/parse_response_utils.dart'; part 'package:parse_server_sdk/src/objects/response/parse_error_response.dart'; part 'package:parse_server_sdk/src/objects/response/parse_exception_response.dart'; part 'package:parse_server_sdk/src/objects/response/parse_response_builder.dart'; @@ -44,6 +43,7 @@ part 'src/objects/parse_function.dart'; part 'src/objects/parse_geo_point.dart'; part 'src/objects/parse_installation.dart'; part 'src/objects/parse_object.dart'; +part 'src/objects/parse_relation.dart'; part 'src/objects/parse_response.dart'; part 'src/objects/parse_session.dart'; part 'src/objects/parse_user.dart'; @@ -54,6 +54,12 @@ part 'src/utils/parse_file_extensions.dart'; part 'src/utils/parse_logger.dart'; part 'src/utils/parse_utils.dart'; +part 'src/utils/parse_date_format.dart'; + +part 'src/data/core_store.dart'; +part 'src/data/core_store_impl.dart'; +part 'src/data/xxtea_codec.dart'; + class Parse { ParseCoreData data; bool _hasBeenInitialized = false; @@ -79,7 +85,7 @@ class Parse { String sessionId, bool autoSendSessionId, SecurityContext securityContext, - FutureOr coreStore}) { + CoreStore coreStore}) { final String url = removeTrailingSlash(serverUrl); ParseCoreData.init(appId, url, @@ -101,14 +107,15 @@ class Parse { bool hasParseBeenInitialized() => _hasBeenInitialized; Future healthCheck( - {bool debug, ParseHTTPClient client, bool autoSendSessionId}) async { + {bool debug, ParseHTTPClient client, bool sendSessionIdByDefault}) async { ParseResponse parseResponse; final bool _debug = isDebugEnabled(objectLevelDebug: debug); + final ParseHTTPClient _client = client ?? ParseHTTPClient( sendSessionId: - autoSendSessionId ?? ParseCoreData().autoSendSessionId, + sendSessionIdByDefault ?? ParseCoreData().autoSendSessionId, securityContext: ParseCoreData().securityContext); const String className = 'parseBase'; @@ -117,7 +124,6 @@ class Parse { try { final Response response = await _client.get('${ParseCoreData().serverUrl}$keyEndPointHealth'); - parseResponse = handleResponse(null, response, type, _debug, className); } on Exception catch (e) { diff --git a/lib/src/base/parse_constants.dart b/lib/src/base/parse_constants.dart index 2bf12e226..2f61a8c05 100644 --- a/lib/src/base/parse_constants.dart +++ b/lib/src/base/parse_constants.dart @@ -1,7 +1,7 @@ part of flutter_parse_sdk; // Library -const String keySdkVersion = '1.0.21'; +const String keySdkVersion = '1.0.22'; const String keyLibraryName = 'Flutter Parse SDK'; // End Points @@ -36,6 +36,7 @@ const String keyClassSession = '_Session'; const String keyClassInstallation = '_Installation'; const String keyGeoPoint = 'GeoPoint'; const String keyFile = 'File'; +const String keyRelation = 'Relation'; // Headers const String keyHeaderSessionToken = 'X-Parse-Session-Token'; diff --git a/lib/src/data/core_store.dart b/lib/src/data/core_store.dart index 13a1c6fd5..b7d2fe450 100644 --- a/lib/src/data/core_store.dart +++ b/lib/src/data/core_store.dart @@ -15,17 +15,17 @@ abstract class CoreStore { Future> getStringList(String key); - Future setBool(String key, bool value); + Future setBool(String key, bool value); - Future setInt(String key, int value); + Future setInt(String key, int value); - Future setDouble(String key, double value); + Future setDouble(String key, double value); - Future setString(String key, String value); + Future setString(String key, String value); - Future setStringList(String key, List values); + Future setStringList(String key, List values); - Future remove(String key); + Future remove(String key); - Future clear(); + Future clear(); } diff --git a/lib/src/data/core_store_impl.dart b/lib/src/data/core_store_impl.dart index 3a65c7eb4..8448ac8cd 100644 --- a/lib/src/data/core_store_impl.dart +++ b/lib/src/data/core_store_impl.dart @@ -1,20 +1,27 @@ part of flutter_parse_sdk; class CoreStoreImp implements CoreStore { + CoreStoreImp._internal(this._store); + static CoreStoreImp _instance; + static Future getInstance( {DatabaseFactory factory, String password = 'flutter_sdk'}) async { + if (_instance == null) { factory ??= databaseFactoryIo; final SembastCodec codec = getXXTeaSembastCodec(password: password); String dbDirectory = ''; - if (Platform.isIOS || Platform.isAndroid) + + if (Platform.isIOS || Platform.isAndroid) { dbDirectory = (await getApplicationDocumentsDirectory()).path; - final String dbPath = path.join('$dbDirectory+/parse', 'parse.db'); - final Database db = await factory.openDatabase(dbPath, codec: codec); - _instance = CoreStoreImp._internal(db); + final String dbPath = path.join('$dbDirectory+/parse', 'parse.db'); + final Database db = await factory.openDatabase(dbPath, codec: codec); + _instance = CoreStoreImp._internal(db); + } } + return _instance; } @@ -31,62 +38,67 @@ class CoreStoreImp implements CoreStore { } @override - Future get(String key) { + Future get(String key) { return _store.get(key); } @override Future getBool(String key) async { - return await _store.get(key) as bool; + final bool storedValue = await _store.get(key); + return storedValue; } @override Future getDouble(String key) async { - return await _store.get(key) as double; + final double storedValue = await _store.get(key); + return storedValue; } @override Future getInt(String key) async { - return await _store.get(key) as int; + final int storedValue = await _store.get(key); + return storedValue; } @override Future getString(String key) async { - return await _store.get(key) as String; + final String storedValue = await _store.get(key); + return storedValue; } @override Future> getStringList(String key) async { - return await _store.get(key) as List; + final List storedValue = await _store.get(key); + return storedValue; } @override - Future remove(String key) { + Future remove(String key) { return _store.delete(key); } @override - Future setBool(String key, bool value) { + Future setBool(String key, bool value) { return _store.put(value, key); } @override - Future setDouble(String key, double value) { + Future setDouble(String key, double value) { return _store.put(value, key); } @override - Future setInt(String key, int value) { + Future setInt(String key, int value) { return _store.put(value, key); } @override - Future setString(String key, String value) { + Future setString(String key, String value) { return _store.put(value, key); } @override - Future setStringList(String key, List values) { + Future setStringList(String key, List values) { return _store.put(values, key); } } diff --git a/lib/src/data/parse_core_data.dart b/lib/src/data/parse_core_data.dart index b0e98593f..7b84fb07d 100644 --- a/lib/src/data/parse_core_data.dart +++ b/lib/src/data/parse_core_data.dart @@ -23,20 +23,36 @@ class ParseCoreData { String sessionId, bool autoSendSessionId, SecurityContext securityContext, - FutureOr store}) { + CoreStore store}) { _instance = ParseCoreData._init(appId, serverUrl); - _instance.storage ??= store ?? - Future.value( - SharedPreferencesCoreStore(SharedPreferences.getInstance())); - if (debug != null) _instance.debug = debug; - if (appName != null) _instance.appName = appName; - if (liveQueryUrl != null) _instance.liveQueryURL = liveQueryUrl; - if (clientKey != null) _instance.clientKey = clientKey; - if (masterKey != null) _instance.masterKey = masterKey; - if (sessionId != null) _instance.sessionId = sessionId; - if (autoSendSessionId != null) + + _instance.storage ??= + store ?? CoreStoreImp.getInstance(password: masterKey); + + if (debug != null) { + _instance.debug = debug; + } + if (appName != null) { + _instance.appName = appName; + } + if (liveQueryUrl != null) { + _instance.liveQueryURL = liveQueryUrl; + } + if (clientKey != null) { + _instance.clientKey = clientKey; + } + if (masterKey != null) { + _instance.masterKey = masterKey; + } + if (sessionId != null) { + _instance.sessionId = sessionId; + } + if (autoSendSessionId != null) { _instance.autoSendSessionId = autoSendSessionId; - if (securityContext != null) _instance.securityContext = securityContext; + } + if (securityContext != null) { + _instance.securityContext = securityContext; + } } String appName; @@ -49,7 +65,7 @@ class ParseCoreData { bool autoSendSessionId; SecurityContext securityContext; bool debug; - FutureOr storage; + Future storage; /// Sets the current sessionId. /// diff --git a/lib/src/network/parse_live_query.dart b/lib/src/network/parse_live_query.dart index 066920f8b..6dd0b3a76 100644 --- a/lib/src/network/parse_live_query.dart +++ b/lib/src/network/parse_live_query.dart @@ -22,9 +22,9 @@ class LiveQuery { Map _connectMessage; Map _subscribeMessage; Map _unsubscribeMessage; - Map eventCallbacks = {}; + Map eventCallbacks = {}; int _requestIdCount = 1; - final List _liveQueryEvent = [ + final List _liveQueryEvent = [ 'create', 'enter', 'update', @@ -38,7 +38,9 @@ class LiveQuery { return _requestIdCount++; } - Future subscribe(QueryBuilder query) async { + // ignore: always_specify_types + Future subscribe(QueryBuilder query) async { + String _liveQueryURL = _client.data.liveQueryURL; if (_liveQueryURL.contains('https')) { _liveQueryURL = _liveQueryURL.replaceAll('https', 'wss'); @@ -47,8 +49,9 @@ class LiveQuery { } final String _className = query.object.className; - query.limiters.clear(); //Remove limites in LiveQuery + query.limiters.clear(); //Remove limits in LiveQuery final String _where = query._buildQuery().replaceAll('where=', ''); + //Convert where condition to Map Map _whereMap = Map(); if (_where != '') { @@ -67,14 +70,14 @@ class LiveQuery { } else { if (_debug) { print('$_printConstLiveQuery: Error when connection client'); - return Future.value(null); + return Future.value(null); } } _channel = IOWebSocketChannel(_webSocket); _channel.stream.listen((dynamic message) { if (_debug) { - print('$_printConstLiveQuery: Listen: ${message}'); + print('$_printConstLiveQuery: Listen: $message'); } final Map actionData = jsonDecode(message); @@ -103,7 +106,7 @@ class LiveQuery { print( '$_printConstLiveQuery: Error: ${error.runtimeType.toString()}'); } - return Future.value(handleException( + return Future.value(handleException( Exception(error), ParseApiRQ.liveQuery, _debug, _className)); }); @@ -128,7 +131,7 @@ class LiveQuery { _subscribeMessage = { 'op': 'subscribe', 'requestId': requestId, - 'query': { + 'query': { 'className': _className, 'where': _whereMap, } @@ -167,7 +170,7 @@ class LiveQuery { print( '$_printConstLiveQuery: UnsubscribeMessage: $_unsubscribeMessage'); } - await _channel.sink.add(jsonEncode(_unsubscribeMessage)); + _channel.sink.add(jsonEncode(_unsubscribeMessage)); await _channel.sink.close(); } } diff --git a/lib/src/network/parse_query.dart b/lib/src/network/parse_query.dart index d32a5c1ea..f56bb47ea 100644 --- a/lib/src/network/parse_query.dart +++ b/lib/src/network/parse_query.dart @@ -238,16 +238,18 @@ class QueryBuilder { } // Add a constraint to the query that requires a particular key's value match another QueryBuilder + // ignore: always_specify_types void whereMatchesQuery(String column, QueryBuilder query) { - String inQuery = query._buildQueryRelational(query.object.className); + final 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 + // ignore: always_specify_types void whereDoesNotMatchQuery(String column, QueryBuilder query) { - String inQuery = query._buildQueryRelational(query.object.className); + final String inQuery = query._buildQueryRelational(query.object.className); queries.add(MapEntry( _SINGLE_QUERY, '\"$column\":{\"\$notInQuery\":$inQuery}')); @@ -305,8 +307,7 @@ class QueryBuilder { if (item == queries.first) { queryBuilder += item; } else { - // ignore: prefer_single_quotes - queryBuilder += ",$item"; + queryBuilder += ',$item'; } } @@ -316,7 +317,7 @@ class QueryBuilder { /// Creates a query param using the column, the value and the queryOperator /// that the column and value are being queried against MapEntry _buildQueryWithColumnValueAndOperator( - MapEntry columnAndValue, String queryOperator) { + MapEntry columnAndValue, String queryOperator) { final String key = columnAndValue.key; final dynamic value = convertValueToCorrectType(parseEncode(columnAndValue.value)); @@ -370,10 +371,10 @@ class QueryBuilder { // Compact all the queries in the correct format for (MapEntry queryToCompact in listOfQueriesCompact) { - var queryToCompactValue = queryToCompact.value.toString(); - queryToCompactValue = queryToCompactValue.replaceFirst("{", ""); + String queryToCompactValue = queryToCompact.value.toString(); + queryToCompactValue = queryToCompactValue.replaceFirst('{', ''); queryToCompactValue = queryToCompactValue.replaceRange( - queryToCompactValue.length - 1, queryToCompactValue.length, ""); + queryToCompactValue.length - 1, queryToCompactValue.length, ''); if (listOfQueriesCompact.first == queryToCompact) { queryEnd += queryToCompactValue.replaceAll(queryStart, ' '); } else { diff --git a/lib/src/objects/parse_acl.dart b/lib/src/objects/parse_acl.dart index 2d3ff2e5e..7919343ab 100644 --- a/lib/src/objects/parse_acl.dart +++ b/lib/src/objects/parse_acl.dart @@ -1,7 +1,7 @@ part of flutter_parse_sdk; /// [ParseACL] is used to control which users can access or modify a particular object -/// [ParseObject] can have its own [ParceACL] +/// [ParseObject] can have its own [ParseACL] /// You can grant read and write permissions separately to specific users /// or you can grant permissions to "the public" so that, for example, any user could read a particular object but /// only a particular set of users could write to that object @@ -16,7 +16,8 @@ class ParseACL { } final String _publicKEY = '*'; - final Map _permissionsById = {}; + final Map _permissionsById = + {}; /// Helper for setting stuff void _setPermissionsIfNonEmpty( @@ -125,8 +126,8 @@ class _ACLPermissions { _ACLPermissions(this._readPermission, this._writePermission); final String _keyReadPermission = 'read'; final String _keyWritePermission = 'write'; - bool _readPermission = false; - bool _writePermission = false; + final bool _readPermission; + final bool _writePermission; bool getReadPermission() { return _readPermission; diff --git a/lib/src/objects/parse_error.dart b/lib/src/objects/parse_error.dart index eda1e30b9..39fd9ad19 100644 --- a/lib/src/objects/parse_error.dart +++ b/lib/src/objects/parse_error.dart @@ -2,7 +2,6 @@ part of flutter_parse_sdk; /// ParseException is used in [ParseResult] to inform the user of the exception class ParseError { - ParseError( {this.code = -1, this.message = 'Unknown error', diff --git a/lib/src/objects/parse_file.dart b/lib/src/objects/parse_file.dart index bd2a53909..ee93e3f40 100644 --- a/lib/src/objects/parse_file.dart +++ b/lib/src/objects/parse_file.dart @@ -93,9 +93,16 @@ class ParseFile extends ParseObject { Future upload() async { if (saved) { //Creates a Fake Response to return the correct result - final Map response = {'url': url, 'name': name}; - return handleResponse(this, Response(json.encode(response), 201), - ParseApiRQ.upload, _debug, className); + final Map response = { + 'url': url, + 'name': name + }; + return handleResponse( + this, + Response(json.encode(response), 201), + ParseApiRQ.upload, + _debug, + className); } final String ext = path.extension(file.path).replaceAll('.', ''); diff --git a/lib/src/objects/parse_function.dart b/lib/src/objects/parse_function.dart index 16bcf12e6..8f80d6a3d 100644 --- a/lib/src/objects/parse_function.dart +++ b/lib/src/objects/parse_function.dart @@ -34,8 +34,9 @@ class ParseCloudFunction extends ParseObject { } final Response result = - await _client.post(uri, body: json.encode(getObjectData())); - return handleResponse(this, result, ParseApiRQ.execute, _debug, className); + await _client.post(uri, body: json.encode(getObjectData())); + return handleResponse( + this, result, ParseApiRQ.execute, _debug, className); } /// Executes a cloud function that returns a ParseObject type diff --git a/lib/src/objects/parse_geo_point.dart b/lib/src/objects/parse_geo_point.dart index f6c16168b..61c52e894 100644 --- a/lib/src/objects/parse_geo_point.dart +++ b/lib/src/objects/parse_geo_point.dart @@ -4,7 +4,6 @@ const String keyLatitude = 'latitude'; const String keyLongitude = 'longitude'; class ParseGeoPoint extends ParseObject { - /// Creates a Parse Object of type GeoPoint ParseGeoPoint( {double latitude = 0.0, @@ -13,7 +12,6 @@ class ParseGeoPoint extends ParseObject { ParseHTTPClient client, bool autoSendSessionId}) : super(keyGeoPoint) { - this.latitude = latitude; this.longitude = longitude; @@ -32,7 +30,8 @@ class ParseGeoPoint extends ParseObject { set longitude(double longitude) => set(keyLongitude, longitude); @override - Map toJson({bool full = false, bool forApiRQ = false}) => { + Map toJson({bool full = false, bool forApiRQ = false}) => + { '__type': 'GeoPoint', 'latitude': latitude, 'longitude': longitude diff --git a/lib/src/objects/parse_installation.dart b/lib/src/objects/parse_installation.dart index 008ed79f6..a0ea8350b 100644 --- a/lib/src/objects/parse_installation.dart +++ b/lib/src/objects/parse_installation.dart @@ -16,14 +16,17 @@ class ParseInstallation extends ParseObject { ParseInstallation.forQuery() : super(keyClassUser); static final List readOnlyKeys = [ - // TODO - keyDeviceToken, keyDeviceType, keyInstallationId, - keyAppName, keyAppVersion, keyAppIdentifier, keyParseVersion + keyDeviceToken, + keyDeviceType, + keyInstallationId, + keyAppName, + keyAppVersion, + keyAppIdentifier, + keyParseVersion ]; static String _currentInstallationId; //Getters/setters - Map get acl => super.get>(keyVarAcl); set acl(Map acl) => @@ -65,15 +68,17 @@ class ParseInstallation extends ParseObject { /// Updates the installation with current device data Future _updateInstallation() async { //Device type - if (Platform.isAndroid) + if (Platform.isAndroid) { set(keyDeviceType, 'android'); - else if (Platform.isIOS) + } else if (Platform.isIOS) { set(keyDeviceType, 'ios'); - else if (Platform.isLinux) + } else if (Platform.isLinux) { set(keyDeviceType, 'Linux'); - else if (Platform.isMacOS) + } else if (Platform.isMacOS) { set(keyDeviceType, 'MacOS'); - else if (Platform.isWindows) set(keyDeviceType, 'Windows'); + } else if (Platform.isWindows) { + set(keyDeviceType, 'Windows'); + } //Locale final String locale = await Devicelocale.currentLocale; @@ -82,7 +87,6 @@ class ParseInstallation extends ParseObject { } //Timezone - //TODO set(keyTimeZone, ); //App info final PackageInfo packageInfo = await PackageInfo.fromPlatform(); @@ -95,8 +99,10 @@ class ParseInstallation extends ParseObject { @override Future create() async { final bool isCurrent = await ParseInstallation.isCurrent(this); - if (isCurrent) await _updateInstallation(); - //ParseResponse parseResponse = await super.create(); + if (isCurrent) { + await _updateInstallation(); + } + final ParseResponse parseResponse = await _create(); if (parseResponse.success && isCurrent) { saveInStorage(keyParseStoreInstallation); @@ -143,6 +149,7 @@ class ParseInstallation extends ParseObject { /// so it creates and sets the static current installation UUID static Future _createInstallation() async { _currentInstallationId ??= Uuid().v4(); + final ParseInstallation installation = ParseInstallation(); installation._installationId = _currentInstallationId; await installation._updateInstallation(); @@ -199,13 +206,15 @@ class ParseInstallation extends ParseObject { ///Subscribes the device to a channel of push notifications. void subscribeToChannel(String value) { final List channel = [value]; - addUnique('channels', channel); + setAddUnique('channels', channel); + save(); } ///Unsubscribes the device to a channel of push notifications. void unsubscribeFromChannel(String value) { final List channel = [value]; - removeAll('channels', channel); + setRemove('channels', channel); + save(); } ///Returns an > containing all the channel names this device is subscribed to. @@ -216,7 +225,8 @@ class ParseInstallation extends ParseObject { if (apiResponse.success) { final ParseObject installation = apiResponse.result; - return Future.value(installation.get>('channels')); + return Future>.value( + installation.get>('channels')); } else { return null; } diff --git a/lib/src/objects/parse_object.dart b/lib/src/objects/parse_object.dart index 6b474fb9a..404e28643 100644 --- a/lib/src/objects/parse_object.dart +++ b/lib/src/objects/parse_object.dart @@ -74,7 +74,7 @@ class ParseObject extends ParseBase implements ParseCloneable { final Map map = json.decode(result.body); objectId = map['objectId'].toString(); } - + return handleResponse( this, result, ParseApiRQ.create, _debug, className); } on Exception catch (e) { @@ -99,6 +99,11 @@ class ParseObject extends ParseBase implements ParseCloneable { } } + /// Get the instance of ParseRelation class associated with the given key. + ParseRelation getRelation(String key) { + return ParseRelation(parent: this, key: key); + } + /// Removes an element from an Array @Deprecated('Prefer to use the setRemove() method in save()') Future remove(String key, dynamic values) async { @@ -174,6 +179,14 @@ class ParseObject extends ParseBase implements ParseCloneable { _arrayOperation('Add', key, values); } + void addRelation(String key, List values) { + _arrayOperation('AddRelation', key, values); + } + + void removeRelation(String key, List values) { + _arrayOperation('RemoveRelation', key, values); + } + /// Can be used to add arrays to a given type Future _sortArrays(ParseApiRQ apiRQType, String arrayAction, String key, List values) async { diff --git a/lib/src/objects/parse_relation.dart b/lib/src/objects/parse_relation.dart new file mode 100644 index 000000000..bd8ca8b95 --- /dev/null +++ b/lib/src/objects/parse_relation.dart @@ -0,0 +1,44 @@ +part of flutter_parse_sdk; + +class ParseRelation { + ParseRelation({ParseObject parent, String key}) { + _parent = parent; + _key = key; + } + + String _targetClass; + ParseObject _parent; + String _key; + Set _objects = Set(); + + QueryBuilder getQuery() { + return QueryBuilder(ParseObject(_targetClass)); + } + + void add(T object) { + if (object != null) { + _targetClass = object.getClassName(); + _objects.add(object); + _parent.addRelation(_key, _objects.map((T value) { + return value.toPointer(); + }).toList()); + } + } + + void remove(T object) { + if (object != null) { + _targetClass = object.getClassName(); + _objects.remove(object); + _parent.removeRelation(_key, _objects.map((T value) { + return value.toPointer(); + }).toList()); + } + } + + Map toJson() => + {'__type': keyRelation, 'className': _objects?.first?.className, 'objects': parseEncode(_objects?.toList())}; + + ParseRelation fromJson(Map map) => ParseRelation() + .._objects = parseDecode(map['objects']) + .._targetClass = map['className']; +} \ No newline at end of file diff --git a/lib/src/objects/response/parse_error_response.dart b/lib/src/objects/response/parse_error_response.dart index b90196166..f7a50a623 100644 --- a/lib/src/objects/response/parse_error_response.dart +++ b/lib/src/objects/response/parse_error_response.dart @@ -2,13 +2,13 @@ part of flutter_parse_sdk; /// Handles any errors returned in response ParseResponse buildErrorResponse(ParseResponse response, Response apiResponse) { - if (apiResponse.body == null) { return null; } final Map responseData = json.decode(apiResponse.body); - response.error = ParseError(code: responseData[keyCode], message: responseData[keyError].toString()); + response.error = ParseError( + code: responseData[keyCode], message: responseData[keyError].toString()); response.statusCode = responseData[keyCode]; return response; } diff --git a/lib/src/objects/response/parse_exception_response.dart b/lib/src/objects/response/parse_exception_response.dart index e8b3f4367..9a798c8b0 100644 --- a/lib/src/objects/response/parse_exception_response.dart +++ b/lib/src/objects/response/parse_exception_response.dart @@ -3,6 +3,7 @@ part of flutter_parse_sdk; /// Handles exception instead of throwing an exception ParseResponse buildParseResponseWithException(Exception exception) { final ParseResponse response = ParseResponse(); - response.error = ParseError(message: exception.toString(), isTypeOfException: true); + response.error = + ParseError(message: exception.toString(), isTypeOfException: true); return response; } diff --git a/lib/src/objects/response/parse_response_builder.dart b/lib/src/objects/response/parse_response_builder.dart index 76b77d6c8..a28e51c01 100644 --- a/lib/src/objects/response/parse_response_builder.dart +++ b/lib/src/objects/response/parse_response_builder.dart @@ -75,7 +75,7 @@ class _ParseResponseBuilder { response.result = items; response.count = items.length; } else if (map != null && map.length == 2 && map.containsKey('count')) { - final List results = [map['count']]; + final List results = [map['count']]; response.results = results; response.result = results; response.count = map['count']; @@ -113,6 +113,6 @@ class _ParseResponseBuilder { } bool isHealthCheck(Response apiResponse) { - return apiResponse.body == "{\"status\":\"ok\"}"; + return apiResponse.body == '{\"status\":\"ok\"}'; } } diff --git a/lib/src/objects/response/parse_response_utils.dart b/lib/src/objects/response/parse_response_utils.dart index 0fff98d21..12a96bc06 100644 --- a/lib/src/objects/response/parse_response_utils.dart +++ b/lib/src/objects/response/parse_response_utils.dart @@ -4,10 +4,8 @@ part of flutter_parse_sdk; @protected ParseResponse handleResponse(ParseCloneable object, Response response, ParseApiRQ type, bool debug, String className) { - final ParseResponse parseResponse = _ParseResponseBuilder().handleResponse( - object, - response, + object, response, returnAsResult: shouldReturnAsABaseResult(type)); if (debug) { @@ -21,7 +19,6 @@ ParseResponse handleResponse(ParseCloneable object, Response response, @protected ParseResponse handleException( Exception exception, ParseApiRQ type, bool debug, String className) { - final ParseResponse parseResponse = buildParseResponseWithException(exception); @@ -50,7 +47,8 @@ bool shouldReturnAsABaseResult(ParseApiRQ type) { } } -bool isUnsuccessfulResponse(Response apiResponse) => apiResponse.statusCode != 200 && apiResponse.statusCode != 201; +bool isUnsuccessfulResponse(Response apiResponse) => + apiResponse.statusCode != 200 && apiResponse.statusCode != 201; bool isSuccessButNoResults(Response apiResponse) { final Map decodedResponse = jsonDecode(apiResponse.body); @@ -62,4 +60,3 @@ bool isSuccessButNoResults(Response apiResponse) { return results.isEmpty; } - diff --git a/lib/src/objects/response/parse_success_no_results.dart b/lib/src/objects/response/parse_success_no_results.dart index 053dbadb4..e49eb989a 100644 --- a/lib/src/objects/response/parse_success_no_results.dart +++ b/lib/src/objects/response/parse_success_no_results.dart @@ -1,7 +1,8 @@ part of flutter_parse_sdk; /// Handles successful responses with no results -ParseResponse buildSuccessResponseWithNoResults(ParseResponse response, int code, String value) { +ParseResponse buildSuccessResponseWithNoResults(ParseResponse response, + int code, String value) { response.success = true; response.statusCode = 200; response.error = ParseError(code: code, message: value); diff --git a/lib/src/utils/parse_date_format.dart b/lib/src/utils/parse_date_format.dart index fc394d734..c025878d7 100644 --- a/lib/src/utils/parse_date_format.dart +++ b/lib/src/utils/parse_date_format.dart @@ -63,4 +63,4 @@ class _ParseDateFormat { } return '0$n'; } -} \ No newline at end of file +} diff --git a/lib/src/utils/parse_decoder.dart b/lib/src/utils/parse_decoder.dart index cff6fcb87..2ae0a4333 100644 --- a/lib/src/utils/parse_decoder.dart +++ b/lib/src/utils/parse_decoder.dart @@ -53,7 +53,7 @@ dynamic parseDecode(dynamic value) { switch (map['__type']) { case 'Date': final String iso = map['iso']; - return _parseDateFormat.parse(iso); + return _parseDateFormat.parse(iso); case 'Bytes': final String val = map['base64']; return base64.decode(val); @@ -77,6 +77,8 @@ dynamic parseDecode(dynamic value) { final num longitude = map['longitude'] ?? 0.0; return ParseGeoPoint( latitude: latitude.toDouble(), longitude: longitude.toDouble()); + case 'Relation': + return ParseRelation().fromJson(map); } } diff --git a/lib/src/utils/parse_encoder.dart b/lib/src/utils/parse_encoder.dart index f2929c376..322d83b2c 100644 --- a/lib/src/utils/parse_encoder.dart +++ b/lib/src/utils/parse_encoder.dart @@ -34,6 +34,10 @@ dynamic parseEncode(dynamic value, {bool full}) { return value; } + if (value is ParseRelation) { + return value.toJson(); + } + if (value is ParseObject || value is ParseUser) { if (full) { return value.toJson(full: full); diff --git a/lib/src/utils/parse_file_extensions.dart b/lib/src/utils/parse_file_extensions.dart index 7dc4a829a..6482c2c41 100644 --- a/lib/src/utils/parse_file_extensions.dart +++ b/lib/src/utils/parse_file_extensions.dart @@ -1,5 +1,7 @@ part of flutter_parse_sdk; +// ignore_for_file: always_specify_types + /// Get the extension type of the file String getExtension(String contentType) { if (_extensions.containsKey(contentType) && diff --git a/lib/src/utils/parse_logger.dart b/lib/src/utils/parse_logger.dart index 047d3d37d..50fce8190 100644 --- a/lib/src/utils/parse_logger.dart +++ b/lib/src/utils/parse_logger.dart @@ -1,10 +1,7 @@ part of flutter_parse_sdk; -void logAPIResponse( - String className, - String type, +void logAPIResponse(String className, String type, ParseResponse parseResponse) { - const String spacer = ' \n'; String responseString = ''; @@ -66,10 +63,10 @@ void logRequest( if (name.isNotEmpty) { name = '$appName '; } - requestString += '----\n${name}API Request ($className : $type) :'; - requestString += '\nUri: $uri'; - requestString += '\nBody: $body'; + requestString += '----\n${name}API Request ($className : $type) :'; + requestString += '\nUri: $uri'; + requestString += '\nBody: $body'; - requestString += '\n----\n'; - print(requestString); + requestString += '\n----\n'; + print(requestString); } diff --git a/lib/src/utils/parse_utils.dart b/lib/src/utils/parse_utils.dart index c2d8a090f..7ec88e4a5 100644 --- a/lib/src/utils/parse_utils.dart +++ b/lib/src/utils/parse_utils.dart @@ -15,7 +15,7 @@ dynamic convertValueToCorrectType(dynamic value) { /*if (value is String && !value.contains('__type')) { return '\"$value\"'; }*/ - + if (value is DateTime || value is ParseObject) { return parseEncode(value); } else { @@ -26,7 +26,6 @@ dynamic convertValueToCorrectType(dynamic value) { /// Sanitises a url Uri getSanitisedUri(ParseHTTPClient client, String pathToAppend, {Map queryParams, String query}) { - final Uri tempUri = Uri.parse(client.data.serverUrl); final Uri url = Uri( @@ -39,10 +38,11 @@ Uri getSanitisedUri(ParseHTTPClient client, String pathToAppend, return url; } + /// Removes unncessary / String removeTrailingSlash(String serverUrl) { - if (serverUrl.substring(serverUrl.length -1) == '/') { - return serverUrl.substring(0, serverUrl.length -1); + if (serverUrl.substring(serverUrl.length - 1) == '/') { + return serverUrl.substring(0, serverUrl.length - 1); } else { return serverUrl; } diff --git a/pubspec.yaml b/pubspec.yaml index 48c9e1a9c..844f42d2a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: parse_server_sdk description: Flutter plugin for Parse Server, (https://parseplatform.org), (https://back4app.com) -version: 1.0.21 +version: 1.0.22 homepage: https://github.com/phillwiggins/flutter_parse_sdk author: PhillWiggins @@ -16,7 +16,6 @@ dependencies: http: ^0.12.0 #Database - shared_preferences: ^0.5.2 sembast: ^1.15.1 xxtea: ^2.0.2