diff --git a/.cirrus.yml b/.cirrus.yml index d323feeba2c2..0897b3dc4911 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -69,6 +69,7 @@ task: SIMCTL_CHILD_MAPS_API_KEY: ENCRYPTED[596a9f6bca436694625ac50851dc5da6b4d34cba8025f7db5bc9465142e8cd44e15f69e3507787753accebfc4910d550] setup_script: - brew update + - brew upgrade cocoapods - brew install libimobiledevice - brew install ideviceinstaller - brew install ios-deploy diff --git a/packages/cloud_firestore/CHANGELOG.md b/packages/cloud_firestore/CHANGELOG.md index 1173ff2927d8..1b31f6262404 100644 --- a/packages/cloud_firestore/CHANGELOG.md +++ b/packages/cloud_firestore/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.12.3 + +* Added support for `Query.collectionGroup`. + ## 0.12.2 * Ensure that all channel calls to the Dart side from the Java side are done diff --git a/packages/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/CloudFirestorePlugin.java b/packages/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/CloudFirestorePlugin.java index 0470713ef234..ccb5f5baf41f 100644 --- a/packages/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/CloudFirestorePlugin.java +++ b/packages/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/CloudFirestorePlugin.java @@ -89,6 +89,16 @@ private FirebaseFirestore getFirestore(Map arguments) { return FirebaseFirestore.getInstance(FirebaseApp.getInstance(appName)); } + private Query getReference(Map arguments) { + if ((boolean) arguments.get("isCollectionGroup")) return getCollectionGroupReference(arguments); + else return getCollectionReference(arguments); + } + + private Query getCollectionGroupReference(Map arguments) { + String path = (String) arguments.get("path"); + return getFirestore(arguments).collectionGroup(path); + } + private CollectionReference getCollectionReference(Map arguments) { String path = (String) arguments.get("path"); return getFirestore(arguments).collection(path); @@ -180,7 +190,7 @@ private Transaction getTransaction(Map arguments) { } private Query getQuery(Map arguments) { - Query query = getCollectionReference(arguments); + Query query = getReference(arguments); @SuppressWarnings("unchecked") Map parameters = (Map) arguments.get("parameters"); if (parameters == null) return query; diff --git a/packages/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/FlutterFirebaseAppRegistrar.java b/packages/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/FlutterFirebaseAppRegistrar.java index efdf7eefcc17..83b35dcab473 100644 --- a/packages/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/FlutterFirebaseAppRegistrar.java +++ b/packages/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/FlutterFirebaseAppRegistrar.java @@ -8,7 +8,7 @@ public class FlutterFirebaseAppRegistrar implements ComponentRegistrar { private static final String LIBRARY_NAME = "flutter-fire-fst"; - private static final String LIBRARY_VERSION = "0.12.2"; + private static final String LIBRARY_VERSION = "0.12.3"; @Override public List> getComponents() { diff --git a/packages/cloud_firestore/example/test_driver/cloud_firestore.dart b/packages/cloud_firestore/example/test_driver/cloud_firestore.dart index be91b436a6c9..c271804590fb 100644 --- a/packages/cloud_firestore/example/test_driver/cloud_firestore.dart +++ b/packages/cloud_firestore/example/test_driver/cloud_firestore.dart @@ -25,7 +25,7 @@ void main() { firestore = Firestore(app: app); }); - test('getDocuments', () async { + test('getDocumentsFromCollection', () async { final Query query = firestore .collection('messages') .where('message', isEqualTo: 'Hello world!') @@ -43,6 +43,21 @@ void main() { expect(snapshot.data['message'], 'Hello world!'); }); + test('getDocumentsFromCollectionGroup', () async { + final Query query = firestore + .collectionGroup('reviews') + .where('message', isEqualTo: 'Hello world!') + .limit(1); + final QuerySnapshot querySnapshot = await query.getDocuments(); + expect(querySnapshot.documents.first['message'], 'Hello world!'); + final DocumentReference firstDoc = + querySnapshot.documents.first.reference; + final DocumentSnapshot documentSnapshot = await firstDoc.get(); + expect(documentSnapshot.data['message'], 'Hello world!'); + final DocumentSnapshot snapshot = await firstDoc.snapshots().first; + expect(snapshot.data['message'], 'Hello world!'); + }); + test('increment', () async { final DocumentReference ref = firestore.collection('messages').document(); await ref.setData({ diff --git a/packages/cloud_firestore/ios/Classes/CloudFirestorePlugin.m b/packages/cloud_firestore/ios/Classes/CloudFirestorePlugin.m index 2b46c408ebf2..2ce65c02ebff 100644 --- a/packages/cloud_firestore/ios/Classes/CloudFirestorePlugin.m +++ b/packages/cloud_firestore/ios/Classes/CloudFirestorePlugin.m @@ -7,7 +7,7 @@ #import #define LIBRARY_NAME @"flutter-firebase_cloud_firestore" -#define LIBRARY_VERSION @"0.12.2" +#define LIBRARY_VERSION @"0.12.3" static FlutterError *getFlutterError(NSError *error) { if (error == nil) return nil; @@ -42,7 +42,14 @@ } static FIRQuery *getQuery(NSDictionary *arguments) { - FIRQuery *query = [getFirestore(arguments) collectionWithPath:arguments[@"path"]]; + NSNumber *data = arguments[@"isCollectionGroup"]; + BOOL isCollectionGroup = data.boolValue; + FIRQuery *query; + if (isCollectionGroup) { + query = [getFirestore(arguments) collectionGroupWithID:arguments[@"path"]]; + } else { + query = [getFirestore(arguments) collectionWithPath:arguments[@"path"]]; + } NSDictionary *parameters = arguments[@"parameters"]; NSArray *whereConditions = parameters[@"where"]; for (id item in whereConditions) { diff --git a/packages/cloud_firestore/lib/src/firestore.dart b/packages/cloud_firestore/lib/src/firestore.dart index 1b5eac33588b..a4bbaaffd34d 100644 --- a/packages/cloud_firestore/lib/src/firestore.dart +++ b/packages/cloud_firestore/lib/src/firestore.dart @@ -71,6 +71,17 @@ class Firestore { return CollectionReference._(this, path.split('/')); } + /// Gets a [Query] for the specified collection group. + Query collectionGroup(String path) { + assert(path != null); + assert(!path.contains("/"), "Collection IDs must not contain '/'."); + return Query._( + firestore: this, + isCollectionGroup: true, + pathComponents: path.split('/'), + ); + } + /// Gets a [DocumentReference] for the specified Firestore path. DocumentReference document(String path) { assert(path != null); diff --git a/packages/cloud_firestore/lib/src/query.dart b/packages/cloud_firestore/lib/src/query.dart index 5969e76477b8..e166ee54962d 100644 --- a/packages/cloud_firestore/lib/src/query.dart +++ b/packages/cloud_firestore/lib/src/query.dart @@ -9,8 +9,10 @@ class Query { Query._( {@required this.firestore, @required List pathComponents, + bool isCollectionGroup = false, Map parameters}) : _pathComponents = pathComponents, + _isCollectionGroup = isCollectionGroup, _parameters = parameters ?? Map.unmodifiable({ 'where': List>.unmodifiable(>[]), @@ -24,12 +26,14 @@ class Query { final List _pathComponents; final Map _parameters; + final bool _isCollectionGroup; String get _path => _pathComponents.join('/'); Query _copyWithParameters(Map parameters) { return Query._( firestore: firestore, + isCollectionGroup: _isCollectionGroup, pathComponents: _pathComponents, parameters: Map.unmodifiable( Map.from(_parameters)..addAll(parameters), @@ -58,6 +62,7 @@ class Query { { 'app': firestore.app.name, 'path': _path, + 'isCollectionGroup': _isCollectionGroup, 'parameters': _parameters, }, ).then((dynamic result) => result); @@ -88,6 +93,7 @@ class Query { { 'app': firestore.app.name, 'path': _path, + 'isCollectionGroup': _isCollectionGroup, 'parameters': _parameters, 'source': _getSourceString(source), }, diff --git a/packages/cloud_firestore/pubspec.yaml b/packages/cloud_firestore/pubspec.yaml index cf8a0df0b2c9..b25a490d650b 100755 --- a/packages/cloud_firestore/pubspec.yaml +++ b/packages/cloud_firestore/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Cloud Firestore, a cloud-hosted, noSQL database live synchronization and offline support on Android and iOS. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/cloud_firestore -version: 0.12.2 +version: 0.12.3 flutter: plugin: diff --git a/packages/cloud_firestore/test/cloud_firestore_test.dart b/packages/cloud_firestore/test/cloud_firestore_test.dart index 8b47e1daba49..bee9a4e01d41 100755 --- a/packages/cloud_firestore/test/cloud_firestore_test.dart +++ b/packages/cloud_firestore/test/cloud_firestore_test.dart @@ -17,6 +17,7 @@ void main() { Firestore firestore; final List log = []; CollectionReference collectionReference; + Query collectionGroupQuery; Transaction transaction; const Map kMockDocumentSnapshotData = { '1': 2 @@ -25,7 +26,6 @@ void main() { "hasPendingWrites": false, "isFromCache": false, }; - setUp(() async { mockHandleId = 0; // Required for FirebaseApp.configure @@ -41,6 +41,7 @@ void main() { ); firestore = Firestore(app: app); collectionReference = firestore.collection('foo'); + collectionGroupQuery = firestore.collectionGroup('bar'); transaction = Transaction(0, firestore); Firestore.channel.setMockMethodCallHandler((MethodCall methodCall) async { log.add(methodCall); @@ -291,6 +292,7 @@ void main() { expect(a.hashCode == b.hashCode, isFalse); }); }); + group('CollectionsReference', () { test('id', () async { expect(collectionReference.id, equals('foo')); @@ -318,6 +320,7 @@ void main() { arguments: { 'app': app.name, 'path': 'foo', + 'isCollectionGroup': false, 'parameters': { 'where': >[], 'orderBy': >[], @@ -346,6 +349,7 @@ void main() { arguments: { 'app': app.name, 'path': 'foo', + 'isCollectionGroup': false, 'parameters': { 'where': >[ ['createdAt', '<', 100], @@ -377,6 +381,7 @@ void main() { arguments: { 'app': app.name, 'path': 'foo', + 'isCollectionGroup': false, 'parameters': { 'where': >[ ['profile', '==', null], @@ -408,6 +413,7 @@ void main() { arguments: { 'app': app.name, 'path': 'foo', + 'isCollectionGroup': false, 'parameters': { 'where': >[], 'orderBy': >[ @@ -583,7 +589,7 @@ void main() { }); group('Query', () { - test('getDocuments', () async { + test('getDocumentsFromCollection', () async { QuerySnapshot snapshot = await collectionReference.getDocuments(source: Source.server); DocumentSnapshot document = snapshot.documents.first; @@ -634,6 +640,7 @@ void main() { arguments: { 'app': app.name, 'path': 'foo', + 'isCollectionGroup': false, 'source': 'server', 'parameters': { 'where': >[], @@ -646,6 +653,7 @@ void main() { arguments: { 'app': app.name, 'path': 'foo', + 'isCollectionGroup': false, 'source': 'default', 'parameters': { 'where': >[], @@ -662,6 +670,7 @@ void main() { arguments: { 'app': app.name, 'path': 'foo', + 'isCollectionGroup': false, 'source': 'default', 'parameters': { 'where': >[], @@ -678,6 +687,7 @@ void main() { arguments: { 'app': app.name, 'path': 'foo', + 'isCollectionGroup': false, 'source': 'default', 'parameters': { 'where': >[], @@ -694,6 +704,137 @@ void main() { arguments: { 'app': app.name, 'path': 'foo', + 'isCollectionGroup': false, + 'source': 'default', + 'parameters': { + 'where': >[], + 'orderBy': >[], + 'endBeforeDocument': { + 'id': '0', + 'data': kMockDocumentSnapshotData, + }, + }, + }, + ), + ], + ), + ); + }); + test('getDocumentsFromCollectionGroup', () async { + QuerySnapshot snapshot = await collectionGroupQuery.getDocuments(); + DocumentSnapshot document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('bar/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + // startAtDocument + snapshot = + await collectionGroupQuery.startAtDocument(document).getDocuments(); + document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('bar/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + // startAfterDocument + snapshot = await collectionGroupQuery + .startAfterDocument(document) + .getDocuments(); + document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('bar/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + // endAtDocument + snapshot = + await collectionGroupQuery.endAtDocument(document).getDocuments(); + document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('bar/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + // endBeforeDocument + snapshot = await collectionGroupQuery + .endBeforeDocument(document) + .getDocuments(); + document = snapshot.documents.first; + expect(document.documentID, equals('0')); + expect(document.reference.path, equals('bar/0')); + expect(document.data, equals(kMockDocumentSnapshotData)); + + expect( + log, + equals( + [ + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'bar', + 'isCollectionGroup': true, + 'parameters': { + 'where': >[], + 'orderBy': >[], + }, + 'source': 'default', + }, + ), + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'bar', + 'isCollectionGroup': true, + 'parameters': { + 'where': >[], + 'orderBy': >[], + 'startAtDocument': { + 'id': '0', + 'data': kMockDocumentSnapshotData, + }, + }, + 'source': 'default', + }, + ), + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'bar', + 'isCollectionGroup': true, + 'parameters': { + 'where': >[], + 'orderBy': >[], + 'startAfterDocument': { + 'id': '0', + 'data': kMockDocumentSnapshotData, + }, + }, + 'source': 'default', + }, + ), + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'bar', + 'isCollectionGroup': true, + 'parameters': { + 'where': >[], + 'orderBy': >[], + 'endAtDocument': { + 'id': '0', + 'data': kMockDocumentSnapshotData, + }, + }, + 'source': 'default', + }, + ), + isMethodCall( + 'Query#getDocuments', + arguments: { + 'app': app.name, + 'path': 'bar', + 'isCollectionGroup': true, 'source': 'default', 'parameters': { 'where': >[], @@ -703,6 +844,7 @@ void main() { 'data': kMockDocumentSnapshotData, }, }, + 'source': 'default', }, ), ],