diff --git a/.swiftformat b/.swiftformat index 79d00f34f91c..7f0db7664edf 100644 --- a/.swiftformat +++ b/.swiftformat @@ -2,4 +2,5 @@ --maxwidth 100 --wrapparameters afterfirst --disable sortedImports,unusedArguments,wrapMultilineStatementBraces ---exclude Pods,**/MainFlutterWindow.swift,**/AppDelegate.swift,**/.symlinks/** \ No newline at end of file +--exclude Pods,**/MainFlutterWindow.swift,**/AppDelegate.swift,**/.symlinks/** +--swiftversion 5.7 diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java index 68e420d9427c..539884cb0283 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java @@ -10,6 +10,9 @@ import com.google.android.gms.tasks.TaskCompletionSource; import com.google.android.gms.tasks.Tasks; import com.google.firebase.FirebaseApp; +import com.google.firebase.firestore.AggregateQuery; +import com.google.firebase.firestore.AggregateQuerySnapshot; +import com.google.firebase.firestore.AggregateSource; import com.google.firebase.firestore.DocumentReference; import com.google.firebase.firestore.DocumentSnapshot; import com.google.firebase.firestore.FieldPath; @@ -482,6 +485,32 @@ private Task waitForPendingWrites(Map arguments) { return taskCompletionSource.getTask(); } + private Task> aggregateQuery(Map arguments) { + TaskCompletionSource> taskCompletionSource = new TaskCompletionSource<>(); + + cachedThreadPool.execute( + () -> { + try { + Query query = (Query) Objects.requireNonNull(arguments.get("query")); + // NOTE: There is only "server" as the source at the moment. So this + // is unused for the time being. Using "AggregateSource.SERVER". + // String source = (String) Objects.requireNonNull(arguments.get("source")); + + AggregateQuery aggregateQuery = query.count(); + AggregateQuerySnapshot aggregateQuerySnapshot = + Tasks.await(aggregateQuery.get(AggregateSource.SERVER)); + Map result = new HashMap<>(); + result.put("count", aggregateQuerySnapshot.getCount()); + taskCompletionSource.setResult(result); + + } catch (Exception e) { + taskCompletionSource.setException(e); + } + }); + + return taskCompletionSource.getTask(); + } + @Override public void onMethodCall(MethodCall call, @NonNull final MethodChannel.Result result) { Task methodCallTask; @@ -560,6 +589,9 @@ public void onMethodCall(MethodCall call, @NonNull final MethodChannel.Result re case "Firestore#waitForPendingWrites": methodCallTask = waitForPendingWrites(call.arguments()); break; + case "AggregateQuery#count": + methodCallTask = aggregateQuery(call.arguments()); + break; default: result.notImplemented(); return; diff --git a/packages/cloud_firestore/cloud_firestore/example/ios/Podfile b/packages/cloud_firestore/cloud_firestore/example/ios/Podfile index b21e03e161c5..432189c78c2c 100644 --- a/packages/cloud_firestore/cloud_firestore/example/ios/Podfile +++ b/packages/cloud_firestore/cloud_firestore/example/ios/Podfile @@ -56,6 +56,9 @@ end post_install do |installer| installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' + end flutter_additional_ios_build_settings(target) end end diff --git a/packages/cloud_firestore/cloud_firestore/example/test_driver/query_e2e.dart b/packages/cloud_firestore/cloud_firestore/example/test_driver/query_e2e.dart index 289346a9f129..d6b0bd467741 100644 --- a/packages/cloud_firestore/cloud_firestore/example/test_driver/query_e2e.dart +++ b/packages/cloud_firestore/cloud_firestore/example/test_driver/query_e2e.dart @@ -1807,6 +1807,27 @@ void runQueryTests() { }, timeout: const Timeout.factor(3), ); + + test( + 'count()', + () async { + final collection = await initializeTest('count'); + + await Future.wait([ + collection.add({'foo': 'bar'}), + collection.add({'bar': 'baz'}) + ]); + + AggregateQuery query = collection.count(); + + AggregateQuerySnapshot snapshot = await query.get(); + + expect( + snapshot.count, + 2, + ); + }, + ); }); }); } diff --git a/packages/cloud_firestore/cloud_firestore/ios/Classes/FLTFirebaseFirestorePlugin.m b/packages/cloud_firestore/cloud_firestore/ios/Classes/FLTFirebaseFirestorePlugin.m index 2fcb7c1de0a7..f76b4c6c7f81 100644 --- a/packages/cloud_firestore/cloud_firestore/ios/Classes/FLTFirebaseFirestorePlugin.m +++ b/packages/cloud_firestore/cloud_firestore/ios/Classes/FLTFirebaseFirestorePlugin.m @@ -225,6 +225,8 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)flutter withMethodCallResult:methodCallResult]; } else if ([@"LoadBundle#snapshots" isEqualToString:call.method]) { [self setupLoadBundleListener:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"AggregateQuery#count" isEqualToString:call.method]) { + [self aggregateQuery:call.arguments withMethodCallResult:methodCallResult]; } else { methodCallResult.success(FlutterMethodNotImplemented); } @@ -535,6 +537,29 @@ - (void)batchCommit:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallRes }]; } +- (void)aggregateQuery:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + FIRQuery *query = arguments[@"query"]; + + // NOTE: There is only "server" as the source at the moment. So this + // is unused for the time being. Using "FIRAggregateSourceServer". + // NSString *source = arguments[@"source"]; + + FIRAggregateQuery *aggregateQuery = [query count]; + + [aggregateQuery aggregationWithSource:FIRAggregateSourceServer + completion:^(FIRAggregateQuerySnapshot *_Nullable snapshot, + NSError *_Nullable error) { + if (error != nil) { + result.error(nil, nil, nil, error); + } else { + NSMutableDictionary *response = [NSMutableDictionary dictionary]; + response[@"count"] = snapshot.count; + + result.success(response); + } + }]; +} + - (NSString *)registerEventChannelWithPrefix:(NSString *)prefix streamHandler:(NSObject *)handler { return [self registerEventChannelWithPrefix:prefix diff --git a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart index 6fcb06616f4d..2b48ff5b4abc 100755 --- a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart @@ -17,6 +17,7 @@ import 'package:meta/meta.dart'; export 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart' show + AggregateSource, ListEquality, FieldPath, Blob, @@ -46,3 +47,5 @@ part 'src/snapshot_metadata.dart'; part 'src/transaction.dart'; part 'src/utils/codec_utility.dart'; part 'src/write_batch.dart'; +part 'src/aggregate_query.dart'; +part 'src/aggregate_query_snapshot.dart'; diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/aggregate_query.dart b/packages/cloud_firestore/cloud_firestore/lib/src/aggregate_query.dart new file mode 100644 index 000000000000..3cc33beb0ab4 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/aggregate_query.dart @@ -0,0 +1,26 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +/// [AggregateQuery] represents the data at a particular location for retrieving metadata +/// without retrieving the actual documents. +class AggregateQuery { + AggregateQuery._(this._delegate, this.query) { + AggregateQueryPlatform.verifyExtends(_delegate); + } + + /// [Query] represents the query over the data at a particular location used by the [AggregateQuery] to + /// retrieve the metadata. + final Query query; + + final AggregateQueryPlatform _delegate; + + /// Returns an [AggregateQuerySnapshot] with the count of the documents that match the query. + Future get({ + AggregateSource source = AggregateSource.server, + }) async { + return AggregateQuerySnapshot._(await _delegate.get(source: source), query); + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/aggregate_query_snapshot.dart b/packages/cloud_firestore/cloud_firestore/lib/src/aggregate_query_snapshot.dart new file mode 100644 index 000000000000..be3d3c612b61 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/aggregate_query_snapshot.dart @@ -0,0 +1,20 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of cloud_firestore; + +/// [AggregateQuerySnapshot] represents a response to an [AggregateQuery] request. +class AggregateQuerySnapshot { + AggregateQuerySnapshot._(this._delegate, this.query) { + AggregateQuerySnapshotPlatform.verifyExtends(_delegate); + } + final AggregateQuerySnapshotPlatform _delegate; + + /// [Query] represents the query over the data at a particular location used by the [AggregateQuery] to + /// retrieve the metadata. + final Query query; + + /// Returns the count of the documents that match the query. + int get count => _delegate.count; +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/query.dart b/packages/cloud_firestore/cloud_firestore/lib/src/query.dart index 40733db6cebd..ab55ddb055e3 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/query.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/query.dart @@ -186,6 +186,8 @@ abstract class Query { required FromFirestore fromFirestore, required ToFirestore toFirestore, }); + + AggregateQuery count(); } /// Represents a [Query] over the data at a particular location. @@ -808,6 +810,13 @@ class _JsonQuery implements Query> { @override int get hashCode => Object.hash(runtimeType, firestore, _delegate); + + /// Represents an [AggregateQuery] over the data at a particular location for retrieving metadata + /// without retrieving the actual documents. + @override + AggregateQuery count() { + return AggregateQuery._(_delegate.count(), this); + } } class _WithConverterQuery implements Query { @@ -970,4 +979,11 @@ class _WithConverterQuery implements Query { @override int get hashCode => Object.hash(runtimeType, _fromFirestore, _toFirestore, _originalQuery); + + /// Represents an [AggregateQuery] over the data at a particular location for retrieving metadata + /// without retrieving the actual documents. + @override + AggregateQuery count() { + return _originalQuery.count(); + } } diff --git a/packages/cloud_firestore/cloud_firestore/test/aggregate_query_test.dart b/packages/cloud_firestore/cloud_firestore/test/aggregate_query_test.dart new file mode 100644 index 000000000000..f34936909aee --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/test/aggregate_query_test.dart @@ -0,0 +1,87 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:cloud_firestore_platform_interface/src/method_channel/method_channel_firestore.dart'; +import 'package:cloud_firestore_platform_interface/src/method_channel/method_channel_query.dart'; +import 'package:cloud_firestore_platform_interface/src/method_channel/utils/firestore_message_codec.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/services.dart'; + +import './mock.dart'; + +int kCount = 4; + +void main() { + setupCloudFirestoreMocks(); + MethodChannelFirebaseFirestore.channel = const MethodChannel( + 'plugins.flutter.io/firebase_firestore', + StandardMethodCodec(AggregateQueryMessageCodec()), + ); + + MethodChannelFirebaseFirestore.channel.setMockMethodCallHandler((call) async { + if (call.method == 'AggregateQuery#count') { + return { + 'count': kCount, + }; + } + + return null; + }); + + FirebaseFirestore? firestore; + + group('$AggregateQuery', () { + setUpAll(() async { + await Firebase.initializeApp(); + firestore = FirebaseFirestore.instance; + }); + + test('returns the correct `AggregateQuerySnapshot` with correct `count`', + () async { + Query query = firestore!.collection('flutter-tests'); + AggregateQuery aggregateQuery = query.count(); + + expect(query, aggregateQuery.query); + AggregateQuerySnapshot snapshot = await aggregateQuery.get(); + + expect(snapshot.count, equals(kCount)); + }); + }); +} + +class AggregateQueryMessageCodec extends FirestoreMessageCodec { + /// Constructor. + const AggregateQueryMessageCodec(); + static const int _kFirestoreInstance = 144; + static const int _kFirestoreQuery = 145; + static const int _kFirestoreSettings = 146; + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + // The following cases are only used by unit tests, and not by actual application + // code paths. + case _kFirestoreInstance: + String appName = readValue(buffer)! as String; + readValue(buffer); + final FirebaseApp app = Firebase.app(appName); + return MethodChannelFirebaseFirestore(app: app); + case _kFirestoreQuery: + Map values = + readValue(buffer)! as Map; + final FirebaseApp app = Firebase.app(); + return MethodChannelQuery( + MethodChannelFirebaseFirestore(app: app), + values['path'], + ); + case _kFirestoreSettings: + readValue(buffer); + return const Settings(); + default: + return super.readValueOfType(type, buffer); + } + } +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart index 584332c1031c..6cd079d7c4b7 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart @@ -24,6 +24,9 @@ export 'src/platform_interface/platform_interface_transaction.dart'; export 'src/platform_interface/platform_interface_write_batch.dart'; export 'src/platform_interface/platform_interface_load_bundle_task.dart'; export 'src/platform_interface/platform_interface_load_bundle_task_snapshot.dart'; +export 'src/platform_interface/platform_interface_aggregate_query.dart'; +export 'src/platform_interface/platform_interface_aggregate_query_snapshot.dart'; +export 'src/aggregate_source.dart'; export 'src/snapshot_metadata.dart'; export 'src/source.dart'; export 'src/load_bundle_task_state.dart'; diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/aggregate_source.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/aggregate_source.dart new file mode 100644 index 000000000000..e9893dd19dc8 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/aggregate_source.dart @@ -0,0 +1,9 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// [AggregateSource] represents the source of data for an [AggregateQuery]. +enum AggregateSource { + /// Indicates that the data should be retrieved from the server. + server, +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_aggregate_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_aggregate_query.dart new file mode 100644 index 000000000000..ee03c076cf03 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_aggregate_query.dart @@ -0,0 +1,33 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/src/method_channel/utils/source.dart'; + +import 'method_channel_firestore.dart'; +import '../../cloud_firestore_platform_interface.dart'; + +/// An implementation of [AggregateQueryPlatform] for the [MethodChannel] +class MethodChannelAggregateQuery extends AggregateQueryPlatform { + MethodChannelAggregateQuery(QueryPlatform query) : super(query); + + @override + Future get({ + required AggregateSource source, + }) async { + final Map? data = await MethodChannelFirebaseFirestore + .channel + .invokeMapMethod( + 'AggregateQuery#count', + { + 'query': query, + 'firestore': query.firestore, + 'source': getAggregateSourceString(source), + }, + ); + + return AggregateQuerySnapshotPlatform( + count: data!['count'] as int, + ); + } +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query.dart index e24b1065d30c..e06842546b0a 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query.dart @@ -11,6 +11,7 @@ import 'package:cloud_firestore_platform_interface/src/internal/pointer.dart'; import 'package:collection/collection.dart'; import 'package:flutter/services.dart'; +import 'method_channel_aggregate_query.dart'; import 'method_channel_firestore.dart'; import 'method_channel_query_snapshot.dart'; import 'utils/source.dart'; @@ -212,6 +213,13 @@ class MethodChannelQuery extends QueryPlatform { }); } + @override + AggregateQueryPlatform count() { + return MethodChannelAggregateQuery( + this, + ); + } + @override bool operator ==(Object other) { return runtimeType == other.runtimeType && diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/source.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/source.dart index 30a852b9e360..47386e04e55c 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/source.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/utils/source.dart @@ -1,8 +1,8 @@ -// ignore_for_file: require_trailing_commas -// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'package:cloud_firestore_platform_interface/src/aggregate_source.dart'; import 'package:cloud_firestore_platform_interface/src/source.dart'; /// Converts [Source] to [String] @@ -16,3 +16,11 @@ String getSourceString(Source source) { return 'default'; } } + +/// Converts [AggregateSource] to [String] +String getAggregateSourceString(AggregateSource source) { + switch (source) { + case AggregateSource.server: + return 'server'; + } +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_aggregate_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_aggregate_query.dart new file mode 100644 index 000000000000..41642fde0c34 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_aggregate_query.dart @@ -0,0 +1,34 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import '../../cloud_firestore_platform_interface.dart'; + +/// [AggregateQueryPlatform] represents the data at a particular location for retrieving metadata +/// without retrieving the actual documents. +abstract class AggregateQueryPlatform extends PlatformInterface { + AggregateQueryPlatform(this.query) : super(token: _token); + + static final Object _token = Object(); + + /// Throws an [AssertionError] if [instance] does not extend + /// [AggregateQueryPlatform]. + /// + /// This is used by the app-facing [AggregateQuery] to ensure that + /// the object in which it's going to delegate calls has been + /// constructed properly. + static void verifyExtends(AggregateQueryPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + } + + /// The [QueryPlatform] instance to which this [AggregateQueryPlatform] queries against to retrieve the metadata. + final QueryPlatform query; + + /// Returns an [AggregateQuerySnapshotPlatform] with the count of the documents that match the query. + Future get({ + required AggregateSource source, + }) async { + throw UnimplementedError('get() is not implemented'); + } +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_aggregate_query_snapshot.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_aggregate_query_snapshot.dart new file mode 100644 index 000000000000..55c7b38387ae --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_aggregate_query_snapshot.dart @@ -0,0 +1,29 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +/// [AggregateQuerySnapshotPlatform] represents a response to an [AggregateQueryPlatform] request. +class AggregateQuerySnapshotPlatform extends PlatformInterface { + AggregateQuerySnapshotPlatform({required count}) + : _count = count, + super(token: _token); + + static final Object _token = Object(); + + /// Throws an [AssertionError] if [instance] does not extend + /// [AggregateQuerySnapshotPlatform]. + /// + /// This is used by the app-facing [AggregateQuerySnapshot] to ensure that + /// the object in which it's going to delegate calls has been + /// constructed properly. + static void verifyExtends(AggregateQuerySnapshotPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + } + + final int _count; + + /// Returns the count of the documents that match the query. + int get count => _count; +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query.dart index c29790fe73fc..c89ba9c67023 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query.dart @@ -228,4 +228,10 @@ abstract class QueryPlatform extends PlatformInterface { QueryPlatform where(List> conditions) { throw UnimplementedError('where() is not implemented'); } + + /// Returns an [AggregateQueryPlatform] which uses the [QueryPlatform] to query for + /// metadata + AggregateQueryPlatform count() { + throw UnimplementedError('count() is not implemented'); + } } diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/test/platform_interface_tests/platform_interface_aggregate_query_test.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/test/platform_interface_tests/platform_interface_aggregate_query_test.dart new file mode 100644 index 000000000000..64e60a2208e7 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/test/platform_interface_tests/platform_interface_aggregate_query_test.dart @@ -0,0 +1,49 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/test_common.dart'; + +class AggregateQuery extends AggregateQueryPlatform { + AggregateQuery(QueryPlatform query) : super(query); +} + +class TestQuery extends QueryPlatform { + TestQuery._() : super(FirebaseFirestorePlatform.instance, null); +} + +late QueryPlatform query; +late AggregateQueryPlatform aggregateQuery; + +void main() { + initializeMethodChannel(); + group('$AggregateQueryPlatform()', () { + setUpAll(() async { + await Firebase.initializeApp(); + query = TestQuery._(); + aggregateQuery = AggregateQuery(query); + }); + + test('constructor', () { + expect(aggregateQuery, isInstanceOf()); + expect(aggregateQuery.query, isInstanceOf()); + }); + + test('throws if .get() is unimplemented ', () { + expect( + () => aggregateQuery.get(source: AggregateSource.server), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'get() is not implemented', + ), + ), + ); + }); + }); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/aggregate_query_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/aggregate_query_web.dart new file mode 100644 index 000000000000..63d3ee40074c --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/aggregate_query_web.dart @@ -0,0 +1,30 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; + +import 'interop/firestore.dart' as firestore_interop; + +/// Web implementation for Firestore [AggregateQueryPlatform]. +class AggregateQueryWeb extends AggregateQueryPlatform { + /// instance of [AggregateQuery] from the web plugin + final firestore_interop.AggregateQuery _delegate; + + /// [AggregateQueryWeb] represents the data at a particular location for retrieving metadata + /// without retrieving the actual documents. + AggregateQueryWeb(QueryPlatform query, _webQuery) + : _delegate = firestore_interop.AggregateQuery(_webQuery), + super(query); + + /// Returns an [AggregateQuerySnapshotPlatform] with the count of the documents that match the query. + @override + Future get({ + required AggregateSource source, + }) async { + // Note: There isn't a source option on the web platform + firestore_interop.AggregateQuerySnapshot snapshot = await _delegate.get(); + + return AggregateQuerySnapshotPlatform(count: snapshot.count); + } +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore.dart index 46e8c6efcf4c..716113e4218f 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore.dart @@ -772,3 +772,33 @@ abstract class FieldValue { static final FieldValue _serverTimestamp = _FieldValueServerTimestamp(); static final FieldValue _delete = _FieldValueDelete(); } + +class AggregateQuery { + AggregateQuery(Query query) : _jsQuery = query.jsObject; + final firestore_interop.QueryJsImpl _jsQuery; + Future get() async { + return handleThenable( + firestore_interop.getCountFromServer(_jsQuery)) + .then(AggregateQuerySnapshot.getInstance); + } +} + +class AggregateQuerySnapshot + extends JsObjectWrapper { + static final _expando = Expando(); + late final Map _data; + + /// Creates a new [AggregateQuerySnapshot] from a [jsObject]. + static AggregateQuerySnapshot getInstance( + firestore_interop.AggregateQuerySnapshotJsImpl jsObject) { + return _expando[jsObject] ??= + AggregateQuerySnapshot._fromJsObject(jsObject); + } + + AggregateQuerySnapshot._fromJsObject( + firestore_interop.AggregateQuerySnapshotJsImpl jsObject) + : _data = Map.from(dartify(jsObject.data())), + super.fromJsObject(jsObject); + + int get count => _data['count']! as int; +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore_interop.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore_interop.dart index d316518c3d4f..ef47fc9d4b09 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore_interop.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/interop/firestore_interop.dart @@ -651,3 +651,13 @@ external Object get arrayRemove; @JS() external Object get arrayUnion; + +@JS() +external PromiseJsImpl getCountFromServer( + QueryJsImpl query, +); + +@JS('AggregateQuerySnapshot') +abstract class AggregateQuerySnapshotJsImpl { + external Map data(); +} diff --git a/packages/cloud_firestore/cloud_firestore_web/lib/src/query_web.dart b/packages/cloud_firestore/cloud_firestore_web/lib/src/query_web.dart index 7f1cccea86fb..b8c1cc57a418 100644 --- a/packages/cloud_firestore/cloud_firestore_web/lib/src/query_web.dart +++ b/packages/cloud_firestore/cloud_firestore_web/lib/src/query_web.dart @@ -7,6 +7,7 @@ import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_inte import 'package:collection/collection.dart'; import 'package:cloud_firestore_web/src/utils/encode_utility.dart'; +import 'aggregate_query_web.dart'; import 'internals.dart'; import 'interop/firestore.dart' as firestore_interop; import 'utils/web_utils.dart'; @@ -232,4 +233,9 @@ class QueryWeb extends QueryPlatform { 'where': conditions, }); } + + @override + AggregateQueryPlatform count() { + return AggregateQueryWeb(this, _webQuery); + } } diff --git a/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart b/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart index 24c0aea6bb17..4f33f8ce8244 100644 --- a/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart +++ b/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart @@ -6,4 +6,4 @@ part of firebase_core_web; /// The currently supported Firebase JS SDK version. -const String supportedFirebaseJsSdkVersion = '9.9.0'; +const String supportedFirebaseJsSdkVersion = '9.11.0';