diff --git a/.travis.yml b/.travis.yml
index 13c0c03b0..e1599d287 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,12 +6,14 @@ dart:
install:
- git clone https://github.com/flutter/flutter.git -b stable --depth 1
- - export PATH=./flutter/bin:$PATH
+ - export PATH=~/build/parse-community/Parse-SDK-Flutter/flutter/bin:$PATH
- flutter doctor
script:
- - flutter packages get
- - flutter test --no-pub test/
+ - (cd packages/dart && pub get)
+ - (cd packages/dart && pub run test)
+ - (cd packages/flutter && flutter pub get)
+ - (cd packages/flutter && flutter test --no-pub test/)
cache:
directories:
diff --git a/README.md b/README.md
index e30959fd3..3de7ab640 100644
--- a/README.md
+++ b/README.md
@@ -1,891 +1,24 @@
+
+
+
+
-
+---
-## Parse For Flutter!
-Hi, this is a Flutter plugin that allows communication with a Parse Server, (https://parseplatform.org) either hosted on your own server or another, like (http://Back4App.com).
+This repository contains packages that allow communication with a Parse Server,
+(https://parseplatform.org) either hosted on your own server or another,
+like (http://Back4App.com).
This is a work in progress and we are consistently updating it. Please let us know if you think anything needs changing/adding, and more than ever, please do join in on this project. (Even if it is just to improve our documentation)
-## Join in!
-Want to get involved? Join our Slack channel and help out! (http://flutter-parse-sdk.slack.com)
+## Packages
-## Getting Started
-To install, either add to your pubspec.yaml
-```yml
-dependencies:
- parse_server_sdk: ^1.0.27
-```
-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.
+These are the available packages in this repository.
+| Plugin | Pub | explanation|
+|--------|-----|------------|
+| [parse_server_sdk](./packages/dart) | [](https://pub.dev/packages/parse_server_sdk) | a dart package that lets you communicate with the parse server |
+| [parse_server_sdk_flutter](./packages/flutter) | [](https://pub.dev/packages/parse_server_sdk_flutter) | a flutter package that lets you communicate with the parse server |
-Once you have the library added to your project, upon first call to your app (Similar to what your application class would be) add the following...
-
-```dart
-await Parse().initialize(
- keyApplicationId,
- keyParseServerUrl);
-```
-
-If you want to use secure storage or use the Flutter web/desktop SDK, please change to the below instance of CoreStorage as it has no dependencies on Flutter.
-```dart
-
-await Parse().initialize(
- keyParseApplicationId,
- keyParseServerUrl,
- coreStore: await CoreStoreSembastImp.getInstance());
-```
-It's possible to add other parameters to work with your instance of Parse Server:-
-
-```dart
- await Parse().initialize(
- keyApplicationId,
- keyParseServerUrl,
- masterKey: keyParseMasterKey, // Required for Back4App and others
- clientKey: keyParseClientKey, // Required for some setups
- debug: true, // When enabled, prints logs to console
- liveQueryUrl: keyLiveQueryUrl, // Required if using LiveQuery
- autoSendSessionId: true, // Required for authentication and ACL
- securityContext: securityContext, // Again, required for some setups
- coreStore: await CoreStoreSharedPrefsImp.getInstance()); // Local data storage method. Will use SharedPreferences instead of Sembast as an internal DB
-```
-
-
-#### Early Web support
-Currently this requires adding `X-Parse-Installation-Id` as an allowed header to parse-server.
-When running directly via docker, set the env var `PARSE_SERVER_ALLOW_HEADERS=X-Parse-Installation-Id`.
-When running via express, set [ParseServerOptions](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) `allowHeaders: ['X-Parse-Installation-Id']`.
-
-Be aware that for web ParseInstallation does include app name, version or package identifier.
-
-
-## Objects
-You can create custom objects by calling:
-```dart
-var dietPlan = ParseObject('DietPlan')
- ..set('Name', 'Ketogenic')
- ..set('Fat', 65);
-await dietPlan.save();
-```
-Or update existing object by its objectId by calling:
-```dart
-var dietPlan = ParseObject('DietPlan')
- ..objectId = 'R5EonpUDWy'
- ..set('Fat', 70);
-await dietPlan.save();
-```
-Verify that the object has been successfully saved using
-```dart
-var response = await dietPlan.save();
-if (response.success) {
- dietPlan = response.results.first;
-}
-```
-Types supported:
- * String
- * Double
- * Int
- * Boolean
- * DateTime
- * File
- * Geopoint
- * ParseObject/ParseUser (Pointer)
- * Map
- * List (all types supported)
-
-You then have the ability to do the following with that object:
-The features available are:-
- * Get
- * GetAll
- * Create
- * Save
- * Query - By object Id
- * Delete
- * Complex queries as shown above
- * Pin
- * Plenty more
- * Counters
- * Array Operators
-
-## Custom Objects
-You can create your own `ParseObjects` or convert your existing objects into Parse Objects by doing the following:
-
-```dart
-class DietPlan extends ParseObject implements ParseCloneable {
-
- DietPlan() : super(_keyTableName);
- DietPlan.clone(): this();
-
- /// Looks strangely hacky but due to Flutter not using reflection, we have to
- /// mimic a clone
- @override clone(Map map) => DietPlan.clone()..fromJson(map);
-
- static const String _keyTableName = 'Diet_Plans';
- static const String keyName = 'Name';
-
- String get name => get(keyName);
- set name(String name) => set(keyName, name);
-}
-
-```
-
-When receiving an `ParseObject` from the SDK, you can often provide an instance of your custom object as an copy object.
-To always use your custom object class, you can register your subclass at the initialization of the SDK.
-```dart
-Parse().initialize(
- ...,
- registeredSubClassMap: {
- 'Diet_Plans': () => DietPlan(),
- },
- parseUserConstructor: (username, password, emailAddress, {client, debug, sessionToken}) => CustomParseUser(username, password, emailAddress),
-);
-```
-Additionally you can register `SubClasses` after the initialization of the SDK.
-```dart
-ParseCoreData().registerSubClass('Diet_Plans', () => DietPlan());
-ParseCoreData().registerUserSubClass((username, password, emailAddress, {client, debug, sessionToken}) => CustomParseUser(username, password, emailAddress));
-```
-Providing a `ParseObject` as described above should still work, even if you have registered a different `SubClass`.
-
-For custom file classes have a lock at [here](#File).
-
-## Add new values to objects
-To add a variable to an object call and retrieve it, call
-
-```dart
-dietPlan.set('RandomInt', 8);
-var randomInt = dietPlan.get('RandomInt');
-```
-
-## Save objects using pins
-You can now save an object by calling `.pin()` on an instance of an object
-
-```dart
-dietPlan.pin();
-```
-
-and to retrieve it
-
-```dart
-var dietPlan = DietPlan().fromPin('OBJECT ID OF OBJECT');
-```
-
-## Storage
-We now have 2 types of storage, secure and unsecure. We currently rely on 2 third party options:
-
-- SharedPreferences
-- Sembast
-Sembast offers secured storage, whilst SharePreferences wraps NSUserDefaults (on iOS) and SharedPreferences (on Android).
-
-The storage method is defined in the parameter __coreStore__ in Parse().initialize
-
-Check sample code for options
-
-## Increment Counter values in objects
-Retrieve it, call
-
-```dart
-var response = await dietPlan.increment("count", 1);
-
-```
-or using with save function
-
-```dart
-dietPlan.setIncrement('count', 1);
-dietPlan.setDecrement('count', 1);
-var response = dietPlan.save()
-
-```
-
-## Array Operator in objects
-Retrieve it, call
-
-```dart
-var response = await dietPlan.add("listKeywords", ["a", "a","d"]);
-
-var response = await dietPlan.addUnique("listKeywords", ["a", "a","d"]);
-
-var response = await dietPlan.remove("listKeywords", ["a"]);
-
-```
-or using with save function
-
-```dart
-dietPlan.setAdd('listKeywords', ['a','a','d']);
-dietPlan.setAddUnique('listKeywords', ['a','a','d']);
-dietPlan.setRemove('listKeywords', ['a']);
-var response = dietPlan.save()
-```
-
-## Queries
-Once you have setup the project and initialised the instance, you can then retreive data from your server by calling:
-```dart
-var apiResponse = await ParseObject('ParseTableName').getAll();
-
-if (apiResponse.success){
- for (var testObject in apiResponse.result) {
- print(ApplicationConstants.APP_NAME + ": " + testObject.toString());
- }
-}
-```
-Or you can get an object by its objectId:
-
-```dart
-var dietPlan = await DietPlan().getObject('R5EonpUDWy');
-
-if (dietPlan.success) {
- print(ApplicationConstants.keyAppName + ": " + (dietPlan.result as DietPlan).toString());
-} else {
- print(ApplicationConstants.keyAppName + ": " + dietPlan.exception.message);
-}
-```
-
-## Complex queries
-You can create complex queries to really put your database to the test:
-
-```dart
-var queryBuilder = QueryBuilder(DietPlan())
- ..startsWith(DietPlan.keyName, "Keto")
- ..greaterThan(DietPlan.keyFat, 64)
- ..lessThan(DietPlan.keyFat, 66)
- ..equals(DietPlan.keyCarbs, 5);
-
-var response = await queryBuilder.query();
-
-if (response.success) {
- print(ApplicationConstants.keyAppName + ": " + ((response.results as List).first as DietPlan).toString());
-} else {
- print(ApplicationConstants.keyAppName + ": " + response.exception.message);
-}
-```
-
-if you want to find objects that match one of several queries, you can use __QueryBuilder.or__ method to construct a query that is an OR of the queries passed in. For instance if you want to find players who either have a lot of wins or a few wins, you can do:
-```dart
-ParseObject playerObject = ParseObject("Player");
-
-QueryBuilder lotsOfWins =
- QueryBuilder(playerObject))
- ..whereGreaterThan('wins', 50);
-
-QueryBuilder fewWins =
- QueryBuilder(playerObject)
- ..whereLessThan('wins', 5);
-
-QueryBuilder mainQuery = QueryBuilder.or(
- playerObject,
- [lotsOfWins, fewWins],
- );
-
-var apiResponse = await mainQuery.query();
-```
-
-The features available are:-
- * Equals
- * Contains
- * LessThan
- * LessThanOrEqualTo
- * GreaterThan
- * GreaterThanOrEqualTo
- * NotEqualTo
- * StartsWith
- * EndsWith
- * Exists
- * Near
- * WithinMiles
- * WithinKilometers
- * WithinRadians
- * WithinGeoBox
- * MatchesQuery
- * DoesNotMatchQuery
- * MatchesKeyInQuery
- * DoesNotMatchKeyInQuery
- * Regex
- * Order
- * Limit
- * Skip
- * Ascending
- * Descending
- * Plenty more!
-
-## Relational queries
-If you want to retrieve objects where a field contains an object that matches another query, you can use the
-__whereMatchesQuery__ condition.
-For example, imagine you have Post class and a Comment class, where each Comment has a pointer to its parent Post.
-You can find comments on posts with images by doing:
-
-```dart
-QueryBuilder queryPost =
- QueryBuilder(ParseObject('Post'))
- ..whereValueExists('image', true);
-
-QueryBuilder queryComment =
- QueryBuilder(ParseObject('Comment'))
- ..whereMatchesQuery('post', queryPost);
-
-var apiResponse = await queryComment.query();
-```
-
-If you want to retrieve objects where a field contains an object that does not match another query, you can use the
-__whereDoesNotMatchQuery__ condition.
-Imagine you have Post class and a Comment class, where each Comment has a pointer to its parent Post.
-You can find comments on posts without images by doing:
-
-```dart
-QueryBuilder queryPost =
- QueryBuilder(ParseObject('Post'))
- ..whereValueExists('image', true);
-
-QueryBuilder queryComment =
- QueryBuilder(ParseObject('Comment'))
- ..whereDoesNotMatchQuery('post', queryPost);
-
-var apiResponse = await queryComment.query();
-```
-
-You can use the __whereMatchesKeyInQuery__ method to get objects where a key matches the value of a key in a set of objects resulting from another query. For example, if you have a class containing sports teams and you store a user’s hometown in the user class, you can issue one query to find the list of users whose hometown teams have winning records. The query would look like::
-
-```dart
-QueryBuilder teamQuery =
- QueryBuilder(ParseObject('Team'))
- ..whereGreaterThan('winPct', 0.5);
-
-QueryBuilder userQuery =
- QueryBuilderParseUser.forQuery())
- ..whereMatchesKeyInQuery('hometown', 'city', teamQuery);
-
-var apiResponse = await userQuery.query();
-```
-
-Conversely, to get objects where a key does not match the value of a key in a set of objects resulting from another query, use __whereDoesNotMatchKeyInQuery__. For example, to find users whose hometown teams have losing records:
-
-```dart
-QueryBuilder teamQuery =
- QueryBuilder(ParseObject('Team'))
- ..whereGreaterThan('winPct', 0.5);
-
-QueryBuilder losingUserQuery =
- QueryBuilderParseUser.forQuery())
- ..whereDoesNotMatchKeyInQuery('hometown', 'city', teamQuery);
-
-var apiResponse = await losingUserQuery.query();
-```
-
-To filter rows based on objectId’s from pointers in a second table, you can use dot notation:
-```dart
-QueryBuilder rolesOfTypeX =
- QueryBuilder(ParseObject('Role'))
- ..whereEqualTo('type', 'x');
-
-QueryBuilder groupsWithRoleX =
- QueryBuilder(ParseObject('Group')))
- ..whereMatchesKeyInQuery('objectId', 'belongsTo.objectId', rolesOfTypeX);
-
-var apiResponse = await groupsWithRoleX.query();
-```
-
-## Counting Objects
-If you only care about the number of games played by a particular player:
-
-```dart
-QueryBuilder queryPlayers =
- QueryBuilder(ParseObject('GameScore'))
- ..whereEqualTo('playerName', 'Jonathan Walsh');
-var apiResponse = await queryPlayers.count();
-if (apiResponse.success && apiResponse.result != null) {
- int countGames = apiResponse.count;
-}
-```
-
-## Live Queries
-This tool allows you to subscribe to a QueryBuilder you are interested in. Once subscribed, the server will notify clients
-whenever a ParseObject that matches the QueryBuilder is created or updated, in real-time.
-
-Parse LiveQuery contains two parts, the LiveQuery server and the LiveQuery clients. In order to use live queries, you need
-to set up both of them.
-
-The Parse Server configuration guide on the server is found here https://docs.parseplatform.org/parse-server/guide/#live-queries and is not part of this documentation.
-
-Initialize the Parse Live Query by entering the parameter liveQueryUrl in Parse().initialize:
-```dart
-Parse().initialize(
- keyApplicationId,
- keyParseServerUrl,
- clientKey: keyParseClientKey,
- debug: true,
- liveQueryUrl: keyLiveQueryUrl,
- autoSendSessionId: true);
-```
-
-Declare LiveQuery:
-```dart
-final LiveQuery liveQuery = LiveQuery();
-```
-
-Set the QueryBuilder that will be monitored by LiveQuery:
-```dart
-QueryBuilder query =
- QueryBuilder(ParseObject('TestAPI'))
- ..whereEqualTo('intNumber', 1);
-```
-__Create a subscription__
-You’ll get the LiveQuery events through this subscription.
-The first time you call subscribe, we’ll try to open the WebSocket connection to the LiveQuery server for you.
-
-```dart
-Subscription subscription = await liveQuery.client.subscribe(query);
-```
-
-__Event Handling__
-We define several types of events you’ll get through a subscription object:
-
-__Create event__
-When a new ParseObject is created and it fulfills the QueryBuilder you subscribe, you’ll get this event.
-The object is the ParseObject which was created.
-```dart
-subscription.on(LiveQueryEvent.create, (value) {
- print('*** CREATE ***: ${DateTime.now().toString()}\n $value ');
- print((value as ParseObject).objectId);
- print((value as ParseObject).updatedAt);
- print((value as ParseObject).createdAt);
- print((value as ParseObject).get('objectId'));
- print((value as ParseObject).get('updatedAt'));
- print((value as ParseObject).get('createdAt'));
-});
-```
-
-__Update event__
-When an existing ParseObject which fulfills the QueryBuilder you subscribe is updated (The ParseObject fulfills the
-QueryBuilder before and after changes), you’ll get this event.
-The object is the ParseObject which was updated. Its content is the latest value of the ParseObject.
-```dart
-subscription.on(LiveQueryEvent.update, (value) {
- print('*** UPDATE ***: ${DateTime.now().toString()}\n $value ');
- print((value as ParseObject).objectId);
- print((value as ParseObject).updatedAt);
- print((value as ParseObject).createdAt);
- print((value as ParseObject).get('objectId'));
- print((value as ParseObject).get('updatedAt'));
- print((value as ParseObject).get('createdAt'));
-});
-```
-
-__Enter event__
-When an existing ParseObject’s old value does not fulfill the QueryBuilder but its new value fulfills the QueryBuilder,
-you’ll get this event. The object is the ParseObject which enters the QueryBuilder.
-Its content is the latest value of the ParseObject.
-```dart
-subscription.on(LiveQueryEvent.enter, (value) {
- print('*** ENTER ***: ${DateTime.now().toString()}\n $value ');
- print((value as ParseObject).objectId);
- print((value as ParseObject).updatedAt);
- print((value as ParseObject).createdAt);
- print((value as ParseObject).get('objectId'));
- print((value as ParseObject).get('updatedAt'));
- print((value as ParseObject).get('createdAt'));
-});
-```
-
-__Leave event__
-When an existing ParseObject’s old value fulfills the QueryBuilder but its new value doesn’t fulfill the QueryBuilder,
-you’ll get this event. The object is the ParseObject which leaves the QueryBuilder.
-Its content is the latest value of the ParseObject.
-```dart
-subscription.on(LiveQueryEvent.leave, (value) {
- print('*** LEAVE ***: ${DateTime.now().toString()}\n $value ');
- print((value as ParseObject).objectId);
- print((value as ParseObject).updatedAt);
- print((value as ParseObject).createdAt);
- print((value as ParseObject).get('objectId'));
- print((value as ParseObject).get('updatedAt'));
- print((value as ParseObject).get('createdAt'));
-});
-```
-
-__Delete event__
-When an existing ParseObject which fulfills the QueryBuilder is deleted, you’ll get this event.
-The object is the ParseObject which is deleted
-```dart
-subscription.on(LiveQueryEvent.delete, (value) {
- print('*** DELETE ***: ${DateTime.now().toString()}\n $value ');
- print((value as ParseObject).objectId);
- print((value as ParseObject).updatedAt);
- print((value as ParseObject).createdAt);
- print((value as ParseObject).get('objectId'));
- print((value as ParseObject).get('updatedAt'));
- print((value as ParseObject).get('createdAt'));
-});
-```
-
-__Unsubscribe__
-If you would like to stop receiving events from a QueryBuilder, you can just unsubscribe the subscription.
-After that, you won’t get any events from the subscription object and will close the WebSocket connection to the
-LiveQuery server.
-
-```dart
-liveQuery.client.unSubscribe(subscription);
-```
-
-__Disconnection__
-In case the client's connection to the server breaks,
-LiveQuery will automatically try to reconnect.
-LiveQuery will wait at increasing intervals between reconnection attempts.
-By default, these intervals are set to `[0, 500, 1000, 2000, 5000, 10000]` for mobile and `[0, 500, 1000, 2000, 5000]` for web.
-You can change these by providing a custom list using the `liveListRetryIntervals` parameter at `Parse.initialize()` ("-1" means "do not try to reconnect").
-
-## ParseLiveList
-ParseLiveList makes implementing a dynamic List as simple as possible.
-
-#### General Use
-It ships with the ParseLiveList class itself, this class manages all elements of the list, sorts them,
-keeps itself up to date and Notifies you on changes.
-
-ParseLiveListWidget is a widget that handles all the communication with the ParseLiveList for you.
-Using ParseLiveListWidget you can create a dynamic List by just providing a QueryBuilder.
-
-```dart
-ParseLiveListWidget(
- query: query,
- );
-```
-To customize the List Elements, you can provide a childBuilder.
-```dart
-ParseLiveListWidget(
- query: query,
- reverse: false,
- childBuilder:
- (BuildContext context, ParseLiveListElementSnapshot snapshot) {
- if (snapshot.failed) {
- return const Text('something went wrong!');
- } else if (snapshot.hasData) {
- return ListTile(
- title: Text(
- snapshot.loadedData.get("text"),
- ),
- );
- } else {
- return const ListTile(
- leading: CircularProgressIndicator(),
- );
- }
- },
-);
-```
-Similar to the standard ListView, you can provide arguments like reverse or shrinkWrap.
-By providing the listLoadingElement, you can show the user something while the list is loading.
-```dart
-ParseLiveListWidget(
- query: query,
- childBuilder: childBuilder,
- listLoadingElement: Center(
- child: CircularProgressIndicator(),
- ),
-);
-```
-By providing the duration argument, you can change the animation speed.
-```dart
-ParseLiveListWidget(
- query: query,
- childBuilder: childBuilder,
- duration: Duration(seconds: 1),
-);
-```
-### included Sub-Objects
-By default, ParseLiveQuery will provide you with all the objects you included in your Query like this:
-```dart
-queryBuilder.includeObject(/*List of all the included sub-objects*/);
-```
-ParseLiveList will not listen for updates on this objects by default.
-To activate listening for updates on all included objects, add `listenOnAllSubItems: true` to your ParseLiveListWidgets constructor.
-If you want ParseLiveList to listen for updates on only some sub-objects, use `listeningIncludes: const [/*all the included sub-objects*/]` instead.
-Just as QueryBuilder, ParseLiveList supports nested sub-objects too.
-
-**NOTE:** To use this features you have to enable [Live Queries](#live-queries) first.
-
-## Users
-You can create and control users just as normal using this SDK.
-
-To register a user, first create one :
-```dart
-var user = ParseUser().create("TestFlutter", "TestPassword123", "TestFlutterSDK@gmail.com");
-```
-Then have the user sign up:
-
-```dart
-var response = await user.signUp();
-if (response.success) user = response.result;
-```
-You can also login with the user:
-```dart
-var response = await user.login();
-if (response.success) user = response.result;
-```
-You can also logout with the user:
-```dart
-var response = await user.logout();
-if (response.success) {
- print('User logout');
-}
-```
-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.
-```dart
-user = ParseUser.currentUser();
-```
-
-To add additional columns to the user:
-```dart
-var user = ParseUser("TestFlutter", "TestPassword123", "TestFlutterSDK@gmail.com")
- ..set("userLocation", "FlutterLand");
-```
-
-Other user features are:-
- * Request Password Reset
- * Verification Email Request
- * Get all users
- * Save
- * Destroy user
- * Queries
-
- ## Facebook, OAuth and 3rd Party Login/User
-
- Usually, each provider will provide their own library for logins, but the loginWith method on ParseUser accepts a name of provider, then a Map with the authentication details required.
- For Facebook and the example below, we used the library provided at https://pub.dev/packages/flutter_facebook_login
-
- ```
- Future goToFacebookLogin() async {
- final FacebookLogin facebookLogin = FacebookLogin();
- final FacebookLoginResult result = await facebookLogin.logInWithReadPermissions(['email']);
-
- switch (result.status) {
- case FacebookLoginStatus.loggedIn:
- final ParseResponse response = await ParseUser.loginWith(
- 'facebook',
- facebook(result.accessToken.token,
- result.accessToken.userId,
- result.accessToken.expires));
-
- if (response.success) {
- // User is logged in, test with ParseUser.currentUser()
- }
- break;
- case FacebookLoginStatus.cancelledByUser:
- // User cancelled
- break;
- case FacebookLoginStatus.error:
- // Error
- break;
- }
- }
-```
-
-For Google and the example below, we used the library provided at https://pub.dev/packages/google_sign_in
-
-```
-class OAuthLogin {
- final GoogleSignIn _googleSignIn = GoogleSignIn( scopes: ['email', 'https://www.googleapis.com/auth/contacts.readonly'] );
-
- sigInGoogle() async {
- GoogleSignInAccount account = await _googleSignIn.signIn();
- GoogleSignInAuthentication authentication = await account.authentication;
- await ParseUser.loginWith(
- 'google',
- google(_googleSignIn.currentUser.id,
- authentication.accessToken,
- authentication.idToken));
- }
-}
-```
-
-## Security for Objects - ParseACL
-For any object, you can specify which users are allowed to read the object, and which users are allowed to modify an object.
-To support this type of security, each object has an access control list, implemented by the __ParseACL__ class.
-
-If ParseACL is not specified (with the exception of the ParseUser class) all objects are set to Public for read and write.
-The simplest way to use a ParseACL is to specify that an object may only be read or written by a single user.
-To create such an object, there must first be a logged in ParseUser. Then, new ParseACL(user) generates a ParseACL that
-limits access to that user. An object’s ACL is updated when the object is saved, like any other property.
-
-```dart
-ParseUser user = await ParseUser.currentUser() as ParseUser;
-ParseACL parseACL = ParseACL(owner: user);
-
-ParseObject parseObject = ParseObject("TestAPI");
-...
-parseObject.setACL(parseACL);
-var apiResponse = await parseObject.save();
-```
-Permissions can also be granted on a per-user basis. You can add permissions individually to a ParseACL using
-__setReadAccess__ and __setWriteAccess__
-```dart
-ParseUser user = await ParseUser.currentUser() as ParseUser;
-ParseACL parseACL = ParseACL();
-//grant total access to current user
-parseACL.setReadAccess(userId: user.objectId, allowed: true);
-parseACL.setWriteAccess(userId: user.objectId, allowed: true);
-//grant read access to userId: 'TjRuDjuSAO'
-parseACL.setReadAccess(userId: 'TjRuDjuSAO', allowed: true);
-parseACL.setWriteAccess(userId: 'TjRuDjuSAO', allowed: false);
-
-ParseObject parseObject = ParseObject("TestAPI");
-...
-parseObject.setACL(parseACL);
-var apiResponse = await parseObject.save();
-```
-You can also grant permissions to all users at once using setPublicReadAccess and setPublicWriteAccess.
-```dart
-ParseACL parseACL = ParseACL();
-parseACL.setPublicReadAccess(allowed: true);
-parseACL.setPublicWriteAccess(allowed: true);
-
-ParseObject parseObject = ParseObject("TestAPI");
-...
-parseObject.setACL(parseACL);
-var apiResponse = await parseObject.save();
-```
-Operations that are forbidden, such as deleting an object that you do not have write access to, result in a
-ParseError with code 101: 'ObjectNotFound'.
-For security purposes, this prevents clients from distinguishing which object ids exist but are secured, versus which
-object ids do not exist at all.
-
-You can retrieve the ACL list of an object using:
-```dart
-ParseACL parseACL = parseObject.getACL();
-```
-
-## Config
-The SDK supports Parse Config. A map of all configs can be grabbed from the server by calling :
-```dart
-var response = await ParseConfig().getConfigs();
-```
-
-and to add a config:
-```dart
-ParseConfig().addConfig('TestConfig', 'testing');
-```
-
-## Cloud Functions
-The SDK supports call Cloud Functions.
-
-Executes a cloud function that returns a ParseObject type
-```dart
-final ParseCloudFunction function = ParseCloudFunction('hello');
-final ParseResponse result =
- await function.executeObjectFunction();
-if (result.success) {
- if (result.result is ParseObject) {
- final ParseObject parseObject = result.result;
- print(parseObject.className);
- }
-}
-```
-
-Executes a cloud function with parameters
-```dart
-final ParseCloudFunction function = ParseCloudFunction('hello');
-final Map params = {'plan': 'paid'};
-function.execute(parameters: params);
-```
-
-## Relation
-
-The SDK supports Relation.
-
-To add relation to object:
-
-```dart
-dietPlan.addRelation('fruits', [ParseObject("Fruits")..set("objectId", "XGadzYxnac")]);
-```
-
-To remove relation to object:
-
-```dart
-dietPlan.removeRelation('fruits', [ParseObject("Fruits")..set("objectId", "XGadzYxnac")]);
-```
-
-To Retrive a relation instance for user, call:
-```dart
-final relation = dietPlan.getRelation('fruits');
-```
-
-and then you can add a relation to the passed in object:
-```
-relation.add(dietPlan);
-final result = await user.save();
-```
-
-To retrieve objects that are members of Relation field of a parent object:
-```dart
-QueryBuilder query =
- QueryBuilder(ParseObject('Fruits'))
- ..whereRelatedTo('fruits', 'DietPlan', DietPlan.objectId);
-```
-
-## File
-There are three different file classes in this SDK:
-- `ParseFileBase` is and abstract class and is the foundation of every file class that can be handled by this SDK.
-- `ParseFile` (former the only file class in the SDK) extends ParseFileBase and is by default used as the file class on every platform but web.
-This class uses a `File` from `dart:io` for storing the raw file.
-- `ParseWebFile` is the equivalent to ParseFile used at Flutter Web.
-This class uses an `Uint8List` for storing the raw file.
-
-These classes are used by default to represent files, but you can also build your own class extending ParseFileBase and provide a custom `ParseFileConstructor` similar to the `SubClasses`.
-
-Have a look at the example application for a small (non web) example.
-
-
-```dart
-//A short example for showing an image from a ParseFileBase
-Widget buildImage(ParseFileBase image){
- return FutureBuilder(
- future: image.download(),
- builder: (BuildContext context,
- AsyncSnapshot snapshot) {
- if (snapshot.hasData) {
- if (kIsWeb) {
- return Image.memory((snapshot.data as ParseWebFile).file);
- } else {
- return Image.file((snapshot.data as ParseFile).file);
- }
- } else {
- return CircularProgressIndicator();
- }
- },
- );
-}
-```
-```dart
-//A short example for storing a selected picture
-//libraries: image_picker (https://pub.dev/packages/image_picker), image_picker_for_web (https://pub.dev/packages/image_picker_for_web)
-PickedFile pickedFile = await ImagePicker().getImage(source: ImageSource.gallery);
-ParseFileBase parseFile;
-if (kIsWeb) {
- //Seems weird, but this lets you get the data from the selected file as an Uint8List very easily.
- ParseWebFile file = ParseWebFile(null, name: null, url: pickedFile.path);
- await file.download();
- parseFile = ParseWebFile(file.file, name: file.name);
-} else {
- parseFile = ParseFile(File(pickedFile.path));
-}
-someParseObject.set("image", parseFile);
-//This saves the ParseObject as well as all of its children, and the ParseFileBase is such a child.
-await someParseObject.save();
-```
-
-## Other Features of this library
-Main:
-* Installation (View the example application)
-* GeoPoints (View the example application)
-* Persistent storage
-* Debug Mode - Logging API calls
-* Manage Session ID's tokens
-
-User:
-* Queries
-* Anonymous (View the example application)
-* 3rd Party Authentication
-
-Objects:
-* Create new object
-* Extend Parse Object and create local objects that can be saved and retreived
-* Queries
-
-## Author:-
+### Author:-
This project was authored by Phill Wiggins. You can contact me at phill.wiggins@gmail.com
diff --git a/docs/migrate-1-0-28.md b/docs/migrate-1-0-28.md
new file mode 100644
index 000000000..e7771ad91
--- /dev/null
+++ b/docs/migrate-1-0-28.md
@@ -0,0 +1,53 @@
+# Migrate your Flutter application to version 1.0.28
+
+Starting with version 1.0.28, this repository is now separated in a pure dart (parse_server_sdk) and a flutter package (parse_server_sdk_flutter).
+This was done in order to provide a dart package for the parse-server, while keeping maintenance simple.
+You can find both packages in the package directory.
+
+### 1. pubspec.yaml
+In your projects pubspec.yaml at the dependencies section, you have to change
+```
+dependencies:
+ parse_server_sdk: ^1.0.27
+```
+to
+```
+dependencies:
+ parse_server_sdk_flutter: ^1.0.28
+```
+This is the current released version of the parse_server_sdk_flutter package: [](https://pub.dev/packages/parse_server_sdk_flutter)
+
+### 2. imports
+As the package name changed, you have to change
+```
+import 'package:parse_server_sdk/parse_server_sdk.dart';
+```
+ to
+```
+import 'package:parse_server_sdk_flutter/parse_server_sdk.dart';
+```
+in every file.
+
+It is recommended to do so by the replacement feature of your IDE.
+
+### optional: provide app information on web
+As flutter web is now in beta, this SDK aims to be web compatible.
+But there are some parts completely different on web. For example, the wep-app cant determine it's name, version or packagename.
+That's why you should provide this information on web.
+```dart
+Parse().initialize(
+ ...
+ appName: kIsWeb ? "MyApplication" : null,
+ appVersion: kIsWeb ? "Version 1" : null,
+ appPackageName: kIsWeb ? "com.example.myapplication" : null,
+);
+```
+
+### optional: adjust your tests
+You tests might fail if the package tries to automatically discover facts like you applications name, version, package name or the default file directory.
+You can avoid this automatic discovery by providing values for `appName`, `appVersion`, `appPackageName` as well as `fileDirectory` at you `Parse().initialize()` call.
+
+### changed network library
+In order to provide a `ProgressCallback` for heavy file operations,
+the network library was switched from [http](https://pub.dev/packages/http) to [dio](https://pub.dev/packages/dio).
+There should be no breaking changes regarding this change, except if you are overriding the `ParseHTTPClient`.
\ No newline at end of file
diff --git a/example/.vscode/launch.json b/example/.vscode/launch.json
deleted file mode 100644
index 5086a1da4..000000000
--- a/example/.vscode/launch.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "version": "0.2.0",
- "configurations": [
- {
- "name": "Flutter Desktop Attach",
- "request": "attach",
- "observatoryUri": "http://127.0.0.1:52878/UWW_6_X9Y74=/",
- "type": "dart"
- }
- ]
-}
\ No newline at end of file
diff --git a/example/android/gradle/wrapper/gradle-wrapper.jar b/example/android/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 13372aef5..000000000
Binary files a/example/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ
diff --git a/example/android/gradlew b/example/android/gradlew
deleted file mode 100755
index 9d82f7891..000000000
--- a/example/android/gradlew
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/usr/bin/env bash
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
- echo "$*"
-}
-
-die ( ) {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
-esac
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=$((i+1))
- done
- case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
-fi
-
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
-}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
-
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/lib/src/network/parse_http_client.dart b/lib/src/network/parse_http_client.dart
deleted file mode 100644
index b577f4849..000000000
--- a/lib/src/network/parse_http_client.dart
+++ /dev/null
@@ -1,46 +0,0 @@
-part of flutter_parse_sdk;
-
-/// Creates a custom version of HTTP Client that has Parse Data Preset
-class ParseHTTPClient extends BaseClient {
- ParseHTTPClient({bool sendSessionId = false, SecurityContext securityContext})
- : _sendSessionId = sendSessionId,
- _client = securityContext != null
- ? IOClient(HttpClient(context: securityContext))
- : Client();
-
- final Client _client;
- final bool _sendSessionId;
- final String _userAgent = '$keyLibraryName $keySdkVersion';
- ParseCoreData data = ParseCoreData();
- Map additionalHeaders;
-
- /// Overrides the call method for HTTP Client and adds custom headers
- @override
- Future send(BaseRequest request) {
- if (!identical(0, 0.0)) {
- request.headers[keyHeaderUserAgent] = _userAgent;
- }
- request.headers[keyHeaderApplicationId] = data.applicationId;
- if ((_sendSessionId == true) &&
- (data.sessionId != null) &&
- (request.headers[keyHeaderSessionToken] == null))
- request.headers[keyHeaderSessionToken] = data.sessionId;
-
- if (data.clientKey != null)
- request.headers[keyHeaderClientKey] = data.clientKey;
- if (data.masterKey != null)
- request.headers[keyHeaderMasterKey] = data.masterKey;
-
- /// If developer wants to add custom headers, extend this class and add headers needed.
- if (additionalHeaders != null && additionalHeaders.isNotEmpty) {
- additionalHeaders
- .forEach((String key, String value) => request.headers[key] = value);
- }
-
- if (data.debug) {
- logCUrl(request);
- }
-
- return _client.send(request);
- }
-}
diff --git a/lib/src/network/parse_live_query_web.dart b/lib/src/network/parse_live_query_web.dart
deleted file mode 100644
index 6d9012785..000000000
--- a/lib/src/network/parse_live_query_web.dart
+++ /dev/null
@@ -1,462 +0,0 @@
-import 'dart:async';
-import 'dart:convert';
-import 'dart:html' as html;
-
-import 'package:connectivity/connectivity.dart';
-import 'package:flutter/widgets.dart';
-
-import '../../parse_server_sdk.dart';
-
-enum LiveQueryEvent { create, enter, update, leave, delete, error }
-
-const String _printConstLiveQuery = 'LiveQuery: ';
-
-// // ignore_for_file: always_specify_types
-class Subscription {
- Subscription(this.query, this.requestId, {T copyObject}) {
- _copyObject = copyObject;
- }
- QueryBuilder query;
- T _copyObject;
- int requestId;
- bool _enabled = false;
- final List _liveQueryEvent = [
- 'create',
- 'enter',
- 'update',
- 'leave',
- 'delete',
- 'error'
- ];
- Map eventCallbacks = {};
- void on(LiveQueryEvent op, Function callback) {
- eventCallbacks[_liveQueryEvent[op.index]] = callback;
- }
-
- T get copyObject {
- return _copyObject;
- }
-}
-
-enum LiveQueryClientEvent { CONNECTED, DISCONNECTED, USER_DISCONNECTED }
-
-class LiveQueryReconnectingController with WidgetsBindingObserver {
- LiveQueryReconnectingController(
- this._reconnect, this._eventStream, this.debug) {
- _connectivityChanged(ConnectivityResult.wifi);
- _eventStream.listen((LiveQueryClientEvent event) {
- switch (event) {
- case LiveQueryClientEvent.CONNECTED:
- _isConnected = true;
- _retryState = 0;
- _userDisconnected = false;
- break;
- case LiveQueryClientEvent.DISCONNECTED:
- _isConnected = false;
- _setReconnect();
- break;
- case LiveQueryClientEvent.USER_DISCONNECTED:
- _userDisconnected = true;
- if (_currentTimer != null) {
- _currentTimer.cancel();
- _currentTimer = null;
- }
- break;
- }
-
- if (debug) {
- print('$DEBUG_TAG: $event');
- }
- });
- WidgetsBinding.instance.addObserver(this);
- }
-
- static List get retryInterval => ParseCoreData().liveListRetryIntervals;
- static const String DEBUG_TAG = 'LiveQueryReconnectingController';
-
- final Function _reconnect;
- final Stream _eventStream;
- final bool debug;
-
- int _retryState = 0;
- bool _isOnline = false;
- bool _isConnected = false;
- bool _userDisconnected = false;
-
- Timer _currentTimer;
-
- void _connectivityChanged(ConnectivityResult state) {
- if (!_isOnline && state != ConnectivityResult.none) {
- _retryState = 0;
- }
- _isOnline = state != ConnectivityResult.none;
- if (debug) {
- print('$DEBUG_TAG: $state');
- }
- _setReconnect();
- }
-
- @override
- void didChangeAppLifecycleState(AppLifecycleState state) {
- switch (state) {
- case AppLifecycleState.resumed:
- _setReconnect();
- break;
- default:
- break;
- }
- }
-
- void _setReconnect() {
- if (_isOnline &&
- !_isConnected &&
- _currentTimer == null &&
- !_userDisconnected &&
- retryInterval[_retryState] >= 0) {
- _currentTimer =
- Timer(Duration(milliseconds: retryInterval[_retryState]), () {
- _currentTimer = null;
- _reconnect();
- });
- if (debug)
- print('$DEBUG_TAG: Retrytimer set to ${retryInterval[_retryState]}ms');
- if (_retryState < retryInterval.length - 1) {
- _retryState++;
- }
- }
- }
-}
-
-class Client {
- factory Client() => _getInstance();
- Client._internal(
- {bool debug, ParseHTTPClient client, bool autoSendSessionId}) {
- _clientEventStreamController = StreamController();
- _clientEventStream =
- _clientEventStreamController.stream.asBroadcastStream();
-
- _client = client ??
- ParseHTTPClient(
- sendSessionId:
- autoSendSessionId ?? ParseCoreData().autoSendSessionId,
- securityContext: ParseCoreData().securityContext);
-
- _debug = isDebugEnabled(objectLevelDebug: debug);
- _sendSessionId =
- autoSendSessionId ?? ParseCoreData().autoSendSessionId ?? true;
- _liveQueryURL = _client.data.liveQueryURL;
- if (_liveQueryURL.contains('https')) {
- _liveQueryURL = _liveQueryURL.replaceAll('https', 'wss');
- } else if (_liveQueryURL.contains('http')) {
- _liveQueryURL = _liveQueryURL.replaceAll('http', 'ws');
- }
-
- reconnectingController = LiveQueryReconnectingController(
- () => reconnect(userInitialized: false), getClientEventStream, _debug);
- }
- static Client get instance => _getInstance();
- static Client _instance;
- static Client _getInstance(
- {bool debug, ParseHTTPClient client, bool autoSendSessionId}) {
- _instance ??= Client._internal(
- debug: debug, client: client, autoSendSessionId: autoSendSessionId);
- return _instance;
- }
-
- Stream get getClientEventStream {
- return _clientEventStream;
- }
-
- html.WebSocket _webSocket;
- ParseHTTPClient _client;
- bool _debug;
- bool _sendSessionId;
- Stream _stream;
- String _liveQueryURL;
- bool _connecting = false;
- StreamController _clientEventStreamController;
- Stream _clientEventStream;
- LiveQueryReconnectingController reconnectingController;
-
- final Map _requestSubScription = {};
-
- Future reconnect({bool userInitialized = false}) async {
- await _connect(userInitialized: userInitialized);
- _connectLiveQuery();
- }
-
- int readyState() {
- if (_webSocket != null) {
- return _webSocket.readyState;
- }
- return html.WebSocket.CONNECTING;
- }
-
- Future disconnect({bool userInitialized = false}) async {
- if (_webSocket != null && _webSocket.readyState == html.WebSocket.OPEN) {
- if (_debug) {
- print('$_printConstLiveQuery: Socket closed');
- }
- _webSocket.close();
- _webSocket = null;
- }
- if (_webSocket != null) {
- if (_debug) {
- print('$_printConstLiveQuery: close');
- }
- _webSocket.close();
- _webSocket = null;
- _stream = null;
- }
-
- _requestSubScription.values.toList().forEach((Subscription subscription) {
- subscription._enabled = false;
- });
- _connecting = false;
- if (userInitialized)
- _clientEventStreamController.sink
- .add(LiveQueryClientEvent.USER_DISCONNECTED);
- }
-
- Future> subscribe(
- QueryBuilder query,
- {T copyObject}) async {
- if (_webSocket == null) {
- await _clientEventStream.any((LiveQueryClientEvent event) =>
- event == LiveQueryClientEvent.CONNECTED);
- }
- final int requestId = _requestIdGenerator();
- final Subscription subscription =
- Subscription(query, requestId, copyObject: copyObject);
- _requestSubScription[requestId] = subscription;
- //After a client connects to the LiveQuery server,
- //it can send a subscribe message to subscribe a ParseQuery.
- _subscribeLiveQuery(subscription);
- return subscription;
- }
-
- void unSubscribe(Subscription subscription) {
- //Mount message for Unsubscribe
- final Map unsubscribeMessage = {
- 'op': 'unsubscribe',
- 'requestId': subscription.requestId,
- };
- if (_webSocket != null) {
- if (_debug) {
- print('$_printConstLiveQuery: UnsubscribeMessage: $unsubscribeMessage');
- }
- _webSocket.send(jsonEncode(unsubscribeMessage));
-// _channel.sink.add(jsonEncode(unsubscribeMessage));
- subscription._enabled = false;
- _requestSubScription.remove(subscription.requestId);
- }
- }
-
- static int _requestIdCount = 1;
-
- int _requestIdGenerator() {
- return _requestIdCount++;
- }
-
- Future _connect({bool userInitialized = false}) async {
- if (_connecting) {
- print('already connecting');
- return Future.value(null);
- }
- await disconnect(userInitialized: userInitialized);
- _connecting = true;
-
- try {
- _webSocket = html.WebSocket(_liveQueryURL);
- await _webSocket.onOpen.first;
-
- _connecting = false;
- if (_webSocket != null && _webSocket.readyState == html.WebSocket.OPEN) {
- if (_debug) {
- print('$_printConstLiveQuery: Socket opened');
- }
- } else {
- if (_debug) {
- print('$_printConstLiveQuery: Error when connection client');
- }
- return Future.value(null);
- }
- _stream = _webSocket.onMessage;
-
- _stream.listen((html.MessageEvent event) {
- final dynamic message = event.data;
- _handleMessage(message);
- }, onDone: () {
- _clientEventStreamController.sink
- .add(LiveQueryClientEvent.DISCONNECTED);
- if (_debug) {
- print('$_printConstLiveQuery: Done');
- }
- }, onError: (Object error) {
- _clientEventStreamController.sink
- .add(LiveQueryClientEvent.DISCONNECTED);
- if (_debug) {
- print(
- '$_printConstLiveQuery: Error: ${error.runtimeType.toString()}');
- }
- return Future.value(handleException(
- Exception(error), ParseApiRQ.liveQuery, _debug, 'HtmlWebSocket'));
- });
- } on Exception catch (e) {
- _connecting = false;
- _clientEventStreamController.sink.add(LiveQueryClientEvent.DISCONNECTED);
- if (_debug) {
- print('$_printConstLiveQuery: Error: ${e.toString()}');
- }
- return handleException(e, ParseApiRQ.liveQuery, _debug, 'LiveQuery');
- }
- }
-
- void _connectLiveQuery() {
- if (_webSocket == null) {
- return;
- }
- //The connect message is sent from a client to the LiveQuery server.
- //It should be the first message sent from a client after the WebSocket connection is established.
- final Map connectMessage = {
- 'op': 'connect',
- 'applicationId': _client.data.applicationId
- };
-
- if (_sendSessionId && _client.data.sessionId != null) {
- connectMessage['sessionToken'] = _client.data.sessionId;
- }
-
- if (_client.data.clientKey != null)
- connectMessage['clientKey'] = _client.data.clientKey;
- if (_client.data.masterKey != null)
- connectMessage['masterKey'] = _client.data.masterKey;
-
- if (_debug) {
- print('$_printConstLiveQuery: ConnectMessage: $connectMessage');
- }
- _webSocket.send(jsonEncode(connectMessage));
-// _channel.sink.add(jsonEncode(connectMessage));
- }
-
- void _subscribeLiveQuery(Subscription subscription) {
- if (subscription._enabled) {
- return;
- }
- subscription._enabled = true;
-
- final QueryBuilder query = subscription.query;
- final List keysToReturn = query.limiters['keys']?.split(',');
- query.limiters.clear(); //Remove limits in LiveQuery
- final String _where = query.buildQuery().replaceAll('where=', '');
-
- //Convert where condition to Map
- Map _whereMap = Map();
- if (_where != '') {
- _whereMap = json.decode(_where);
- }
-
- final Map subscribeMessage = {
- 'op': 'subscribe',
- 'requestId': subscription.requestId,
- 'query': {
- 'className': query.object.parseClassName,
- 'where': _whereMap,
- if (keysToReturn != null && keysToReturn.isNotEmpty)
- 'fields': keysToReturn
- }
- };
- if (_sendSessionId && _client.data.sessionId != null) {
- subscribeMessage['sessionToken'] = _client.data.sessionId;
- }
-
- if (_debug) {
- print('$_printConstLiveQuery: SubscribeMessage: $subscribeMessage');
- }
-
- _webSocket.send(jsonEncode(subscribeMessage));
-// _channel.sink.add(jsonEncode(subscribeMessage));
- }
-
- void _handleMessage(String message) {
- if (_debug) {
- print('$_printConstLiveQuery: Listen: $message');
- }
-
- final Map actionData = jsonDecode(message);
-
- Subscription subscription;
- if (actionData.containsKey('op') && actionData['op'] == 'connected') {
- print('ReSubScription:$_requestSubScription');
-
- _requestSubScription.values.toList().forEach((Subscription subcription) {
- _subscribeLiveQuery(subcription);
- });
- _clientEventStreamController.sink.add(LiveQueryClientEvent.CONNECTED);
- return;
- }
- if (actionData.containsKey('requestId')) {
- subscription = _requestSubScription[actionData['requestId']];
- }
- if (subscription == null) {
- return;
- }
- if (subscription.eventCallbacks.containsKey(actionData['op'])) {
- if (actionData.containsKey('object')) {
- final Map map = actionData['object'];
- final String className = map['className'];
- if (className == keyClassUser) {
- subscription.eventCallbacks[actionData['op']](
- (subscription.copyObject ??
- ParseCoreData.instance.createParseUser(null, null, null))
- .fromJson(map));
- } else {
- subscription.eventCallbacks[actionData['op']](
- (subscription.copyObject ??
- ParseCoreData.instance.createObject(className))
- .fromJson(map));
- }
- } else {
- subscription.eventCallbacks[actionData['op']](actionData);
- }
- }
- }
-}
-
-class LiveQuery {
- LiveQuery({bool debug, ParseHTTPClient client, bool autoSendSessionId}) {
- _client = client ??
- ParseHTTPClient(
- sendSessionId:
- autoSendSessionId ?? ParseCoreData().autoSendSessionId,
- securityContext: ParseCoreData().securityContext);
-
- _debug = isDebugEnabled(objectLevelDebug: debug);
- _sendSessionId =
- autoSendSessionId ?? ParseCoreData().autoSendSessionId ?? true;
- this.client = Client._getInstance(
- client: _client, debug: _debug, autoSendSessionId: _sendSessionId);
- }
-
- ParseHTTPClient _client;
- bool _debug;
- bool _sendSessionId;
- Subscription _latestSubscription;
- Client client;
-
- @deprecated
- Future subscribe(QueryBuilder query) async {
- _latestSubscription = await client.subscribe(query);
- return _latestSubscription;
- }
-
- @deprecated
- Future unSubscribe() async {
- client.unSubscribe(_latestSubscription);
- }
-
- @deprecated
- void on(LiveQueryEvent op, Function callback) {
- _latestSubscription.on(op, callback);
- }
-}
diff --git a/lib/src/storage/core_store.dart b/lib/src/storage/core_store.dart
deleted file mode 100644
index b7d2fe450..000000000
--- a/lib/src/storage/core_store.dart
+++ /dev/null
@@ -1,31 +0,0 @@
-part of flutter_parse_sdk;
-
-abstract class CoreStore {
- Future containsKey(String key);
-
- Future get(String key);
-
- Future getBool(String key);
-
- Future getInt(String key);
-
- Future getDouble(String key);
-
- Future getString(String key);
-
- Future> getStringList(String key);
-
- Future setBool(String key, bool value);
-
- Future setInt(String key, int value);
-
- Future setDouble(String key, double value);
-
- Future setString(String key, String value);
-
- Future setStringList(String key, List values);
-
- Future remove(String key);
-
- Future clear();
-}
diff --git a/CHANGELOG.md b/packages/dart/CHANGELOG.md
similarity index 84%
rename from CHANGELOG.md
rename to packages/dart/CHANGELOG.md
index 14398f151..a5803d9ef 100644
--- a/CHANGELOG.md
+++ b/packages/dart/CHANGELOG.md
@@ -1,3 +1,12 @@
+## 1.0.28
+- Added full web support
+- split this package in a dart and a flutter one
+ - [flutter package](https://pub.dev/packages/parse_server_sdk_flutter)
+ - [dart package](https://pub.dev/packages/parse_server_sdk)
+- Moved ParseHTTPClient to Dio [#459](https://github.com/parse-community/Parse-SDK-Flutter/pull/459)
+- Bug fixes
+- general improvements
+
## 1.0.27
User login / signUp / loginAnonymous delete SessionId stored in device before calling server
diff --git a/packages/dart/LICENSE b/packages/dart/LICENSE
new file mode 100644
index 000000000..38f25d526
--- /dev/null
+++ b/packages/dart/LICENSE
@@ -0,0 +1,31 @@
+BSD 3-Clause License
+
+Copyright (c) 2019, Phill Wiggins.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=======
diff --git a/packages/dart/README.md b/packages/dart/README.md
new file mode 100644
index 000000000..b57447dbc
--- /dev/null
+++ b/packages/dart/README.md
@@ -0,0 +1,853 @@
+
+
+
+
+
+---
+
+**This is now a dart package. The Flutter package was moved [here](https://pub.dev/packages/parse_server_sdk_flutter).**
+
+**THIS README IS WORK IN PROGRESS!**
+Help out to improve this README on [GitHub](https://github.com/parse-community/Parse-SDK-Flutter/edit/master/packages/dart/README.md).
+
+## Parse For Dart!
+This is a Dart package that allows communication with a Parse Server, (https://parseplatform.org) either hosted on your own server or another, like (http://Back4App.com).
+
+This is a work in progress and we are consistently updating it. Please let us know if you think anything needs changing/adding, and more than ever, please do join in on this project. (Even if it is just to improve our documentation)
+
+## Getting Started
+To install, either add to your pubspec.yaml
+```yml
+dependencies:
+ parse_server_sdk: ^1.0.28
+```
+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.
+
+
+Once you have the library added to your project, upon first call to your app (Similar to what your application class would be) add the following...
+
+```dart
+await Parse().initialize(
+ keyApplicationId,
+ keyParseServerUrl,
+ );
+```
+
+If you want to use secure storage or use the Flutter web/desktop SDK, please change to the below instance of CoreStorage as it has no dependencies on Flutter.
+
+**The `CoreStoreSembastImp` does not encrypt the data on web!** (Web is not safe anyway. Encrypt fields manually as needed.)
+```dart
+
+await Parse().initialize(
+ keyParseApplicationId,
+ keyParseServerUrl,
+ coreStore: await CoreStoreSembastImp.getInstance("/data"));
+```
+It's possible to add other parameters to work with your instance of Parse Server:-
+
+```dart
+ await Parse().initialize(
+ keyApplicationId,
+ keyParseServerUrl,
+ masterKey: keyParseMasterKey, // Required for Back4App and others
+ clientKey: keyParseClientKey, // Required for some setups
+ debug: true, // When enabled, prints logs to console
+ liveQueryUrl: keyLiveQueryUrl, // Required if using LiveQuery
+ autoSendSessionId: true, // Required for authentication and ACL
+ securityContext: securityContext, // Again, required for some setups
+ coreStore: CoreStoreMemoryImp()); // Non persistent mode (default): Sdk will store everything in memmore instead of using Sembast as an internal DB.
+```
+⚠️ Please note that the master key should only be used in safe environments and never on client side ‼️ Using this package on a server should be fine.
+
+#### Early Web support
+Currently this requires adding `X-Parse-Installation-Id` as an allowed header to parse-server.
+When running directly via docker, set the env var `PARSE_SERVER_ALLOW_HEADERS=X-Parse-Installation-Id`.
+When running via express, set [ParseServerOptions](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) `allowHeaders: ['X-Parse-Installation-Id']`.
+
+Be aware that for web ParseInstallation does include app name, version or package identifier.
+
+
+## Objects
+You can create custom objects by calling:
+```dart
+var dietPlan = ParseObject('DietPlan')
+ ..set('Name', 'Ketogenic')
+ ..set('Fat', 65);
+await dietPlan.save();
+```
+Or update existing object by its objectId by calling:
+```dart
+var dietPlan = ParseObject('DietPlan')
+ ..objectId = 'R5EonpUDWy'
+ ..set('Fat', 70);
+await dietPlan.save();
+```
+Verify that the object has been successfully saved using
+```dart
+var response = await dietPlan.save();
+if (response.success) {
+ dietPlan = response.results.first;
+}
+```
+Types supported:
+ * String
+ * Double
+ * Int
+ * Boolean
+ * DateTime
+ * File
+ * Geopoint
+ * ParseObject/ParseUser (Pointer)
+ * Map
+ * List (all types supported)
+
+You then have the ability to do the following with that object:
+The features available are:-
+ * Get
+ * GetAll
+ * Create
+ * Save
+ * Query - By object Id
+ * Delete
+ * Complex queries as shown above
+ * Pin
+ * Plenty more
+ * Counters
+ * Array Operators
+
+## Custom Objects
+You can create your own `ParseObjects` or convert your existing objects into Parse Objects by doing the following:
+
+```dart
+class DietPlan extends ParseObject implements ParseCloneable {
+
+ DietPlan() : super(_keyTableName);
+ DietPlan.clone(): this();
+
+ /// Looks strangely hacky but due to Flutter not using reflection, we have to
+ /// mimic a clone
+ @override clone(Map map) => DietPlan.clone()..fromJson(map);
+
+ static const String _keyTableName = 'Diet_Plans';
+ static const String keyName = 'Name';
+
+ String get name => get(keyName);
+ set name(String name) => set(keyName, name);
+}
+
+```
+
+When receiving an `ParseObject` from the SDK, you can often provide an instance of your custom object as an copy object.
+To always use your custom object class, you can register your subclass at the initialization of the SDK.
+```dart
+Parse().initialize(
+ ...,
+ registeredSubClassMap: {
+ 'Diet_Plans': () => DietPlan(),
+ },
+ parseUserConstructor: (username, password, emailAddress, {client, debug, sessionToken}) => CustomParseUser(username, password, emailAddress),
+);
+```
+Additionally you can register `SubClasses` after the initialization of the SDK.
+```dart
+ParseCoreData().registerSubClass('Diet_Plans', () => DietPlan());
+ParseCoreData().registerUserSubClass((username, password, emailAddress, {client, debug, sessionToken}) => CustomParseUser(username, password, emailAddress));
+```
+Providing a `ParseObject` as described above should still work, even if you have registered a different `SubClass`.
+
+For custom file classes have a lock at [here](#File).
+
+## Add new values to objects
+To add a variable to an object call and retrieve it, call
+
+```dart
+dietPlan.set('RandomInt', 8);
+var randomInt = dietPlan.get('RandomInt');
+```
+
+## Save objects using pins
+You can now save an object by calling `.pin()` on an instance of an object
+
+```dart
+dietPlan.pin();
+```
+
+and to retrieve it
+
+```dart
+var dietPlan = DietPlan().fromPin('OBJECT ID OF OBJECT');
+```
+
+## Storage
+We now have 2 types of storage, secure and unsecure. We currently rely on 2 third party options:
+
+- SharedPreferences
+- Sembast
+Sembast offers secured storage, whilst SharePreferences wraps NSUserDefaults (on iOS) and SharedPreferences (on Android).
+
+The storage method is defined in the parameter __coreStore__ in Parse().initialize
+
+Check sample code for options
+
+## Increment Counter values in objects
+Retrieve it, call
+
+```dart
+var response = await dietPlan.increment("count", 1);
+
+```
+or using with save function
+
+```dart
+dietPlan.setIncrement('count', 1);
+dietPlan.setDecrement('count', 1);
+var response = dietPlan.save()
+
+```
+
+## Array Operator in objects
+Retrieve it, call
+
+```dart
+var response = await dietPlan.add("listKeywords", ["a", "a","d"]);
+
+var response = await dietPlan.addUnique("listKeywords", ["a", "a","d"]);
+
+var response = await dietPlan.remove("listKeywords", ["a"]);
+
+```
+or using with save function
+
+```dart
+dietPlan.setAdd('listKeywords', ['a','a','d']);
+dietPlan.setAddUnique('listKeywords', ['a','a','d']);
+dietPlan.setRemove('listKeywords', ['a']);
+var response = dietPlan.save()
+```
+
+## Queries
+Once you have setup the project and initialised the instance, you can then retreive data from your server by calling:
+```dart
+var apiResponse = await ParseObject('ParseTableName').getAll();
+
+if (apiResponse.success){
+ for (var testObject in apiResponse.result) {
+ print(ApplicationConstants.APP_NAME + ": " + testObject.toString());
+ }
+}
+```
+Or you can get an object by its objectId:
+
+```dart
+var dietPlan = await DietPlan().getObject('R5EonpUDWy');
+
+if (dietPlan.success) {
+ print(ApplicationConstants.keyAppName + ": " + (dietPlan.result as DietPlan).toString());
+} else {
+ print(ApplicationConstants.keyAppName + ": " + dietPlan.exception.message);
+}
+```
+
+## Complex queries
+You can create complex queries to really put your database to the test:
+
+```dart
+var queryBuilder = QueryBuilder(DietPlan())
+ ..startsWith(DietPlan.keyName, "Keto")
+ ..greaterThan(DietPlan.keyFat, 64)
+ ..lessThan(DietPlan.keyFat, 66)
+ ..equals(DietPlan.keyCarbs, 5);
+
+var response = await queryBuilder.query();
+
+if (response.success) {
+ print(ApplicationConstants.keyAppName + ": " + ((response.results as List).first as DietPlan).toString());
+} else {
+ print(ApplicationConstants.keyAppName + ": " + response.exception.message);
+}
+```
+
+if you want to find objects that match one of several queries, you can use __QueryBuilder.or__ method to construct a query that is an OR of the queries passed in. For instance if you want to find players who either have a lot of wins or a few wins, you can do:
+```dart
+ParseObject playerObject = ParseObject("Player");
+
+QueryBuilder lotsOfWins =
+ QueryBuilder(playerObject))
+ ..whereGreaterThan('wins', 50);
+
+QueryBuilder fewWins =
+ QueryBuilder(playerObject)
+ ..whereLessThan('wins', 5);
+
+QueryBuilder mainQuery = QueryBuilder.or(
+ playerObject,
+ [lotsOfWins, fewWins],
+ );
+
+var apiResponse = await mainQuery.query();
+```
+
+The features available are:-
+ * Equals
+ * Contains
+ * LessThan
+ * LessThanOrEqualTo
+ * GreaterThan
+ * GreaterThanOrEqualTo
+ * NotEqualTo
+ * StartsWith
+ * EndsWith
+ * Exists
+ * Near
+ * WithinMiles
+ * WithinKilometers
+ * WithinRadians
+ * WithinGeoBox
+ * MatchesQuery
+ * DoesNotMatchQuery
+ * MatchesKeyInQuery
+ * DoesNotMatchKeyInQuery
+ * Regex
+ * Order
+ * Limit
+ * Skip
+ * Ascending
+ * Descending
+ * Plenty more!
+
+## Relational queries
+If you want to retrieve objects where a field contains an object that matches another query, you can use the
+__whereMatchesQuery__ condition.
+For example, imagine you have Post class and a Comment class, where each Comment has a pointer to its parent Post.
+You can find comments on posts with images by doing:
+
+```dart
+QueryBuilder queryPost =
+ QueryBuilder(ParseObject('Post'))
+ ..whereValueExists('image', true);
+
+QueryBuilder queryComment =
+ QueryBuilder(ParseObject('Comment'))
+ ..whereMatchesQuery('post', queryPost);
+
+var apiResponse = await queryComment.query();
+```
+
+If you want to retrieve objects where a field contains an object that does not match another query, you can use the
+__whereDoesNotMatchQuery__ condition.
+Imagine you have Post class and a Comment class, where each Comment has a pointer to its parent Post.
+You can find comments on posts without images by doing:
+
+```dart
+QueryBuilder queryPost =
+ QueryBuilder(ParseObject('Post'))
+ ..whereValueExists('image', true);
+
+QueryBuilder queryComment =
+ QueryBuilder(ParseObject('Comment'))
+ ..whereDoesNotMatchQuery('post', queryPost);
+
+var apiResponse = await queryComment.query();
+```
+
+You can use the __whereMatchesKeyInQuery__ method to get objects where a key matches the value of a key in a set of objects resulting from another query. For example, if you have a class containing sports teams and you store a user’s hometown in the user class, you can issue one query to find the list of users whose hometown teams have winning records. The query would look like::
+
+```dart
+QueryBuilder teamQuery =
+ QueryBuilder(ParseObject('Team'))
+ ..whereGreaterThan('winPct', 0.5);
+
+QueryBuilder userQuery =
+ QueryBuilderParseUser.forQuery())
+ ..whereMatchesKeyInQuery('hometown', 'city', teamQuery);
+
+var apiResponse = await userQuery.query();
+```
+
+Conversely, to get objects where a key does not match the value of a key in a set of objects resulting from another query, use __whereDoesNotMatchKeyInQuery__. For example, to find users whose hometown teams have losing records:
+
+```dart
+QueryBuilder teamQuery =
+ QueryBuilder(ParseObject('Team'))
+ ..whereGreaterThan('winPct', 0.5);
+
+QueryBuilder losingUserQuery =
+ QueryBuilderParseUser.forQuery())
+ ..whereDoesNotMatchKeyInQuery('hometown', 'city', teamQuery);
+
+var apiResponse = await losingUserQuery.query();
+```
+
+To filter rows based on objectId’s from pointers in a second table, you can use dot notation:
+```dart
+QueryBuilder rolesOfTypeX =
+ QueryBuilder(ParseObject('Role'))
+ ..whereEqualTo('type', 'x');
+
+QueryBuilder groupsWithRoleX =
+ QueryBuilder(ParseObject('Group')))
+ ..whereMatchesKeyInQuery('objectId', 'belongsTo.objectId', rolesOfTypeX);
+
+var apiResponse = await groupsWithRoleX.query();
+```
+
+## Counting Objects
+If you only care about the number of games played by a particular player:
+
+```dart
+QueryBuilder queryPlayers =
+ QueryBuilder(ParseObject('GameScore'))
+ ..whereEqualTo('playerName', 'Jonathan Walsh');
+var apiResponse = await queryPlayers.count();
+if (apiResponse.success && apiResponse.result != null) {
+ int countGames = apiResponse.count;
+}
+```
+
+## Live Queries
+This tool allows you to subscribe to a QueryBuilder you are interested in. Once subscribed, the server will notify clients
+whenever a ParseObject that matches the QueryBuilder is created or updated, in real-time.
+
+Parse LiveQuery contains two parts, the LiveQuery server and the LiveQuery clients. In order to use live queries, you need
+to set up both of them.
+
+The Parse Server configuration guide on the server is found here https://docs.parseplatform.org/parse-server/guide/#live-queries and is not part of this documentation.
+
+Initialize the Parse Live Query by entering the parameter liveQueryUrl in Parse().initialize:
+```dart
+Parse().initialize(
+ keyApplicationId,
+ keyParseServerUrl,
+ clientKey: keyParseClientKey,
+ debug: true,
+ liveQueryUrl: keyLiveQueryUrl,
+ autoSendSessionId: true);
+```
+
+Declare LiveQuery:
+```dart
+final LiveQuery liveQuery = LiveQuery();
+```
+
+Set the QueryBuilder that will be monitored by LiveQuery:
+```dart
+QueryBuilder query =
+ QueryBuilder(ParseObject('TestAPI'))
+ ..whereEqualTo('intNumber', 1);
+```
+__Create a subscription__
+You’ll get the LiveQuery events through this subscription.
+The first time you call subscribe, we’ll try to open the WebSocket connection to the LiveQuery server for you.
+
+```dart
+Subscription subscription = await liveQuery.client.subscribe(query);
+```
+
+__Event Handling__
+We define several types of events you’ll get through a subscription object:
+
+__Create event__
+When a new ParseObject is created and it fulfills the QueryBuilder you subscribe, you’ll get this event.
+The object is the ParseObject which was created.
+```dart
+subscription.on(LiveQueryEvent.create, (value) {
+ print('*** CREATE ***: ${DateTime.now().toString()}\n $value ');
+ print((value as ParseObject).objectId);
+ print((value as ParseObject).updatedAt);
+ print((value as ParseObject).createdAt);
+ print((value as ParseObject).get('objectId'));
+ print((value as ParseObject).get('updatedAt'));
+ print((value as ParseObject).get('createdAt'));
+});
+```
+
+__Update event__
+When an existing ParseObject which fulfills the QueryBuilder you subscribe is updated (The ParseObject fulfills the
+QueryBuilder before and after changes), you’ll get this event.
+The object is the ParseObject which was updated. Its content is the latest value of the ParseObject.
+```dart
+subscription.on(LiveQueryEvent.update, (value) {
+ print('*** UPDATE ***: ${DateTime.now().toString()}\n $value ');
+ print((value as ParseObject).objectId);
+ print((value as ParseObject).updatedAt);
+ print((value as ParseObject).createdAt);
+ print((value as ParseObject).get('objectId'));
+ print((value as ParseObject).get('updatedAt'));
+ print((value as ParseObject).get('createdAt'));
+});
+```
+
+__Enter event__
+When an existing ParseObject’s old value does not fulfill the QueryBuilder but its new value fulfills the QueryBuilder,
+you’ll get this event. The object is the ParseObject which enters the QueryBuilder.
+Its content is the latest value of the ParseObject.
+```dart
+subscription.on(LiveQueryEvent.enter, (value) {
+ print('*** ENTER ***: ${DateTime.now().toString()}\n $value ');
+ print((value as ParseObject).objectId);
+ print((value as ParseObject).updatedAt);
+ print((value as ParseObject).createdAt);
+ print((value as ParseObject).get('objectId'));
+ print((value as ParseObject).get('updatedAt'));
+ print((value as ParseObject).get('createdAt'));
+});
+```
+
+__Leave event__
+When an existing ParseObject’s old value fulfills the QueryBuilder but its new value doesn’t fulfill the QueryBuilder,
+you’ll get this event. The object is the ParseObject which leaves the QueryBuilder.
+Its content is the latest value of the ParseObject.
+```dart
+subscription.on(LiveQueryEvent.leave, (value) {
+ print('*** LEAVE ***: ${DateTime.now().toString()}\n $value ');
+ print((value as ParseObject).objectId);
+ print((value as ParseObject).updatedAt);
+ print((value as ParseObject).createdAt);
+ print((value as ParseObject).get('objectId'));
+ print((value as ParseObject).get('updatedAt'));
+ print((value as ParseObject).get('createdAt'));
+});
+```
+
+__Delete event__
+When an existing ParseObject which fulfills the QueryBuilder is deleted, you’ll get this event.
+The object is the ParseObject which is deleted
+```dart
+subscription.on(LiveQueryEvent.delete, (value) {
+ print('*** DELETE ***: ${DateTime.now().toString()}\n $value ');
+ print((value as ParseObject).objectId);
+ print((value as ParseObject).updatedAt);
+ print((value as ParseObject).createdAt);
+ print((value as ParseObject).get('objectId'));
+ print((value as ParseObject).get('updatedAt'));
+ print((value as ParseObject).get('createdAt'));
+});
+```
+
+__Unsubscribe__
+If you would like to stop receiving events from a QueryBuilder, you can just unsubscribe the subscription.
+After that, you won’t get any events from the subscription object and will close the WebSocket connection to the
+LiveQuery server.
+
+```dart
+liveQuery.client.unSubscribe(subscription);
+```
+
+__Disconnection__
+In case the client's connection to the server breaks,
+LiveQuery will automatically try to reconnect.
+LiveQuery will wait at increasing intervals between reconnection attempts.
+By default, these intervals are set to `[0, 500, 1000, 2000, 5000, 10000]` for mobile and `[0, 500, 1000, 2000, 5000]` for web.
+You can change these by providing a custom list using the `liveListRetryIntervals` parameter at `Parse.initialize()` ("-1" means "do not try to reconnect").
+
+## ParseLiveList
+ParseLiveList makes implementing a dynamic List as simple as possible.
+
+### General Use
+It ships with the ParseLiveList class itself, this class manages all elements of the list, sorts them,
+keeps itself up to date and Notifies you on changes.
+
+### included Sub-Objects
+By default, ParseLiveQuery will provide you with all the objects you included in your Query like this:
+```dart
+queryBuilder.includeObject(/*List of all the included sub-objects*/);
+```
+ParseLiveList will not listen for updates on this objects by default.
+To activate listening for updates on all included objects, add `listenOnAllSubItems: true` to your ParseLiveListWidgets constructor.
+If you want ParseLiveList to listen for updates on only some sub-objects, use `listeningIncludes: const [/*all the included sub-objects*/]` instead.
+Just as QueryBuilder, ParseLiveList supports nested sub-objects too.
+
+**NOTE:** To use this features you have to enable [Live Queries](#live-queries) first.
+
+## Users
+You can create and control users just as normal using this SDK.
+
+To register a user, first create one :
+```dart
+var user = ParseUser().create("TestFlutter", "TestPassword123", "TestFlutterSDK@gmail.com");
+```
+Then have the user sign up:
+
+```dart
+var response = await user.signUp();
+if (response.success) user = response.result;
+```
+You can also login with the user:
+```dart
+var response = await user.login();
+if (response.success) user = response.result;
+```
+You can also logout with the user:
+```dart
+var response = await user.logout();
+if (response.success) {
+ print('User logout');
+}
+```
+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.
+```dart
+user = ParseUser.currentUser();
+```
+
+To add additional columns to the user:
+```dart
+var user = ParseUser("TestFlutter", "TestPassword123", "TestFlutterSDK@gmail.com")
+ ..set("userLocation", "FlutterLand");
+```
+
+Other user features are:-
+ * Request Password Reset
+ * Verification Email Request
+ * Get all users
+ * Save
+ * Destroy user
+ * Queries
+
+ ## Facebook, OAuth and 3rd Party Login/User
+
+ Usually, each provider will provide their own library for logins, but the loginWith method on ParseUser accepts a name of provider, then a Map with the authentication details required.
+ For Facebook and the example below, we used the library provided at https://pub.dev/packages/flutter_facebook_login
+
+ ```
+ Future goToFacebookLogin() async {
+ final FacebookLogin facebookLogin = FacebookLogin();
+ final FacebookLoginResult result = await facebookLogin.logInWithReadPermissions(['email']);
+
+ switch (result.status) {
+ case FacebookLoginStatus.loggedIn:
+ final ParseResponse response = await ParseUser.loginWith(
+ 'facebook',
+ facebook(result.accessToken.token,
+ result.accessToken.userId,
+ result.accessToken.expires));
+
+ if (response.success) {
+ // User is logged in, test with ParseUser.currentUser()
+ }
+ break;
+ case FacebookLoginStatus.cancelledByUser:
+ // User cancelled
+ break;
+ case FacebookLoginStatus.error:
+ // Error
+ break;
+ }
+ }
+```
+
+For Google and the example below, we used the library provided at https://pub.dev/packages/google_sign_in
+
+```
+class OAuthLogin {
+ final GoogleSignIn _googleSignIn = GoogleSignIn( scopes: ['email', 'https://www.googleapis.com/auth/contacts.readonly'] );
+
+ sigInGoogle() async {
+ GoogleSignInAccount account = await _googleSignIn.signIn();
+ GoogleSignInAuthentication authentication = await account.authentication;
+ await ParseUser.loginWith(
+ 'google',
+ google(_googleSignIn.currentUser.id,
+ authentication.accessToken,
+ authentication.idToken));
+ }
+}
+```
+
+## Security for Objects - ParseACL
+For any object, you can specify which users are allowed to read the object, and which users are allowed to modify an object.
+To support this type of security, each object has an access control list, implemented by the __ParseACL__ class.
+
+If ParseACL is not specified (with the exception of the ParseUser class) all objects are set to Public for read and write.
+The simplest way to use a ParseACL is to specify that an object may only be read or written by a single user.
+To create such an object, there must first be a logged in ParseUser. Then, new ParseACL(user) generates a ParseACL that
+limits access to that user. An object’s ACL is updated when the object is saved, like any other property.
+
+```dart
+ParseUser user = await ParseUser.currentUser() as ParseUser;
+ParseACL parseACL = ParseACL(owner: user);
+
+ParseObject parseObject = ParseObject("TestAPI");
+...
+parseObject.setACL(parseACL);
+var apiResponse = await parseObject.save();
+```
+Permissions can also be granted on a per-user basis. You can add permissions individually to a ParseACL using
+__setReadAccess__ and __setWriteAccess__
+```dart
+ParseUser user = await ParseUser.currentUser() as ParseUser;
+ParseACL parseACL = ParseACL();
+//grant total access to current user
+parseACL.setReadAccess(userId: user.objectId, allowed: true);
+parseACL.setWriteAccess(userId: user.objectId, allowed: true);
+//grant read access to userId: 'TjRuDjuSAO'
+parseACL.setReadAccess(userId: 'TjRuDjuSAO', allowed: true);
+parseACL.setWriteAccess(userId: 'TjRuDjuSAO', allowed: false);
+
+ParseObject parseObject = ParseObject("TestAPI");
+...
+parseObject.setACL(parseACL);
+var apiResponse = await parseObject.save();
+```
+You can also grant permissions to all users at once using setPublicReadAccess and setPublicWriteAccess.
+```dart
+ParseACL parseACL = ParseACL();
+parseACL.setPublicReadAccess(allowed: true);
+parseACL.setPublicWriteAccess(allowed: true);
+
+ParseObject parseObject = ParseObject("TestAPI");
+...
+parseObject.setACL(parseACL);
+var apiResponse = await parseObject.save();
+```
+Operations that are forbidden, such as deleting an object that you do not have write access to, result in a
+ParseError with code 101: 'ObjectNotFound'.
+For security purposes, this prevents clients from distinguishing which object ids exist but are secured, versus which
+object ids do not exist at all.
+
+You can retrieve the ACL list of an object using:
+```dart
+ParseACL parseACL = parseObject.getACL();
+```
+
+## Config
+The SDK supports Parse Config. A map of all configs can be grabbed from the server by calling :
+```dart
+var response = await ParseConfig().getConfigs();
+```
+
+and to add a config:
+```dart
+ParseConfig().addConfig('TestConfig', 'testing');
+```
+
+## Cloud Functions
+The SDK supports call Cloud Functions.
+
+Executes a cloud function that returns a ParseObject type
+```dart
+final ParseCloudFunction function = ParseCloudFunction('hello');
+final ParseResponse result =
+ await function.executeObjectFunction();
+if (result.success) {
+ if (result.result is ParseObject) {
+ final ParseObject parseObject = result.result;
+ print(parseObject.className);
+ }
+}
+```
+
+Executes a cloud function with parameters
+```dart
+final ParseCloudFunction function = ParseCloudFunction('hello');
+final Map params = {'plan': 'paid'};
+function.execute(parameters: params);
+```
+
+## Relation
+
+The SDK supports Relation.
+
+To add relation to object:
+
+```dart
+dietPlan.addRelation('fruits', [ParseObject("Fruits")..set("objectId", "XGadzYxnac")]);
+```
+
+To remove relation to object:
+
+```dart
+dietPlan.removeRelation('fruits', [ParseObject("Fruits")..set("objectId", "XGadzYxnac")]);
+```
+
+To Retrive a relation instance for user, call:
+```dart
+final relation = dietPlan.getRelation('fruits');
+```
+
+and then you can add a relation to the passed in object:
+```
+relation.add(dietPlan);
+final result = await user.save();
+```
+
+To retrieve objects that are members of Relation field of a parent object:
+```dart
+QueryBuilder query =
+ QueryBuilder(ParseObject('Fruits'))
+ ..whereRelatedTo('fruits', 'DietPlan', DietPlan.objectId);
+```
+
+## File
+There are three different file classes in this SDK:
+- `ParseFileBase` is and abstract class and is the foundation of every file class that can be handled by this SDK.
+- `ParseFile` (former the only file class in the SDK) extends ParseFileBase and is by default used as the file class on every platform but web.
+This class uses a `File` from `dart:io` for storing the raw file.
+- `ParseWebFile` is the equivalent to ParseFile used at Flutter Web.
+This class uses an `Uint8List` for storing the raw file.
+
+These classes are used by default to represent files, but you can also build your own class extending ParseFileBase and provide a custom `ParseFileConstructor` similar to the `SubClasses`.
+
+Have a look at the example application for a small (non web) example.
+
+When uploading or downloading a file, you can use the `progressCallback`-parameter to track the progress of the http request.
+```dart
+//A short example for showing an image from a ParseFileBase
+Widget buildImage(ParseFileBase image){
+ return FutureBuilder(
+ future: image.download(),
+ builder: (BuildContext context,
+ AsyncSnapshot snapshot) {
+ if (snapshot.hasData) {
+ if (kIsWeb) {
+ return Image.memory((snapshot.data as ParseWebFile).file);
+ } else {
+ return Image.file((snapshot.data as ParseFile).file);
+ }
+ } else {
+ return CircularProgressIndicator();
+ }
+ },
+ );
+}
+```
+```dart
+//A short example for storing a selected picture
+//libraries: image_picker (https://pub.dev/packages/image_picker), image_picker_for_web (https://pub.dev/packages/image_picker_for_web)
+PickedFile pickedFile = await ImagePicker().getImage(source: ImageSource.gallery);
+ParseFileBase parseFile;
+if (kIsWeb) {
+ //Seems weird, but this lets you get the data from the selected file as an Uint8List very easily.
+ ParseWebFile file = ParseWebFile(null, name: null, url: pickedFile.path);
+ await file.download();
+ parseFile = ParseWebFile(file.file, name: file.name);
+} else {
+ parseFile = ParseFile(File(pickedFile.path));
+}
+someParseObject.set("image", parseFile);
+//This saves the ParseObject as well as all of its children, and the ParseFileBase is such a child.
+await someParseObject.save();
+```
+```dart
+//progressCallback example
+file.upload(progressCallback: (int count, int total) => print("$count of $total"));
+```
+## Other Features of this library
+Main:
+* Installation (View the example application)
+* GeoPoints (View the example application)
+* Persistent storage
+* Debug Mode - Logging API calls
+* Manage Session ID's tokens
+
+User:
+* Queries
+* Anonymous (View the example application)
+* 3rd Party Authentication
+
+Objects:
+* Create new object
+* Extend Parse Object and create local objects that can be saved and retreived
+* Queries
+
+## Author:-
+This project was authored by Phill Wiggins. You can contact me at phill.wiggins@gmail.com
diff --git a/lib/parse_server_sdk.dart b/packages/dart/lib/parse_server_sdk.dart
similarity index 67%
rename from lib/parse_server_sdk.dart
rename to packages/dart/lib/parse_server_sdk.dart
index 606fd131c..7681b39ab 100644
--- a/lib/parse_server_sdk.dart
+++ b/packages/dart/lib/parse_server_sdk.dart
@@ -5,39 +5,30 @@ import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
-import 'dart:ui' as ui;
-import 'package:devicelocale/devicelocale.dart';
-import 'package:http/http.dart';
-import 'package:http/io_client.dart';
+import 'package:dio/dio.dart' hide Options;
+import 'package:dio/dio.dart' as dio show Options;
import 'package:meta/meta.dart';
-import 'package:package_info/package_info.dart';
+import 'package:mime_type/mime_type.dart';
+import 'package:parse_server_sdk/src/network/http_client_adapter.dart';
+import 'package:parse_server_sdk/src/network/parse_websocket.dart'
+ as parse_web_socket;
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:sembast_web/sembast_web.dart';
import 'package:uuid/uuid.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:xxtea/xxtea.dart';
-export 'src/network/parse_live_query.dart'
- if (dart.library.js) 'src/network/parse_live_query_web.dart';
-export 'src/utils/parse_live_list.dart';
-
-part 'package:parse_server_sdk/src/data/core_store.dart';
-part 'package:parse_server_sdk/src/data/parse_subclass_handler.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';
-part 'package:parse_server_sdk/src/objects/response/parse_response_utils.dart';
-part 'package:parse_server_sdk/src/objects/response/parse_success_no_results.dart';
-part 'package:parse_server_sdk/src/storage/core_store_sem_impl.dart';
-part 'package:parse_server_sdk/src/storage/core_store_sp_impl.dart';
-part 'package:parse_server_sdk/src/storage/xxtea_codec.dart';
part 'src/base/parse_constants.dart';
part 'src/data/parse_core_data.dart';
+part 'src/data/parse_subclass_handler.dart';
part 'src/enums/parse_enum_api_rq.dart';
+part 'src/network/dio-options.dart';
+part 'src/network/parse_connectivity.dart';
part 'src/network/parse_http_client.dart';
+part 'src/network/parse_live_query.dart';
part 'src/network/parse_query.dart';
part 'src/objects/parse_acl.dart';
part 'src/objects/parse_base.dart';
@@ -56,10 +47,20 @@ part 'src/objects/parse_relation.dart';
part 'src/objects/parse_response.dart';
part 'src/objects/parse_session.dart';
part 'src/objects/parse_user.dart';
+part 'src/objects/response/parse_error_response.dart';
+part 'src/objects/response/parse_exception_response.dart';
+part 'src/objects/response/parse_response_builder.dart';
+part 'src/objects/response/parse_response_utils.dart';
+part 'src/objects/response/parse_success_no_results.dart';
+part 'src/storage/core_store.dart';
+part 'src/storage/core_store_memory.dart';
+part 'src/storage/core_store_sem_impl.dart';
+part 'src/storage/xxtea_codec.dart';
part 'src/utils/parse_date_format.dart';
part 'src/utils/parse_decoder.dart';
part 'src/utils/parse_encoder.dart';
part 'src/utils/parse_file_extensions.dart';
+part 'src/utils/parse_live_list.dart';
part 'src/utils/parse_logger.dart';
part 'src/utils/parse_login_helpers.dart';
part 'src/utils/parse_utils.dart';
@@ -74,17 +75,20 @@ class Parse {
///
/// ```
/// Parse().initialize(
- // "PARSE_APP_ID",
- // "https://parse.myaddress.com/parse/,
- // masterKey: "asd23rjh234r234r234r",
- // debug: true,
- // liveQuery: true);
- // ```
+ /// "PARSE_APP_ID",
+ /// "https://parse.myaddress.com/parse/,
+ /// masterKey: "asd23rjh234r234r234r",
+ /// debug: true,
+ /// liveQuery: true);
+ /// ```
Future initialize(
String appId,
String serverUrl, {
bool debug = false,
- String appName = '',
+ String appName,
+ String appVersion,
+ String appPackageName,
+ String locale,
String liveQueryUrl,
String clientKey,
String masterKey,
@@ -96,6 +100,9 @@ class Parse {
ParseUserConstructor parseUserConstructor,
ParseFileConstructor parseFileConstructor,
List liveListRetryIntervals,
+ ParseConnectivityProvider connectivityProvider,
+ String fileDirectory,
+ Stream appResumedStream,
}) async {
final String url = removeTrailingSlash(serverUrl);
@@ -104,6 +111,9 @@ class Parse {
url,
debug: debug,
appName: appName,
+ appVersion: appVersion,
+ appPackageName: appPackageName,
+ locale: locale,
liveQueryUrl: liveQueryUrl,
masterKey: masterKey,
clientKey: clientKey,
@@ -115,6 +125,9 @@ class Parse {
parseUserConstructor: parseUserConstructor,
parseFileConstructor: parseFileConstructor,
liveListRetryIntervals: liveListRetryIntervals,
+ connectivityProvider: connectivityProvider,
+ fileDirectory: fileDirectory,
+ appResumedStream: appResumedStream,
);
_hasBeenInitialized = true;
@@ -140,8 +153,8 @@ class Parse {
const ParseApiRQ type = ParseApiRQ.healthCheck;
try {
- final Response response =
- await _client.get('${ParseCoreData().serverUrl}$keyEndPointHealth');
+ 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/packages/dart/lib/src/base/parse_constants.dart
similarity index 96%
rename from lib/src/base/parse_constants.dart
rename to packages/dart/lib/src/base/parse_constants.dart
index 56a3d9962..25fe69010 100644
--- a/lib/src/base/parse_constants.dart
+++ b/packages/dart/lib/src/base/parse_constants.dart
@@ -1,7 +1,7 @@
part of flutter_parse_sdk;
// Library
-const String keySdkVersion = '1.0.27';
+const String keySdkVersion = '1.0.28';
const String keyLibraryName = 'Flutter Parse SDK';
// End Points
@@ -46,7 +46,7 @@ const String keyHeaderSessionToken = 'X-Parse-Session-Token';
const String keyHeaderRevocableSession = 'X-Parse-Revocable-Session';
const String keyHeaderUserAgent = 'user-agent';
const String keyHeaderApplicationId = 'X-Parse-Application-Id';
-const String keyHeaderContentType = 'Content-Type';
+const String keyHeaderContentType = Headers.contentTypeHeader;
const String keyHeaderContentTypeJson = 'application/json';
const String keyHeaderMasterKey = 'X-Parse-Master-Key';
const String keyHeaderClientKey = 'X-Parse-Client-Key';
diff --git a/lib/src/data/app_info.dart b/packages/dart/lib/src/data/app_info.dart
similarity index 100%
rename from lib/src/data/app_info.dart
rename to packages/dart/lib/src/data/app_info.dart
diff --git a/lib/src/data/parse_core_data.dart b/packages/dart/lib/src/data/parse_core_data.dart
similarity index 80%
rename from lib/src/data/parse_core_data.dart
rename to packages/dart/lib/src/data/parse_core_data.dart
index 02b37c2af..de4fe75c3 100644
--- a/lib/src/data/parse_core_data.dart
+++ b/packages/dart/lib/src/data/parse_core_data.dart
@@ -19,6 +19,9 @@ class ParseCoreData {
String serverUrl, {
bool debug,
String appName,
+ String appVersion,
+ String appPackageName,
+ String locale,
String liveQueryUrl,
String masterKey,
String clientKey,
@@ -30,11 +33,13 @@ class ParseCoreData {
ParseUserConstructor parseUserConstructor,
ParseFileConstructor parseFileConstructor,
List liveListRetryIntervals,
+ ParseConnectivityProvider connectivityProvider,
+ String fileDirectory,
+ Stream appResumedStream,
}) async {
_instance = ParseCoreData._init(appId, serverUrl);
- _instance.storage ??=
- store ?? await CoreStoreSharedPrefsImp.getInstance(password: masterKey);
+ _instance.storage ??= store ?? CoreStoreMemoryImp();
if (debug != null) {
_instance.debug = debug;
@@ -42,6 +47,15 @@ class ParseCoreData {
if (appName != null) {
_instance.appName = appName;
}
+ if (appVersion != null) {
+ _instance.appVersion = appVersion;
+ }
+ if (appPackageName != null) {
+ _instance.appPackageName = appPackageName;
+ }
+ if (locale != null) {
+ _instance.locale = locale;
+ }
if (liveQueryUrl != null) {
_instance.liveQueryURL = liveQueryUrl;
}
@@ -73,10 +87,24 @@ class ParseCoreData {
parseUserConstructor: parseUserConstructor,
parseFileConstructor: parseFileConstructor,
);
+ if (connectivityProvider != null) {
+ _instance.connectivityProvider = connectivityProvider;
+ }
+
+ if (fileDirectory != null) {
+ _instance.fileDirectory = fileDirectory;
+ }
+
+ if (appResumedStream != null) {
+ _instance.appResumedStream = appResumedStream;
+ }
}
String appName;
+ String appVersion;
+ String appPackageName;
String applicationId;
+ String locale;
String serverUrl;
String liveQueryURL;
String masterKey;
@@ -88,6 +116,9 @@ class ParseCoreData {
CoreStore storage;
ParseSubClassHandler _subClassHandler;
List liveListRetryIntervals;
+ ParseConnectivityProvider connectivityProvider;
+ String fileDirectory;
+ Stream appResumedStream;
void registerSubClass(
String className, ParseObjectConstructor objectConstructor) {
diff --git a/lib/src/data/parse_subclass_handler.dart b/packages/dart/lib/src/data/parse_subclass_handler.dart
similarity index 100%
rename from lib/src/data/parse_subclass_handler.dart
rename to packages/dart/lib/src/data/parse_subclass_handler.dart
diff --git a/lib/src/enums/parse_enum_api_rq.dart b/packages/dart/lib/src/enums/parse_enum_api_rq.dart
similarity index 100%
rename from lib/src/enums/parse_enum_api_rq.dart
rename to packages/dart/lib/src/enums/parse_enum_api_rq.dart
diff --git a/packages/dart/lib/src/network/dio-options.dart b/packages/dart/lib/src/network/dio-options.dart
new file mode 100644
index 000000000..cf20dcc9b
--- /dev/null
+++ b/packages/dart/lib/src/network/dio-options.dart
@@ -0,0 +1,34 @@
+part of flutter_parse_sdk;
+
+class Options extends dio.Options {
+ Options({
+ String method,
+ int sendTimeout,
+ int receiveTimeout,
+ Map extra,
+ Map headers,
+ ResponseType responseType,
+ String contentType,
+ ValidateStatus validateStatus,
+ bool receiveDataWhenStatusError,
+ bool followRedirects,
+ int maxRedirects,
+ RequestEncoder requestEncoder,
+ ResponseDecoder responseDecoder,
+ }) : super(
+ method: method,
+ sendTimeout: sendTimeout,
+ receiveTimeout: receiveTimeout,
+ extra: extra,
+ headers: headers,
+ responseType: responseType,
+ contentType: contentType ??
+ (headers ?? {})[Headers.contentTypeHeader],
+ validateStatus: validateStatus,
+ receiveDataWhenStatusError: receiveDataWhenStatusError,
+ followRedirects: followRedirects,
+ maxRedirects: maxRedirects,
+ requestEncoder: requestEncoder,
+ responseDecoder: responseDecoder,
+ );
+}
diff --git a/packages/dart/lib/src/network/http_client_adapter.dart b/packages/dart/lib/src/network/http_client_adapter.dart
new file mode 100644
index 000000000..9d09a3c14
--- /dev/null
+++ b/packages/dart/lib/src/network/http_client_adapter.dart
@@ -0,0 +1,2 @@
+export 'http_client_adapter_native.dart'
+ if (dart.library.js) 'http_client_adapter_web.dart';
diff --git a/packages/dart/lib/src/network/http_client_adapter_native.dart b/packages/dart/lib/src/network/http_client_adapter_native.dart
new file mode 100644
index 000000000..c5f87a175
--- /dev/null
+++ b/packages/dart/lib/src/network/http_client_adapter_native.dart
@@ -0,0 +1,14 @@
+import 'dart:io';
+
+import 'package:dio/adapter.dart';
+import 'package:dio/dio.dart';
+
+HttpClientAdapter createHttpClientAdapter(SecurityContext securityContext) {
+ final DefaultHttpClientAdapter defaultHttpClientAdapter =
+ DefaultHttpClientAdapter();
+
+ if (securityContext != null)
+ defaultHttpClientAdapter.onHttpClientCreate =
+ (HttpClient client) => HttpClient(context: securityContext);
+ return defaultHttpClientAdapter;
+}
diff --git a/packages/dart/lib/src/network/http_client_adapter_web.dart b/packages/dart/lib/src/network/http_client_adapter_web.dart
new file mode 100644
index 000000000..e7158e0de
--- /dev/null
+++ b/packages/dart/lib/src/network/http_client_adapter_web.dart
@@ -0,0 +1,10 @@
+import 'dart:io';
+
+import 'package:dio/adapter_browser.dart';
+import 'package:dio/dio.dart';
+
+HttpClientAdapter createHttpClientAdapter(SecurityContext securityContext) {
+ final BrowserHttpClientAdapter browserHttpClientAdapter =
+ BrowserHttpClientAdapter();
+ return browserHttpClientAdapter;
+}
diff --git a/packages/dart/lib/src/network/parse_connectivity.dart b/packages/dart/lib/src/network/parse_connectivity.dart
new file mode 100644
index 000000000..731ad1a4b
--- /dev/null
+++ b/packages/dart/lib/src/network/parse_connectivity.dart
@@ -0,0 +1,18 @@
+part of flutter_parse_sdk;
+
+/// Connection status check result.
+enum ParseConnectivityResult {
+ /// WiFi: Device connected via Wi-Fi
+ wifi,
+
+ /// Mobile: Device connected to cellular network
+ mobile,
+
+ /// None: Device not connected to any network
+ none
+}
+
+abstract class ParseConnectivityProvider {
+ Future checkConnectivity();
+ Stream get connectivityStream;
+}
diff --git a/packages/dart/lib/src/network/parse_http_client.dart b/packages/dart/lib/src/network/parse_http_client.dart
new file mode 100644
index 000000000..29c293477
--- /dev/null
+++ b/packages/dart/lib/src/network/parse_http_client.dart
@@ -0,0 +1,62 @@
+part of flutter_parse_sdk;
+
+/// Creates a custom version of HTTP Client that has Parse Data Preset
+class ParseHTTPClient with DioMixin implements Dio {
+ ParseHTTPClient({bool sendSessionId = false, SecurityContext securityContext})
+ : _sendSessionId = sendSessionId {
+ options = BaseOptions();
+ httpClientAdapter = createHttpClientAdapter(securityContext);
+ }
+
+ final bool _sendSessionId;
+ final String _userAgent = '$keyLibraryName $keySdkVersion';
+ ParseCoreData data = ParseCoreData();
+ Map additionalHeaders;
+
+ /// Overrides the call method for HTTP Client and adds custom headers
+ @override
+ Future> request(
+ String path, {
+ dynamic data,
+ Map queryParameters,
+ CancelToken cancelToken,
+ dio.Options options,
+ ProgressCallback onSendProgress,
+ ProgressCallback onReceiveProgress,
+ }) {
+ options ??= Options();
+ if (!identical(0, 0.0)) {
+ options.headers[keyHeaderUserAgent] = _userAgent;
+ }
+ options.headers[keyHeaderApplicationId] = this.data.applicationId;
+ if ((_sendSessionId == true) &&
+ (this.data.sessionId != null) &&
+ (options.headers[keyHeaderSessionToken] == null))
+ options.headers[keyHeaderSessionToken] = this.data.sessionId;
+
+ if (this.data.clientKey != null)
+ options.headers[keyHeaderClientKey] = this.data.clientKey;
+ if (this.data.masterKey != null)
+ options.headers[keyHeaderMasterKey] = this.data.masterKey;
+
+ /// If developer wants to add custom headers, extend this class and add headers needed.
+ if (additionalHeaders != null && additionalHeaders.isNotEmpty) {
+ additionalHeaders
+ .forEach((String key, String value) => options.headers[key] = value);
+ }
+
+ if (this.data.debug) {
+ logCUrl(options, data, path);
+ }
+
+ return super.request(
+ path,
+ data: data,
+ queryParameters: queryParameters,
+ cancelToken: cancelToken,
+ options: options,
+ onSendProgress: onSendProgress,
+ onReceiveProgress: onReceiveProgress,
+ );
+ }
+}
diff --git a/lib/src/network/parse_live_query.dart b/packages/dart/lib/src/network/parse_live_query.dart
similarity index 86%
rename from lib/src/network/parse_live_query.dart
rename to packages/dart/lib/src/network/parse_live_query.dart
index ca90110c0..a1f151ecb 100644
--- a/lib/src/network/parse_live_query.dart
+++ b/packages/dart/lib/src/network/parse_live_query.dart
@@ -1,13 +1,4 @@
-import 'dart:async';
-import 'dart:convert';
-import 'dart:io';
-
-import 'package:connectivity/connectivity.dart';
-import 'package:flutter/widgets.dart';
-import 'package:web_socket_channel/io.dart';
-import 'package:web_socket_channel/web_socket_channel.dart';
-
-import '../../parse_server_sdk.dart';
+part of flutter_parse_sdk;
enum LiveQueryEvent { create, enter, update, leave, delete, error }
@@ -31,7 +22,6 @@ class Subscription {
'error'
];
Map eventCallbacks = {};
-
void on(LiveQueryEvent op, Function callback) {
eventCallbacks[_liveQueryEvent[op.index]] = callback;
}
@@ -43,12 +33,21 @@ class Subscription {
enum LiveQueryClientEvent { CONNECTED, DISCONNECTED, USER_DISCONNECTED }
-class LiveQueryReconnectingController with WidgetsBindingObserver {
+class LiveQueryReconnectingController {
LiveQueryReconnectingController(
- this._reconnect, this._eventStream, this.debug) {
- Connectivity().checkConnectivity().then(_connectivityChanged);
- Connectivity().onConnectivityChanged.listen(_connectivityChanged);
-
+ this._reconnect,
+ this._eventStream,
+ this.debug,
+ ) {
+ final ParseConnectivityProvider connectivityProvider =
+ ParseCoreData().connectivityProvider;
+ if (connectivityProvider != null) {
+ connectivityProvider.checkConnectivity().then(_connectivityChanged);
+ connectivityProvider.connectivityStream.listen(_connectivityChanged);
+ } else {
+ print(
+ 'LiveQuery does not work, if there is ParseConnectivityProvider provided.');
+ }
_eventStream.listen((LiveQueryClientEvent event) {
switch (event) {
case LiveQueryClientEvent.CONNECTED:
@@ -73,7 +72,7 @@ class LiveQueryReconnectingController with WidgetsBindingObserver {
print('$DEBUG_TAG: $event');
}
});
- WidgetsBinding.instance.addObserver(this);
+ ParseCoreData().appResumedStream?.listen((void _) => _setReconnect());
}
static List get retryInterval => ParseCoreData().liveListRetryIntervals;
@@ -90,28 +89,20 @@ class LiveQueryReconnectingController with WidgetsBindingObserver {
Timer _currentTimer;
- void _connectivityChanged(ConnectivityResult state) {
- if (!_isOnline && state != ConnectivityResult.none) {
+ void _connectivityChanged(ParseConnectivityResult state) {
+ if (!_isOnline && state != ParseConnectivityResult.none) {
_retryState = 0;
}
- _isOnline = state != ConnectivityResult.none;
+ _isOnline = state != ParseConnectivityResult.none;
+ if(state == ParseConnectivityResult.none) {
+ _isConnected = false;
+ }
if (debug) {
print('$DEBUG_TAG: $state');
}
_setReconnect();
}
- @override
- void didChangeAppLifecycleState(AppLifecycleState state) {
- switch (state) {
- case AppLifecycleState.resumed:
- _setReconnect();
- break;
- default:
- break;
- }
- }
-
void _setReconnect() {
if (_isOnline &&
!_isConnected &&
@@ -132,10 +123,9 @@ class LiveQueryReconnectingController with WidgetsBindingObserver {
}
}
-class Client {
- factory Client() => _getInstance();
-
- Client._internal(
+class LiveQueryClient {
+ factory LiveQueryClient() => _getInstance();
+ LiveQueryClient._internal(
{bool debug, ParseHTTPClient client, bool autoSendSessionId}) {
_clientEventStreamController = StreamController();
_clientEventStream =
@@ -160,13 +150,11 @@ class Client {
reconnectingController = LiveQueryReconnectingController(
() => reconnect(userInitialized: false), getClientEventStream, _debug);
}
-
- static Client get instance => _getInstance();
- static Client _instance;
-
- static Client _getInstance(
+ static LiveQueryClient get instance => _getInstance();
+ static LiveQueryClient _instance;
+ static LiveQueryClient _getInstance(
{bool debug, ParseHTTPClient client, bool autoSendSessionId}) {
- _instance ??= Client._internal(
+ _instance ??= LiveQueryClient._internal(
debug: debug, client: client, autoSendSessionId: autoSendSessionId);
return _instance;
}
@@ -175,7 +163,7 @@ class Client {
return _clientEventStream;
}
- WebSocket _webSocket;
+ parse_web_socket.WebSocket _webSocket;
ParseHTTPClient _client;
bool _debug;
bool _sendSessionId;
@@ -186,7 +174,6 @@ class Client {
Stream _clientEventStream;
LiveQueryReconnectingController reconnectingController;
- // ignore: always_specify_types
final Map _requestSubScription = {};
Future reconnect({bool userInitialized = false}) async {
@@ -198,11 +185,12 @@ class Client {
if (_webSocket != null) {
return _webSocket.readyState;
}
- return WebSocket.connecting;
+ return parse_web_socket.WebSocket.CONNECTING;
}
Future disconnect({bool userInitialized = false}) async {
- if (_webSocket != null && _webSocket.readyState == WebSocket.open) {
+ if (_webSocket != null &&
+ _webSocket.readyState == parse_web_socket.WebSocket.OPEN) {
if (_debug) {
print('$_printConstLiveQuery: Socket closed');
}
@@ -216,7 +204,6 @@ class Client {
await _channel.sink.close();
_channel = null;
}
- // ignore: always_specify_types
_requestSubScription.values.toList().forEach((Subscription subscription) {
subscription._enabled = false;
});
@@ -274,9 +261,10 @@ class Client {
_connecting = true;
try {
- _webSocket = await WebSocket.connect(_liveQueryURL);
+ _webSocket = await parse_web_socket.WebSocket.connect(_liveQueryURL);
_connecting = false;
- if (_webSocket != null && _webSocket.readyState == WebSocket.open) {
+ if (_webSocket != null &&
+ _webSocket.readyState == parse_web_socket.WebSocket.OPEN) {
if (_debug) {
print('$_printConstLiveQuery: Socket opened');
}
@@ -286,7 +274,7 @@ class Client {
}
return Future.value(null);
}
- _channel = IOWebSocketChannel(_webSocket);
+ _channel = _webSocket.createWebSocketChannel();
_channel.stream.listen((dynamic message) {
_handleMessage(message);
}, onDone: () {
@@ -302,8 +290,11 @@ class Client {
print(
'$_printConstLiveQuery: Error: ${error.runtimeType.toString()}');
}
- return Future.value(handleException(Exception(error),
- ParseApiRQ.liveQuery, _debug, 'IOWebSocketChannel'));
+ return Future.value(handleException(
+ Exception(error),
+ ParseApiRQ.liveQuery,
+ _debug,
+ !parseIsWeb ? 'IOWebSocketChannel' : 'HtmlWebSocketChannel'));
});
} on Exception catch (e) {
_connecting = false;
@@ -341,13 +332,11 @@ class Client {
_channel.sink.add(jsonEncode(connectMessage));
}
- // ignore: always_specify_types
void _subscribeLiveQuery(Subscription subscription) {
if (subscription._enabled) {
return;
}
subscription._enabled = true;
- // ignore: always_specify_types
final QueryBuilder query = subscription.query;
final List keysToReturn = query.limiters['keys']?.split(',');
query.limiters.clear(); //Remove limits in LiveQuery
@@ -386,11 +375,11 @@ class Client {
}
final Map actionData = jsonDecode(message);
- // ignore: always_specify_types
+
Subscription subscription;
if (actionData.containsKey('op') && actionData['op'] == 'connected') {
print('ReSubScription:$_requestSubScription');
- // ignore: always_specify_types
+
_requestSubScription.values.toList().forEach((Subscription subcription) {
_subscribeLiveQuery(subcription);
});
@@ -436,21 +425,17 @@ class LiveQuery {
_debug = isDebugEnabled(objectLevelDebug: debug);
_sendSessionId =
autoSendSessionId ?? ParseCoreData().autoSendSessionId ?? true;
- this.client = Client._getInstance(
+ this.client = LiveQueryClient._getInstance(
client: _client, debug: _debug, autoSendSessionId: _sendSessionId);
}
ParseHTTPClient _client;
bool _debug;
bool _sendSessionId;
-
- // ignore: always_specify_types
Subscription _latestSubscription;
- Client client;
+ LiveQueryClient client;
- // ignore: always_specify_types
@deprecated
- // ignore: always_specify_types
Future subscribe(QueryBuilder query) async {
_latestSubscription = await client.subscribe(query);
return _latestSubscription;
diff --git a/lib/src/network/parse_query.dart b/packages/dart/lib/src/network/parse_query.dart
similarity index 98%
rename from lib/src/network/parse_query.dart
rename to packages/dart/lib/src/network/parse_query.dart
index 9aca4aa3e..7f995427a 100644
--- a/lib/src/network/parse_query.dart
+++ b/packages/dart/lib/src/network/parse_query.dart
@@ -301,7 +301,7 @@ class QueryBuilder {
// Add a constraint to the query that requires a particular key's value matches a value for a key in the results of another ParseQuery.
// ignore: always_specify_types
void whereMatchesKeyInQuery(
- String column, String keyInQuery, QueryBuilder query) {
+ String column, String keyInQuery, QueryBuilder query) {
if (query.queries.isEmpty) {
throw ArgumentError('query conditions is required');
}
@@ -322,7 +322,7 @@ class QueryBuilder {
// Add a constraint to the query that requires a particular key's value does not match any value for a key in the results of another ParseQuery
// ignore: always_specify_types
void whereDoesNotMatchKeyInQuery(
- String column, String keyInQuery, QueryBuilder query) {
+ String column, String keyInQuery, QueryBuilder query) {
if (query.queries.isEmpty) {
throw ArgumentError('query conditions is required');
}
@@ -343,8 +343,12 @@ class QueryBuilder {
/// Finishes the query and calls the server
///
/// Make sure to call this after defining your queries
- Future query() async {
- return object.query(buildQuery());
+ Future query(
+ {ProgressCallback progressCallback}) async {
+ return object.query(
+ buildQuery(),
+ progressCallback: progressCallback,
+ );
}
Future distinct(
diff --git a/packages/dart/lib/src/network/parse_websocket.dart b/packages/dart/lib/src/network/parse_websocket.dart
new file mode 100644
index 000000000..4ec07c900
--- /dev/null
+++ b/packages/dart/lib/src/network/parse_websocket.dart
@@ -0,0 +1,2 @@
+export 'parse_websocket_io.dart'
+ if (dart.library.js) 'parse_websocket_html.dart';
diff --git a/packages/dart/lib/src/network/parse_websocket_html.dart b/packages/dart/lib/src/network/parse_websocket_html.dart
new file mode 100644
index 000000000..aeab1d27c
--- /dev/null
+++ b/packages/dart/lib/src/network/parse_websocket_html.dart
@@ -0,0 +1,33 @@
+/// If you change this file, you should apply the same changes to the 'parse_websocket_io.dart' file
+
+import 'dart:html' as html;
+
+import 'package:web_socket_channel/html.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+class WebSocket {
+ WebSocket._(this._webSocket);
+
+ static const int CONNECTING = 0;
+ static const int OPEN = 1;
+ static const int CLOSING = 2;
+ static const int CLOSED = 3;
+
+ final html.WebSocket _webSocket;
+
+ static Future connect(String liveQueryURL) async {
+ final html.WebSocket webSocket = html.WebSocket(liveQueryURL);
+ await webSocket.onOpen.first;
+ return WebSocket._(webSocket);
+ }
+
+ int get readyState => _webSocket.readyState;
+
+ Future close() async {
+ return _webSocket.close();
+ }
+
+ WebSocketChannel createWebSocketChannel() {
+ return HtmlWebSocketChannel(_webSocket);
+ }
+}
diff --git a/packages/dart/lib/src/network/parse_websocket_io.dart b/packages/dart/lib/src/network/parse_websocket_io.dart
new file mode 100644
index 000000000..37206dd49
--- /dev/null
+++ b/packages/dart/lib/src/network/parse_websocket_io.dart
@@ -0,0 +1,31 @@
+/// If you change this file, you should apply the same changes to the 'parse_websocket_html.dart' file
+
+import 'dart:io' as io;
+
+import 'package:web_socket_channel/io.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+class WebSocket {
+ WebSocket._(this._webSocket);
+
+ static const int CONNECTING = 0;
+ static const int OPEN = 1;
+ static const int CLOSING = 2;
+ static const int CLOSED = 3;
+
+ final io.WebSocket _webSocket;
+
+ static Future connect(String liveQueryURL) async {
+ return WebSocket._(await io.WebSocket.connect(liveQueryURL));
+ }
+
+ int get readyState => _webSocket.readyState;
+
+ Future close() {
+ return _webSocket.close();
+ }
+
+ WebSocketChannel createWebSocketChannel() {
+ return IOWebSocketChannel(_webSocket);
+ }
+}
diff --git a/lib/src/objects/parse_acl.dart b/packages/dart/lib/src/objects/parse_acl.dart
similarity index 100%
rename from lib/src/objects/parse_acl.dart
rename to packages/dart/lib/src/objects/parse_acl.dart
diff --git a/lib/src/objects/parse_base.dart b/packages/dart/lib/src/objects/parse_base.dart
similarity index 100%
rename from lib/src/objects/parse_base.dart
rename to packages/dart/lib/src/objects/parse_base.dart
diff --git a/lib/src/objects/parse_cloneable.dart b/packages/dart/lib/src/objects/parse_cloneable.dart
similarity index 100%
rename from lib/src/objects/parse_cloneable.dart
rename to packages/dart/lib/src/objects/parse_cloneable.dart
diff --git a/lib/src/objects/parse_config.dart b/packages/dart/lib/src/objects/parse_config.dart
similarity index 90%
rename from lib/src/objects/parse_config.dart
rename to packages/dart/lib/src/objects/parse_config.dart
index 8f7dedf16..1c2feb28b 100644
--- a/lib/src/objects/parse_config.dart
+++ b/packages/dart/lib/src/objects/parse_config.dart
@@ -16,7 +16,7 @@ class ParseConfig extends ParseObject {
Future getConfigs() async {
try {
final String uri = '${ParseCoreData().serverUrl}/config';
- final Response result = await _client.get(uri);
+ final Response result = await _client.get(uri);
return handleResponse(
this, result, ParseApiRQ.getConfigs, _debug, parseClassName);
} on Exception catch (e) {
@@ -29,7 +29,7 @@ class ParseConfig extends ParseObject {
try {
final String uri = '${ParseCoreData().serverUrl}/config';
final String body = '{\"params\":{\"$key\": \"${parseEncode(value)}\"}}';
- final Response result = await _client.put(uri, body: body);
+ final Response result = await _client.put(uri, data: body);
return handleResponse(
this, result, ParseApiRQ.addConfig, _debug, parseClassName);
} on Exception catch (e) {
diff --git a/lib/src/objects/parse_error.dart b/packages/dart/lib/src/objects/parse_error.dart
similarity index 100%
rename from lib/src/objects/parse_error.dart
rename to packages/dart/lib/src/objects/parse_error.dart
diff --git a/lib/src/objects/parse_file.dart b/packages/dart/lib/src/objects/parse_file.dart
similarity index 65%
rename from lib/src/objects/parse_file.dart
rename to packages/dart/lib/src/objects/parse_file.dart
index c44d63ab3..76349ec41 100644
--- a/lib/src/objects/parse_file.dart
+++ b/packages/dart/lib/src/objects/parse_file.dart
@@ -21,14 +21,12 @@ class ParseFile extends ParseFileBase {
File file;
Future loadStorage() async {
- final Directory tempPath = await getTemporaryDirectory();
-
if (name == null) {
file = null;
return this;
}
- final File possibleFile = File('${tempPath.path}/$name');
+ final File possibleFile = File('${ParseCoreData().fileDirectory}/$name');
// ignore: avoid_slow_async_io
final bool exists = await possibleFile.exists();
@@ -42,23 +40,26 @@ class ParseFile extends ParseFileBase {
}
@override
- Future download() async {
+ Future download({ProgressCallback progressCallback}) async {
if (url == null) {
return this;
}
- final Directory tempPath = await getTemporaryDirectory();
- file = File('${tempPath.path}/$name');
+ file = File('${ParseCoreData().fileDirectory}/$name');
await file.create();
- final Response response = await _client.get(url);
- await file.writeAsBytes(response.bodyBytes);
+ final Response> response = await _client.get>(
+ url,
+ options: Options(responseType: ResponseType.bytes),
+ onReceiveProgress: progressCallback,
+ );
+ await file.writeAsBytes(response.data);
return this;
}
/// Uploads a file to Parse Server
@override
- Future upload() async {
+ Future upload({ProgressCallback progressCallback}) async {
if (saved) {
//Creates a Fake Response to return the correct result
final Map response = {
@@ -67,23 +68,25 @@ class ParseFile extends ParseFileBase {
};
return handleResponse(
this,
- Response(json.encode(response), 201),
+ Response(data: json.encode(response), statusCode: 201),
ParseApiRQ.upload,
_debug,
parseClassName);
}
- final String ext = path.extension(file.path).replaceAll('.', '');
final Map headers = {
- HttpHeaders.contentTypeHeader: getContentType(ext)
+ HttpHeaders.contentTypeHeader: mime(file.path) ?? 'application/octet-stream',
};
try {
final String uri = _client.data.serverUrl + '$_path';
- final List body = await file.readAsBytes();
- final Response response =
- await _client.post(uri, headers: headers, body: body);
+ final Response response = await _client.post(
+ uri,
+ options: Options(headers: headers),
+ data: file.openRead(),
+ onSendProgress: progressCallback,
+ );
if (response.statusCode == 201) {
- final Map map = json.decode(response.body);
+ final Map map = json.decode(response.data);
url = map['url'].toString();
name = map['name'].toString();
}
diff --git a/lib/src/objects/parse_file_base.dart b/packages/dart/lib/src/objects/parse_file_base.dart
similarity index 89%
rename from lib/src/objects/parse_file_base.dart
rename to packages/dart/lib/src/objects/parse_file_base.dart
index e565b92a7..4ca208259 100644
--- a/lib/src/objects/parse_file_base.dart
+++ b/packages/dart/lib/src/objects/parse_file_base.dart
@@ -41,7 +41,7 @@ abstract class ParseFileBase extends ParseObject {
}
/// Uploads a file to Parse Server
- Future upload();
+ Future upload({ProgressCallback progressCallback});
- Future download();
+ Future download({ProgressCallback progressCallback});
}
diff --git a/lib/src/objects/parse_file_web.dart b/packages/dart/lib/src/objects/parse_file_web.dart
similarity index 61%
rename from lib/src/objects/parse_file_web.dart
rename to packages/dart/lib/src/objects/parse_file_web.dart
index f17369a96..323fff54d 100644
--- a/lib/src/objects/parse_file_web.dart
+++ b/packages/dart/lib/src/objects/parse_file_web.dart
@@ -18,19 +18,23 @@ class ParseWebFile extends ParseFileBase {
Uint8List file;
@override
- Future download() async {
+ Future download({ProgressCallback progressCallback}) async {
if (url == null) {
return this;
}
- final Response response = await _client.get(url);
- file = response.bodyBytes;
+ final Response> response = await _client.get>(
+ url,
+ options: Options(responseType: ResponseType.bytes),
+ onReceiveProgress: progressCallback,
+ );
+ file = response.data;
return this;
}
@override
- Future upload() async {
+ Future upload({ProgressCallback progressCallback}) async {
if (saved) {
//Creates a Fake Response to return the correct result
final Map response = {
@@ -39,23 +43,26 @@ class ParseWebFile extends ParseFileBase {
};
return handleResponse(
this,
- Response(json.encode(response), 201),
+ Response(data: json.encode(response), statusCode: 201),
ParseApiRQ.upload,
_debug,
parseClassName);
}
final Map headers = {
- HttpHeaders.contentTypeHeader: url ?? name != null
- ? getContentType(path.extension(url ?? name))
- : 'text/plain'
+ HttpHeaders.contentTypeHeader:
+ mime(url ?? name) ?? 'application/octet-stream',
};
try {
final String uri = _client.data.serverUrl + '$_path';
- final Response response =
- await _client.post(uri, headers: headers, body: file);
+ final Response response = await _client.post(
+ uri,
+ options: Options(headers: headers),
+ data: Stream>.fromIterable(>[file]),
+ onSendProgress: progressCallback,
+ );
if (response.statusCode == 201) {
- final Map map = json.decode(response.body);
+ final Map