Skip to content

Commit 1f5a6d6

Browse files
authored
Merge pull request #1 from parse-community/nullsafety
Update Fork with Parse
2 parents 635c43e + ab9a1bc commit 1f5a6d6

File tree

10 files changed

+194
-80
lines changed

10 files changed

+194
-80
lines changed

.travis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ install:
1111

1212
script:
1313
- (cd packages/dart && pub get)
14+
- (cd packages/dart && dart run build_runner build --delete-conflicting-outputs)
1415
- (cd packages/dart && pub run test)
1516
- (cd packages/flutter && flutter pub get)
1617
- (cd packages/flutter && flutter test --no-pub test/)
1718

1819
cache:
1920
directories:
20-
- "$HOME/.pub-cache"
21+
- "$HOME/.pub-cache"

packages/dart/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,15 @@ if (dietPlan.success) {
248248
}
249249
```
250250

251+
### Alternative query methods
252+
253+
The standard query method `query()` returns a `ParseResponse` that contains the result or the error. As an alternative, you can also use `Future<List<T>> find()` for receiving options.
254+
This method returns an `Future` that either resolves in an error (equivalent of the error in the `ParseResponse`) or an `List` containing the queried objects. One difference, you should be aware of, is the fact, that `Future<List<T>> find()` will return an empty list instead of the 'No results' error you receive in case no object matches you query.
255+
256+
Choosing between `query()` and `find()` comes down to personal preference. Both methods can be used for querying a `ParseQuery`, just the output method differs.
257+
258+
Similar to `find()` the `QueryBuilder` also has a function called `Future<T>? first()`. Just like `find()` `first()` is just a convenience method that makes querying the first object satisfying the query simpler. `first()` returns an `Future`, that resoles in an error or the first object matching the query. In case no object satisfies the query, the result will be `null`.
259+
251260
## Complex queries
252261
You can create complex queries to really put your database to the test:
253262

packages/dart/lib/src/network/parse_query.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,4 +513,25 @@ class QueryBuilder<T extends ParseObject> {
513513
});
514514
return result;
515515
}
516+
517+
/// Find the first object that satisfies the query.
518+
/// Returns null, if no object is found.
519+
Future<T>? first() async {
520+
ParseResponse parseResponse =
521+
await (QueryBuilder.copy(this)..setLimit(1)).query();
522+
if (parseResponse.success) {
523+
return parseResponse.results?.first;
524+
}
525+
throw parseResponse.error ?? ParseError();
526+
}
527+
528+
/// Find the objects that satisfy the query.
529+
/// Returns an empty list if no objects are found.
530+
Future<List<T>> find() async {
531+
ParseResponse parseResponse = await query();
532+
if (parseResponse.success) {
533+
return parseResponse.results?.map((e) => e as T).toList() ?? <T>[];
534+
}
535+
throw parseResponse.error ?? ParseError();
536+
}
516537
}

packages/dart/lib/src/utils/parse_live_list.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,8 @@ class ParseLiveList<T extends ParseObject> {
377377
oldObject: _list[i].object, paths: _includePaths);
378378
if (after(_list[i].object, object) == null) {
379379
_list[i].object = object.clone(object.toJson(full: true));
380+
_eventStreamController.sink.add(ParseLiveListUpdateEvent<T>(
381+
i, object.clone(object.toJson(full: true))));
380382
} else {
381383
_list.removeAt(i).dispose();
382384
_eventStreamController.sink.add(ParseLiveListDeleteEvent<T>(
@@ -742,6 +744,11 @@ class ParseLiveListAddEvent<T extends ParseObject>
742744
ParseLiveListAddEvent(int index, T object) : super(index, object);
743745
}
744746

747+
class ParseLiveListUpdateEvent<T extends ParseObject>
748+
extends ParseLiveListEvent<T> {
749+
ParseLiveListUpdateEvent(int index, T object) : super(index, object);
750+
}
751+
745752
class ParseLiveListDeleteEvent<T extends ParseObject>
746753
extends ParseLiveListEvent<T> {
747754
ParseLiveListDeleteEvent(int index, T object) : super(index, object);

packages/dart/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ dependencies:
2626

2727
dev_dependencies:
2828
# Testing
29+
build_runner: ^2.0.1
2930
mockito: ^5.0.4
3031
test: ^1.16.8

packages/dart/test/parse_query_test.dart

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
import 'package:mockito/annotations.dart';
12
import 'package:mockito/mockito.dart';
23
import 'package:parse_server_sdk/parse_server_sdk.dart';
34
import 'package:test/test.dart';
45

5-
class MockClient extends Mock implements ParseClient {}
6+
import 'parse_query_test.mocks.dart';
67

8+
@GenerateMocks([ParseClient])
79
void main() {
810
group('queryBuilder', () {
911
test('whereRelatedTo', () async {
10-
final MockClient client = MockClient();
12+
final MockParseClient client = MockParseClient();
1113

1214
await Parse().initialize(
1315
'appId',
@@ -27,11 +29,31 @@ void main() {
2729
QueryBuilder<ParseObject>(ParseObject('_User', client: client));
2830
queryBuilder.whereRelatedTo('likes', 'Post', '8TOXdXf3tz');
2931

30-
when(client.data).thenReturn(ParseCoreData());
31-
await queryBuilder.query();
32+
when(client.get(
33+
any,
34+
options: anyNamed("options"),
35+
onReceiveProgress: anyNamed("onReceiveProgress"),
36+
)).thenAnswer((_) async => ParseNetworkResponse(
37+
statusCode: 200,
38+
data:
39+
"{\"results\":[{\"objectId\":\"eT9muOxBTJ\",\"username\":\"test\",\"createdAt\":\"2021-04-23T13:46:06.092Z\",\"updatedAt\":\"2021-04-23T13:46:23.586Z\",\"ACL\":{\"*\":{\"read\":true},\"eT9muOxBTJ\":{\"read\":true,\"write\":true}}}]}"));
3240

33-
final Uri result =
34-
Uri.parse(verify(client.get(captureAny!)).captured.single);
41+
ParseResponse response = await queryBuilder.query();
42+
43+
expect(response.results?.first, isA<ParseObject>());
44+
45+
ParseObject parseObject = response.results?.first;
46+
47+
expect(parseObject.get<String>(keyVarUsername), "test");
48+
expect(parseObject.objectId, "eT9muOxBTJ");
49+
expect(parseObject.createdAt, DateTime.parse("2021-04-23T13:46:06.092Z"));
50+
expect(parseObject.updatedAt, DateTime.parse("2021-04-23T13:46:23.586Z"));
51+
52+
final Uri result = Uri.parse(verify(client.get(
53+
captureAny,
54+
options: anyNamed("options"),
55+
onReceiveProgress: anyNamed("onReceiveProgress"),
56+
)).captured.single);
3557

3658
expect(result.path, '/classes/_User');
3759

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Mocks generated by Mockito 5.0.5 from annotations
2+
// in parse_server_sdk/test/parse_query_test.dart.
3+
// Do not manually edit this file.
4+
5+
import 'dart:async' as _i3;
6+
7+
import 'package:mockito/mockito.dart' as _i1;
8+
import 'package:parse_server_sdk/parse_server_sdk.dart' as _i2;
9+
10+
// ignore_for_file: comment_references
11+
// ignore_for_file: unnecessary_parenthesis
12+
13+
class _FakeParseCoreData extends _i1.Fake implements _i2.ParseCoreData {}
14+
15+
class _FakeParseNetworkResponse extends _i1.Fake
16+
implements _i2.ParseNetworkResponse {}
17+
18+
class _FakeParseNetworkByteResponse extends _i1.Fake
19+
implements _i2.ParseNetworkByteResponse {}
20+
21+
/// A class which mocks [ParseClient].
22+
///
23+
/// See the documentation for Mockito's code generation for more information.
24+
class MockParseClient extends _i1.Mock implements _i2.ParseClient {
25+
MockParseClient() {
26+
_i1.throwOnMissingStub(this);
27+
}
28+
29+
@override
30+
_i2.ParseCoreData get data => (super.noSuchMethod(Invocation.getter(#data),
31+
returnValue: _FakeParseCoreData()) as _i2.ParseCoreData);
32+
@override
33+
_i3.Future<_i2.ParseNetworkResponse> get(String? path,
34+
{_i2.ParseNetworkOptions? options,
35+
_i2.ProgressCallback? onReceiveProgress}) =>
36+
(super.noSuchMethod(
37+
Invocation.method(#get, [path],
38+
{#options: options, #onReceiveProgress: onReceiveProgress}),
39+
returnValue: Future<_i2.ParseNetworkResponse>.value(
40+
_FakeParseNetworkResponse()))
41+
as _i3.Future<_i2.ParseNetworkResponse>);
42+
@override
43+
_i3.Future<_i2.ParseNetworkResponse> put(String? path,
44+
{String? data, _i2.ParseNetworkOptions? options}) =>
45+
(super.noSuchMethod(
46+
Invocation.method(#put, [path], {#data: data, #options: options}),
47+
returnValue: Future<_i2.ParseNetworkResponse>.value(
48+
_FakeParseNetworkResponse()))
49+
as _i3.Future<_i2.ParseNetworkResponse>);
50+
@override
51+
_i3.Future<_i2.ParseNetworkResponse> post(String? path,
52+
{String? data, _i2.ParseNetworkOptions? options}) =>
53+
(super.noSuchMethod(
54+
Invocation.method(#post, [path], {#data: data, #options: options}),
55+
returnValue: Future<_i2.ParseNetworkResponse>.value(
56+
_FakeParseNetworkResponse())) as _i3
57+
.Future<_i2.ParseNetworkResponse>);
58+
@override
59+
_i3.Future<_i2.ParseNetworkResponse> postBytes(String? path,
60+
{_i3.Stream<List<int>>? data,
61+
_i2.ParseNetworkOptions? options,
62+
_i2.ProgressCallback? onSendProgress}) =>
63+
(super.noSuchMethod(
64+
Invocation.method(#postBytes, [
65+
path
66+
], {
67+
#data: data,
68+
#options: options,
69+
#onSendProgress: onSendProgress
70+
}),
71+
returnValue: Future<_i2.ParseNetworkResponse>.value(
72+
_FakeParseNetworkResponse()))
73+
as _i3.Future<_i2.ParseNetworkResponse>);
74+
@override
75+
_i3.Future<_i2.ParseNetworkResponse> delete(String? path,
76+
{_i2.ParseNetworkOptions? options}) =>
77+
(super.noSuchMethod(
78+
Invocation.method(#delete, [path], {#options: options}),
79+
returnValue: Future<_i2.ParseNetworkResponse>.value(
80+
_FakeParseNetworkResponse()))
81+
as _i3.Future<_i2.ParseNetworkResponse>);
82+
@override
83+
_i3.Future<_i2.ParseNetworkByteResponse> getBytes(String? path,
84+
{_i2.ParseNetworkOptions? options,
85+
_i2.ProgressCallback? onReceiveProgress}) =>
86+
(super.noSuchMethod(
87+
Invocation.method(#getBytes, [path],
88+
{#options: options, #onReceiveProgress: onReceiveProgress}),
89+
returnValue: Future<_i2.ParseNetworkByteResponse>.value(
90+
_FakeParseNetworkByteResponse()))
91+
as _i3.Future<_i2.ParseNetworkByteResponse>);
92+
}

packages/flutter/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,15 @@ if (response.success) {
265265
}
266266
```
267267

268+
### Alternative query methods
269+
270+
The standard query method `query()` returns a `ParseResponse` that contains the result or the error. As an alternative, you can also use `Future<List<T>> find()` for receiving options.
271+
This method returns an `Future` that either resolves in an error (equivalent of the error in the `ParseResponse`) or an `List` containing the queried objects. One difference, you should be aware of, is the fact, that `Future<List<T>> find()` will return an empty list instead of the 'No results' error you receive in case no object matches you query.
272+
273+
Choosing between `query()` and `find()` comes down to personal preference. Both methods can be used for querying a `ParseQuery`, just the output method differs.
274+
275+
Similar to `find()` the `QueryBuilder` also has a function called `Future<T>? first()`. Just like `find()` `first()` is just a convenience method that makes querying the first object satisfying the query simpler. `first()` returns an `Future`, that resoles in an error or the first object matching the query. In case no object satisfies the query, the result will be `null`.
276+
268277
## Complex queries
269278
You can create complex queries to really put your database to the test:
270279

packages/flutter/lib/src/utils/parse_live_list.dart

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -92,24 +92,20 @@ class _ParseLiveListWidgetState<T extends sdk.ParseObject>
9292
listeningIncludes: listeningIncludes,
9393
lazyLoading: lazyLoading,
9494
preloadedColumns: preloadedColumns,
95-
).then((sdk.ParseLiveList<T> value) {
96-
if (value.size > 0) {
97-
setState(() {
98-
noData = false;
99-
});
100-
} else {
101-
setState(() {
102-
noData = true;
103-
});
104-
}
95+
).then((sdk.ParseLiveList<T> livelist) {
10596
setState(() {
106-
_liveList = value;
97+
_noData = livelist.size == 0;
98+
_liveList = livelist;
10799
_liveList!.stream
108100
.listen((sdk.ParseLiveListEvent<sdk.ParseObject> event) {
109101
if (event is sdk.ParseLiveListAddEvent) {
110-
if (_animatedListKey.currentState != null)
102+
if (_animatedListKey.currentState != null) {
111103
_animatedListKey.currentState!
112104
.insertItem(event.index, duration: widget.duration);
105+
}
106+
setState(() {
107+
_noData = livelist.size == 0;
108+
});
113109
} else if (event is sdk.ParseLiveListDeleteEvent) {
114110
_animatedListKey.currentState!.removeItem(
115111
event.index,
@@ -126,15 +122,9 @@ class _ParseLiveListWidgetState<T extends sdk.ParseObject>
126122
preLoadedData: () => event.object as T,
127123
),
128124
duration: widget.duration);
129-
if (value.size > 0) {
130-
setState(() {
131-
noData = false;
132-
});
133-
} else {
134-
setState(() {
135-
noData = true;
136-
});
137-
}
125+
setState(() {
126+
_noData = livelist.size == 0;
127+
});
138128
}
139129
});
140130
});
@@ -146,17 +136,26 @@ class _ParseLiveListWidgetState<T extends sdk.ParseObject>
146136
final GlobalKey<AnimatedListState> _animatedListKey =
147137
GlobalKey<AnimatedListState>();
148138
final ChildBuilder<T>? removedItemBuilder;
149-
bool noData = true;
139+
bool _noData = true;
150140

151141
@override
152142
Widget build(BuildContext context) {
153143
if (_liveList == null) {
154144
return widget.listLoadingElement ?? Container();
145+
} else {
146+
return Stack(
147+
children: <Widget>[
148+
if (widget.queryEmptyElement != null)
149+
AnimatedOpacity(
150+
opacity: _noData ? 1 : 0,
151+
duration: widget.duration,
152+
child: widget.queryEmptyElement,
153+
),
154+
//_liveList isn't (checked above)
155+
buildAnimatedList(_liveList!),
156+
],
157+
);
155158
}
156-
if (noData) {
157-
return widget.queryEmptyElement ?? Container();
158-
}
159-
return buildAnimatedList(_liveList!);
160159
}
161160

162161
@override

packages/flutter/test/parse_query_test.dart

Lines changed: 0 additions & 47 deletions
This file was deleted.

0 commit comments

Comments
 (0)