Skip to content

Commit 2088bf1

Browse files
phillwigginsfischerscodePawlikMichal25phanirithvijDanaru87
authored
Release/1.0.27 (#428)
* Release 1.0.27 - Create base branch * Fix for #347 (include in LiveList) (#348) * LiveList: reload included objects First implementation * Key was wrong on order change * include sub-includes * reuse loaded pointers * Deleted stuff * LiveList autoupdate included sub-objects (#349) * LiveList: reload included objects First implementation * Key was wrong on order change * include sub-includes * reuse loaded pointers * Deleted stuff * Adding listening on included objects to LiveList * Updated readme for ParseLiveList * Update parse_live_list.dart * LiveList: sync subObjects after reconnect (#352) * Sync subObjects after reconnect * Update README.md * Update README.md (#353) * ISSUE-351: Replaced isTypeOfException with Exception (#355) Great work! Thank you * RegisterSubClass and smaler changes (#368) * added subclass handler * integraded subclass handler * added constructor for QueryBuilder using registered SubClasses * subclass handler optimizations * Convenience changes * Convenience change * sembast CoreStore on macos * Fix: ParseUser updated on server but not locally This should be a fix for: #364 and #256 * Update README.md * Update README.md * Update README.md (#367) * Save installationId when new installation created. Send installationId when User login (#375) * ParseLiveElement (#376) * First implementation for ParseLiveElement * ParseLiveElement further implementation * Update parse_live_list.dart * ParseLiveElement working * News methods for ParseQuery - whereMatchesKeyInQuery and whereDoesNotMatchKeyInQuery (#379) * Add method whereMatchesKeyInQuery Add method whereMatchesKeyInQuery * Update parse_query.dart * Fix whereMatchesKeyInQuery / whereDoesNotMatchKeyInQuery * Update README.md * Update README.md * Update README.md * Add google login helper (#387) - Update README to show how to get google login attributes for parse * Update parse_response_builder.dart (#390) * [web] Check web before Platform.isX (#392) Platform.isX is unsupported in web * Make web compatible calls for locale and skip PackageInfo (#395) * Make web compatible calls for locale and skip PackageInfo * Also get country code for web locale * Add description of early web support * Added ParseFile check to _canBeSerialized() (#396) * Update README.md (#397) Fix Parse Server parameter name * ParseLiveList: first implementation of lazyLoading (#407) Setting lazyLoading=false should disable lazyLoading. * Update parse_live_list.dart (#406) In theory this should not make any difference. In practise sometimes an error gets thrown: The following NoSuchMethodError was thrown while finalizing the widget tree: The method 'dispose' was called on null. Receiver: null Tried calling: dispose() * File Support for web (#409) * created files and started implementation * add different filetypes to sdk * Update README.md * add an other file example to README (#410) * added file example to README * Update README.md * Web file upload: set content type to "text/plain" incase neighter name nor url is set (#411) This breaks the extension at the uploaded file, but otherwise the upload would fail with a server error. * LiveQuery reconnecting intervals (#414) * parseFileConstructor was missing at Parse.initialize * add liveListRetryIntervals to Parse.initialize * replace kIsWeb with its definition (#415) Using less libaries is always good. * release/1.0.27 update dependencies * Release/1.0.27 (#427) * forgetLocalSession before login / signUP / loginAnonymous #426 * forgetLocalSession before login / signUP / loginAnonymous #426 * Release 1.0.27 - Pre-release process - Update dependencies * Release 1.0.27 - Pre-release process - Static analysis fixes Co-authored-by: Maximilian Fischer <[email protected]> Co-authored-by: Michal Baran <[email protected]> Co-authored-by: Maximilian Fischer <[email protected]> Co-authored-by: Phani Rithvij <[email protected]> Co-authored-by: Danaru <[email protected]> Co-authored-by: Rodrigo de Souza Marques <[email protected]> Co-authored-by: vreamer <[email protected]> Co-authored-by: Dmytro Karpovych <[email protected]> Co-authored-by: Nils Strelow <[email protected]> Co-authored-by: hyperrecursive <[email protected]>
1 parent c743e3a commit 2088bf1

35 files changed

+1185
-303
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
## 1.0.27
2+
User login / signUp / loginAnonymous delete SessionId stored in device before calling server
3+
14
## 1.0.26
5+
LiveList
6+
Bug fixes
7+
Sembast update
28

39
## 1.0.25
410
Update dependencies

README.md

Lines changed: 189 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
## Parse For Flutter!
55
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).
66

7-
This is a work in project 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.
7+
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)
88

99
## Join in!
1010
Want to get involved? Join our Slack channel and help out! (http://flutter-parse-sdk.slack.com)
@@ -13,7 +13,7 @@ Want to get involved? Join our Slack channel and help out! (http://flutter-parse
1313
To install, either add to your pubspec.yaml
1414
```yml
1515
dependencies:
16-
parse_server_sdk: ^1.0.26
16+
parse_server_sdk: ^1.0.27
1717
```
1818
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.
1919
@@ -49,6 +49,15 @@ It's possible to add other parameters to work with your instance of Parse Server
4949
coreStore: await CoreStoreSharedPrefsImp.getInstance()); // Local data storage method. Will use SharedPreferences instead of Sembast as an internal DB
5050
```
5151

52+
53+
#### Early Web support
54+
Currently this requires adding `X-Parse-Installation-Id` as an allowed header to parse-server.
55+
When running directly via docker, set the env var `PARSE_SERVER_ALLOW_HEADERS=X-Parse-Installation-Id`.
56+
When running via express, set [ParseServerOptions](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) `allowHeaders: ['X-Parse-Installation-Id']`.
57+
58+
Be aware that for web ParseInstallation does include app name, version or package identifier.
59+
60+
5261
## Objects
5362
You can create custom objects by calling:
5463
```dart
@@ -98,7 +107,7 @@ The features available are:-
98107
* Array Operators
99108

100109
## Custom Objects
101-
You can create your own ParseObjects or convert your existing objects into Parse Objects by doing the following:
110+
You can create your own `ParseObjects` or convert your existing objects into Parse Objects by doing the following:
102111

103112
```dart
104113
class DietPlan extends ParseObject implements ParseCloneable {
@@ -119,6 +128,26 @@ class DietPlan extends ParseObject implements ParseCloneable {
119128
120129
```
121130

131+
When receiving an `ParseObject` from the SDK, you can often provide an instance of your custom object as an copy object.
132+
To always use your custom object class, you can register your subclass at the initialization of the SDK.
133+
```dart
134+
Parse().initialize(
135+
...,
136+
registeredSubClassMap: <String, ParseObjectConstructor>{
137+
'Diet_Plans': () => DietPlan(),
138+
},
139+
parseUserConstructor: (username, password, emailAddress, {client, debug, sessionToken}) => CustomParseUser(username, password, emailAddress),
140+
);
141+
```
142+
Additionally you can register `SubClasses` after the initialization of the SDK.
143+
```dart
144+
ParseCoreData().registerSubClass('Diet_Plans', () => DietPlan());
145+
ParseCoreData().registerUserSubClass((username, password, emailAddress, {client, debug, sessionToken}) => CustomParseUser(username, password, emailAddress));
146+
```
147+
Providing a `ParseObject` as described above should still work, even if you have registered a different `SubClass`.
148+
149+
For custom file classes have a lock at [here](#File).
150+
122151
## Add new values to objects
123152
To add a variable to an object call and retrieve it, call
124153

@@ -128,7 +157,7 @@ var randomInt = dietPlan.get<int>('RandomInt');
128157
```
129158

130159
## Save objects using pins
131-
You can now save an object by calling .pin() on an instance of an object
160+
You can now save an object by calling `.pin()` on an instance of an object
132161

133162
```dart
134163
dietPlan.pin();
@@ -229,6 +258,26 @@ if (response.success) {
229258
}
230259
```
231260

261+
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:
262+
```dart
263+
ParseObject playerObject = ParseObject("Player");
264+
265+
QueryBuilder<ParseObject> lotsOfWins =
266+
QueryBuilder<ParseObject>(playerObject))
267+
..whereGreaterThan('wins', 50);
268+
269+
QueryBuilder<ParseObject> fewWins =
270+
QueryBuilder<ParseObject>(playerObject)
271+
..whereLessThan('wins', 5);
272+
273+
QueryBuilder<ParseObject> mainQuery = QueryBuilder.or(
274+
playerObject,
275+
[lotsOfWins, fewWins],
276+
);
277+
278+
var apiResponse = await mainQuery.query();
279+
```
280+
232281
The features available are:-
233282
* Equals
234283
* Contains
@@ -245,6 +294,10 @@ The features available are:-
245294
* WithinKilometers
246295
* WithinRadians
247296
* WithinGeoBox
297+
* MatchesQuery
298+
* DoesNotMatchQuery
299+
* MatchesKeyInQuery
300+
* DoesNotMatchKeyInQuery
248301
* Regex
249302
* Order
250303
* Limit
@@ -288,6 +341,47 @@ QueryBuilder<ParseObject> queryComment =
288341
var apiResponse = await queryComment.query();
289342
```
290343

344+
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::
345+
346+
```dart
347+
QueryBuilder<ParseObject> teamQuery =
348+
QueryBuilder<ParseObject>(ParseObject('Team'))
349+
..whereGreaterThan('winPct', 0.5);
350+
351+
QueryBuilder<ParseUser> userQuery =
352+
QueryBuilder<ParseUser>ParseUser.forQuery())
353+
..whereMatchesKeyInQuery('hometown', 'city', teamQuery);
354+
355+
var apiResponse = await userQuery.query();
356+
```
357+
358+
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:
359+
360+
```dart
361+
QueryBuilder<ParseObject> teamQuery =
362+
QueryBuilder<ParseObject>(ParseObject('Team'))
363+
..whereGreaterThan('winPct', 0.5);
364+
365+
QueryBuilder<ParseUser> losingUserQuery =
366+
QueryBuilder<ParseUser>ParseUser.forQuery())
367+
..whereDoesNotMatchKeyInQuery('hometown', 'city', teamQuery);
368+
369+
var apiResponse = await losingUserQuery.query();
370+
```
371+
372+
To filter rows based on objectId’s from pointers in a second table, you can use dot notation:
373+
```dart
374+
QueryBuilder<ParseObject> rolesOfTypeX =
375+
QueryBuilder<ParseObject>(ParseObject('Role'))
376+
..whereEqualTo('type', 'x');
377+
378+
QueryBuilder<ParseObject> groupsWithRoleX =
379+
QueryBuilder<ParseObject>(ParseObject('Group')))
380+
..whereMatchesKeyInQuery('objectId', 'belongsTo.objectId', rolesOfTypeX);
381+
382+
var apiResponse = await groupsWithRoleX.query();
383+
```
384+
291385
## Counting Objects
292386
If you only care about the number of games played by a particular player:
293387

@@ -430,10 +524,17 @@ LiveQuery server.
430524
liveQuery.client.unSubscribe(subscription);
431525
```
432526

527+
__Disconnection__
528+
In case the client's connection to the server breaks,
529+
LiveQuery will automatically try to reconnect.
530+
LiveQuery will wait at increasing intervals between reconnection attempts.
531+
By default, these intervals are set to `[0, 500, 1000, 2000, 5000, 10000]` for mobile and `[0, 500, 1000, 2000, 5000]` for web.
532+
You can change these by providing a custom list using the `liveListRetryIntervals` parameter at `Parse.initialize()` ("-1" means "do not try to reconnect").
533+
433534
## ParseLiveList
434535
ParseLiveList makes implementing a dynamic List as simple as possible.
435536

436-
537+
#### General Use
437538
It ships with the ParseLiveList class itself, this class manages all elements of the list, sorts them,
438539
keeps itself up to date and Notifies you on changes.
439540

@@ -451,13 +552,13 @@ ParseLiveListWidget<ParseObject>(
451552
query: query,
452553
reverse: false,
453554
childBuilder:
454-
(BuildContext context, bool failed, ParseObject loadedData) {
455-
if (failed) {
555+
(BuildContext context, ParseLiveListElementSnapshot<ParseObject> snapshot) {
556+
if (snapshot.failed) {
456557
return const Text('something went wrong!');
457-
} else if (loadedData != null) {
558+
} else if (snapshot.hasData) {
458559
return ListTile(
459560
title: Text(
460-
loadedData.get("text"),
561+
snapshot.loadedData.get("text"),
461562
),
462563
);
463564
} else {
@@ -487,8 +588,17 @@ ParseLiveListWidget<ParseObject>(
487588
duration: Duration(seconds: 1),
488589
);
489590
```
591+
### included Sub-Objects
592+
By default, ParseLiveQuery will provide you with all the objects you included in your Query like this:
593+
```dart
594+
queryBuilder.includeObject(/*List of all the included sub-objects*/);
595+
```
596+
ParseLiveList will not listen for updates on this objects by default.
597+
To activate listening for updates on all included objects, add `listenOnAllSubItems: true` to your ParseLiveListWidgets constructor.
598+
If you want ParseLiveList to listen for updates on only some sub-objects, use `listeningIncludes: const <String>[/*all the included sub-objects*/]` instead.
599+
Just as QueryBuilder, ParseLiveList supports nested sub-objects too.
490600

491-
Note: To use this features you have to enable [Live Queries](#live-queries) first.
601+
**NOTE:** To use this features you have to enable [Live Queries](#live-queries) first.
492602

493603
## Users
494604
You can create and control users just as normal using this SDK.
@@ -566,6 +676,24 @@ Other user features are:-
566676
}
567677
```
568678

679+
For Google and the example below, we used the library provided at https://pub.dev/packages/google_sign_in
680+
681+
```
682+
class OAuthLogin {
683+
final GoogleSignIn _googleSignIn = GoogleSignIn( scopes: ['email', 'https://www.googleapis.com/auth/contacts.readonly'] );
684+
685+
sigInGoogle() async {
686+
GoogleSignInAccount account = await _googleSignIn.signIn();
687+
GoogleSignInAuthentication authentication = await account.authentication;
688+
await ParseUser.loginWith(
689+
'google',
690+
google(_googleSignIn.currentUser.id,
691+
authentication.accessToken,
692+
authentication.idToken));
693+
}
694+
}
695+
```
696+
569697
## Security for Objects - ParseACL
570698
For any object, you can specify which users are allowed to read the object, and which users are allowed to modify an object.
571699
To support this type of security, each object has an access control list, implemented by the __ParseACL__ class.
@@ -690,11 +818,61 @@ QueryBuilder<ParseObject> query =
690818
..whereRelatedTo('fruits', 'DietPlan', DietPlan.objectId);
691819
```
692820

821+
## File
822+
There are three different file classes in this SDK:
823+
- `ParseFileBase` is and abstract class and is the foundation of every file class that can be handled by this SDK.
824+
- `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.
825+
This class uses a `File` from `dart:io` for storing the raw file.
826+
- `ParseWebFile` is the equivalent to ParseFile used at Flutter Web.
827+
This class uses an `Uint8List` for storing the raw file.
828+
829+
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`.
830+
831+
Have a look at the example application for a small (non web) example.
832+
833+
834+
```dart
835+
//A short example for showing an image from a ParseFileBase
836+
Widget buildImage(ParseFileBase image){
837+
return FutureBuilder<ParseFileBase>(
838+
future: image.download(),
839+
builder: (BuildContext context,
840+
AsyncSnapshot<ParseFileBase> snapshot) {
841+
if (snapshot.hasData) {
842+
if (kIsWeb) {
843+
return Image.memory((snapshot.data as ParseWebFile).file);
844+
} else {
845+
return Image.file((snapshot.data as ParseFile).file);
846+
}
847+
} else {
848+
return CircularProgressIndicator();
849+
}
850+
},
851+
);
852+
}
853+
```
854+
```dart
855+
//A short example for storing a selected picture
856+
//libraries: image_picker (https://pub.dev/packages/image_picker), image_picker_for_web (https://pub.dev/packages/image_picker_for_web)
857+
PickedFile pickedFile = await ImagePicker().getImage(source: ImageSource.gallery);
858+
ParseFileBase parseFile;
859+
if (kIsWeb) {
860+
//Seems weird, but this lets you get the data from the selected file as an Uint8List very easily.
861+
ParseWebFile file = ParseWebFile(null, name: null, url: pickedFile.path);
862+
await file.download();
863+
parseFile = ParseWebFile(file.file, name: file.name);
864+
} else {
865+
parseFile = ParseFile(File(pickedFile.path));
866+
}
867+
someParseObject.set("image", parseFile);
868+
//This saves the ParseObject as well as all of its children, and the ParseFileBase is such a child.
869+
await someParseObject.save();
870+
```
871+
693872
## Other Features of this library
694873
Main:
695874
* Installation (View the example application)
696875
* GeoPoints (View the example application)
697-
* Files (View the example application)
698876
* Persistent storage
699877
* Debug Mode - Logging API calls
700878
* Manage Session ID's tokens

example/lib/data/base/api_error.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
class ApiError {
2-
ApiError(this.code, this.message, this.isTypeOfException, this.type);
2+
ApiError(this.code, this.message, this.exception, this.type);
33

44
final int code;
55
final String message;
6-
final bool isTypeOfException;
6+
final Exception exception;
77
final String type;
88
}

example/lib/data/base/api_response.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@ ApiError getApiError(ParseError response) {
2525
return null;
2626
}
2727

28-
return ApiError(response.code, response.message, response.isTypeOfException,
28+
return ApiError(response.code, response.message, response.exception,
2929
response.type);
3030
}

example/lib/data/repositories/diet_plan/provider_db_diet_plan.dart

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,13 @@ class DietPlanProviderDB implements DietPlanProviderContract {
135135
final Map<String, dynamic> values = convertItemToStorageMap(item);
136136
final Finder finder =
137137
Finder(filter: Filter.equals('objectId', item.objectId));
138-
final int returnedCount =
139-
await _store.update(_db, values, finder: finder);
138+
final int returnedCount = await _store.update(_db, values, finder: finder);
140139

141140
if (returnedCount == 0) {
142141
return add(item);
143142
}
144143

145-
return ApiResponse(
146-
true, 200, <dynamic>[item], null);
144+
return ApiResponse(true, 200, <dynamic>[item], null);
147145
}
148146

149147
Map<String, dynamic> convertItemToStorageMap(DietPlan item) {
@@ -171,6 +169,6 @@ class DietPlanProviderDB implements DietPlanProviderContract {
171169
}
172170
}
173171

174-
static ApiError error = ApiError(1, 'No records found', false, '');
172+
static ApiError error = ApiError(1, 'No records found', null, '');
175173
ApiResponse errorResponse = ApiResponse(false, 1, null, error);
176174
}

example/lib/data/repositories/user/provider_db_user.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,6 @@ class UserProviderDB implements UserProviderContract {
102102
}
103103
}
104104

105-
static ApiError error = ApiError(1, 'No records found', false, '');
105+
static ApiError error = ApiError(1, 'No records found', null, '');
106106
ApiResponse errorResponse = ApiResponse(false, 1, null, error);
107107
}

example/lib/pages/home_page.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class _HomePageState extends State<HomePage> {
4141
title: const Text('Parse Server demo'),
4242
actions: <Widget>[
4343
FlatButton(
44-
child: Text('Logout',
44+
child: const Text('Logout',
4545
style: TextStyle(fontSize: 17.0, color: Colors.white)),
4646
onPressed: () async {
4747
final ParseUser user = await ParseUser.currentUser();

0 commit comments

Comments
 (0)