diff --git a/.gitignore b/.gitignore index cdcc86d0f496..ee5a3f5af603 100644 --- a/.gitignore +++ b/.gitignore @@ -41,5 +41,3 @@ build/ .project .classpath .settings -.flutter-plugins-dependencies - diff --git a/packages/cloud_firestore/cloud_firestore/.gitignore b/packages/cloud_firestore/cloud_firestore/.gitignore index 46385dab97b6..dd1148532910 100644 --- a/packages/cloud_firestore/cloud_firestore/.gitignore +++ b/packages/cloud_firestore/cloud_firestore/.gitignore @@ -1 +1,3 @@ ios/Classes/UserAgent.h +.flutter-plugins-dependencies +generated_plugin_registrant.dart diff --git a/packages/cloud_firestore/cloud_firestore/CHANGELOG.md b/packages/cloud_firestore/cloud_firestore/CHANGELOG.md index 2a1fe9216f18..44d409c0ab68 100644 --- a/packages/cloud_firestore/cloud_firestore/CHANGELOG.md +++ b/packages/cloud_firestore/cloud_firestore/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.13.1 + +* Migrate to `cloud_firestore_platform_interface`. + ## 0.13.0+2 * Fixed `persistenceEnabled`, `sslEnabled`, and `timestampsInSnapshotsEnabled` on iOS. diff --git a/packages/cloud_firestore/cloud_firestore/analysis_options.yaml b/packages/cloud_firestore/cloud_firestore/analysis_options.yaml index bbfa25e2146a..880f38c52a83 100644 --- a/packages/cloud_firestore/cloud_firestore/analysis_options.yaml +++ b/packages/cloud_firestore/cloud_firestore/analysis_options.yaml @@ -1,8 +1,3 @@ -# This is a temporary file to allow us to land a new set of linter rules in a -# series of manageable patches instead of one gigantic PR. It disables some of -# the new lints that are already failing on this plugin, for this plugin. It -# should be deleted and the failing lints addressed as soon as possible. - include: ../../../analysis_options.yaml analyzer: diff --git a/packages/cloud_firestore/cloud_firestore/example/android/gradle.properties b/packages/cloud_firestore/cloud_firestore/example/android/gradle.properties index 4d3226abc21b..a6738207fd15 100755 --- a/packages/cloud_firestore/cloud_firestore/example/android/gradle.properties +++ b/packages/cloud_firestore/cloud_firestore/example/android/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file +android.enableJetifier=true +android.enableR8=true diff --git a/packages/cloud_firestore/cloud_firestore/example/ios/Flutter/Flutter.podspec b/packages/cloud_firestore/cloud_firestore/example/ios/Flutter/Flutter.podspec new file mode 100644 index 000000000000..5ca30416bac0 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/example/ios/Flutter/Flutter.podspec @@ -0,0 +1,18 @@ +# +# NOTE: This podspec is NOT to be published. It is only used as a local source! +# + +Pod::Spec.new do |s| + s.name = 'Flutter' + s.version = '1.0.0' + s.summary = 'High-performance, high-fidelity mobile apps.' + s.description = <<-DESC +Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. + DESC + s.homepage = 'https://flutter.io' + s.license = { :type => 'MIT' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } + s.ios.deployment_target = '8.0' + s.vendored_frameworks = 'Flutter.framework' +end diff --git a/packages/cloud_firestore/cloud_firestore/example/lib/main.dart b/packages/cloud_firestore/cloud_firestore/example/lib/main.dart index 9b1954d0a0f8..34bffa9cb9f2 100755 --- a/packages/cloud_firestore/cloud_firestore/example/lib/main.dart +++ b/packages/cloud_firestore/cloud_firestore/example/lib/main.dart @@ -33,7 +33,10 @@ class MessageList extends StatelessWidget { @override Widget build(BuildContext context) { return StreamBuilder( - stream: firestore.collection('messages').snapshots(), + stream: firestore + .collection("messages") + .orderBy("created_at", descending: true) + .snapshots(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (!snapshot.hasData) return const Text('Loading...'); final int messageCount = snapshot.data.documents.length; @@ -43,6 +46,10 @@ class MessageList extends StatelessWidget { final DocumentSnapshot document = snapshot.data.documents[index]; final dynamic message = document['message']; return ListTile( + trailing: IconButton( + onPressed: () => document.reference.delete(), + icon: Icon(Icons.delete), + ), title: Text( message != null ? message.toString() : '', ), @@ -69,11 +76,77 @@ class MyHomePage extends StatelessWidget { }); } + Future _runTransaction() async { + firestore.runTransaction((Transaction transaction) async { + final allDocs = await firestore.collection("messages").getDocuments(); + final toBeRetrieved = + allDocs.documents.sublist(allDocs.documents.length ~/ 2); + final toBeDeleted = + allDocs.documents.sublist(0, allDocs.documents.length ~/ 2); + await Future.forEach(toBeDeleted, (DocumentSnapshot snapshot) async { + await transaction.delete(snapshot.reference); + }); + + await Future.forEach(toBeRetrieved, (DocumentSnapshot snapshot) async { + await transaction.update(snapshot.reference, { + "message": "Updated from Transaction", + "created_at": FieldValue.serverTimestamp() + }); + }); + }); + + await Future.forEach(List.generate(2, (index) => index), (item) async { + await firestore.runTransaction((Transaction transaction) async { + await Future.forEach(List.generate(10, (index) => index), (item) async { + await transaction.set(firestore.collection("messages").document(), { + "message": "Created from Transaction $item", + "created_at": FieldValue.serverTimestamp() + }); + }); + }); + }); + } + + Future _runBatchWrite() async { + final batchWrite = firestore.batch(); + final querySnapshot = await firestore + .collection("messages") + .orderBy("created_at") + .limit(12) + .getDocuments(); + querySnapshot.documents + .sublist(0, querySnapshot.documents.length - 3) + .forEach((DocumentSnapshot doc) { + batchWrite.updateData(doc.reference, { + "message": "Batched message", + "created_at": FieldValue.serverTimestamp() + }); + }); + batchWrite.setData(firestore.collection("messages").document(), { + "message": "Batched message created", + "created_at": FieldValue.serverTimestamp() + }); + batchWrite.delete( + querySnapshot.documents[querySnapshot.documents.length - 2].reference); + batchWrite.delete(querySnapshot.documents.last.reference); + await batchWrite.commit(); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Firestore Example'), + actions: [ + FlatButton( + onPressed: _runTransaction, + child: Text("Run Transaction"), + ), + FlatButton( + onPressed: _runBatchWrite, + child: Text("Batch Write"), + ) + ], ), body: MessageList(firestore: firestore), floatingActionButton: FloatingActionButton( diff --git a/packages/cloud_firestore/cloud_firestore/example/test_driver/cloud_firestore.dart b/packages/cloud_firestore/cloud_firestore/example/test_driver/cloud_firestore.dart index c154f80f6271..3cf96d55d199 100644 --- a/packages/cloud_firestore/cloud_firestore/example/test_driver/cloud_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/example/test_driver/cloud_firestore.dart @@ -14,7 +14,7 @@ void main() { Firestore firestore; Firestore firestoreWithSettings; - setUp(() async { + setUpAll(() async { final FirebaseOptions firebaseOptions = const FirebaseOptions( googleAppID: '1:79601577497:ios:5f2bcc6ba8cecddd', gcmSenderID: '79601577497', diff --git a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart index aef386e77d26..87683c7465ef 100755 --- a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart @@ -5,32 +5,25 @@ library cloud_firestore; import 'dart:async'; -import 'dart:convert'; -import 'dart:typed_data'; -import 'dart:ui' show hashValues, hashList; +import 'dart:ui' show hashList; -import 'package:collection/collection.dart'; +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart' + as platform; import 'package:firebase_core/firebase_core.dart'; -import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; -import 'package:flutter/services.dart'; -import 'package:meta/meta.dart'; -import 'src/utils/auto_id_generator.dart'; +export 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart' + show FieldPath, Blob, GeoPoint, Timestamp, Source, DocumentChangeType; -part 'src/blob.dart'; part 'src/collection_reference.dart'; part 'src/document_change.dart'; +part 'src/utils/platform_utils.dart'; part 'src/document_reference.dart'; part 'src/document_snapshot.dart'; -part 'src/field_path.dart'; part 'src/field_value.dart'; part 'src/firestore.dart'; -part 'src/firestore_message_codec.dart'; -part 'src/geo_point.dart'; part 'src/query.dart'; part 'src/query_snapshot.dart'; +part 'src/utils/codec_utility.dart'; part 'src/snapshot_metadata.dart'; -part 'src/timestamp.dart'; part 'src/transaction.dart'; part 'src/write_batch.dart'; -part 'src/source.dart'; diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/blob.dart b/packages/cloud_firestore/cloud_firestore/lib/src/blob.dart deleted file mode 100644 index 665efead5622..000000000000 --- a/packages/cloud_firestore/cloud_firestore/lib/src/blob.dart +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2018, 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; - -class Blob { - const Blob(this.bytes); - - final Uint8List bytes; - - @override - bool operator ==(dynamic other) => - other is Blob && - const DeepCollectionEquality().equals(other.bytes, bytes); - - @override - int get hashCode => hashList(bytes); -} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/collection_reference.dart b/packages/cloud_firestore/cloud_firestore/lib/src/collection_reference.dart index f1de3a8ba4b9..2f584667f930 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/collection_reference.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/collection_reference.dart @@ -8,8 +8,10 @@ part of cloud_firestore; /// document references, and querying for documents (using the methods /// inherited from [Query]). class CollectionReference extends Query { - CollectionReference._(Firestore firestore, List pathComponents) - : super._(firestore: firestore, pathComponents: pathComponents); + final platform.CollectionReferencePlatform _delegate; + + CollectionReference._(this._delegate, Firestore firestore) + : super._(_delegate, firestore); /// ID of the referenced collection. String get id => _pathComponents.isEmpty ? null : _pathComponents.last; @@ -21,10 +23,7 @@ class CollectionReference extends Query { if (_pathComponents.length < 2) { return null; } - return DocumentReference._( - firestore, - (List.from(_pathComponents)..removeLast()), - ); + return DocumentReference._(_delegate.parent(), firestore); } /// A string containing the slash-separated path to this CollectionReference @@ -37,16 +36,8 @@ class CollectionReference extends Query { /// /// The unique key generated is prefixed with a client-generated timestamp /// so that the resulting list will be chronologically-sorted. - DocumentReference document([String path]) { - List childPath; - if (path == null) { - final String key = AutoIdGenerator.autoId(); - childPath = List.from(_pathComponents)..add(key); - } else { - childPath = List.from(_pathComponents)..addAll(path.split(('/'))); - } - return DocumentReference._(firestore, childPath); - } + DocumentReference document([String path]) => + DocumentReference._(_delegate.document(path), firestore); /// Returns a `DocumentReference` with an auto-generated ID, after /// populating it with provided [data]. diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/document_change.dart b/packages/cloud_firestore/cloud_firestore/lib/src/document_change.dart index 02f797a65d24..3f94fa940672 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/document_change.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/document_change.dart @@ -4,56 +4,36 @@ part of cloud_firestore; -/// An enumeration of document change types. -enum DocumentChangeType { - /// Indicates a new document was added to the set of documents matching the - /// query. - added, - - /// Indicates a document within the query was modified. - modified, - - /// Indicates a document within the query was removed (either deleted or no - /// longer matches the query. - removed, -} - /// A DocumentChange represents a change to the documents matching a query. /// /// It contains the document affected and the type of change that occurred /// (added, modified, or removed). class DocumentChange { - DocumentChange._(Map data, Firestore firestore) - : oldIndex = data['oldIndex'], - newIndex = data['newIndex'], - document = DocumentSnapshot._( - data['path'], - _asStringKeyedMap(data['document']), - SnapshotMetadata._(data["metadata"]["hasPendingWrites"], - data["metadata"]["isFromCache"]), - firestore, - ), - type = DocumentChangeType.values.firstWhere((DocumentChangeType type) { - return type.toString() == data['type']; - }); + final platform.DocumentChangePlatform _delegate; + final Firestore _firestore; + + DocumentChange._(this._delegate, this._firestore) { + platform.DocumentChangePlatform.verifyExtends(_delegate); + } /// The type of change that occurred (added, modified, or removed). - final DocumentChangeType type; + platform.DocumentChangeType get type => _delegate.type; /// The index of the changed document in the result set immediately prior to /// this [DocumentChange] (i.e. supposing that all prior DocumentChange objects /// have been applied). /// /// -1 for [DocumentChangeType.added] events. - final int oldIndex; + int get oldIndex => _delegate.oldIndex; /// The index of the changed document in the result set immediately after this /// DocumentChange (i.e. supposing that all prior [DocumentChange] objects /// and the current [DocumentChange] object have been applied). /// /// -1 for [DocumentChangeType.removed] events. - final int newIndex; + int get newIndex => _delegate.newIndex; /// The document affected by this change. - final DocumentSnapshot document; + DocumentSnapshot get document => + DocumentSnapshot._(_delegate.document, _firestore); } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/document_reference.dart b/packages/cloud_firestore/cloud_firestore/lib/src/document_reference.dart index 413cf41be0be..3830f9e3696d 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/document_reference.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/document_reference.dart @@ -11,35 +11,32 @@ part of cloud_firestore; /// A [DocumentReference] can also be used to create a [CollectionReference] /// to a subcollection. class DocumentReference { - DocumentReference._(this.firestore, List pathComponents) - : _pathComponents = pathComponents, - assert(firestore != null); + platform.DocumentReferencePlatform _delegate; /// The Firestore instance associated with this document reference final Firestore firestore; - final List _pathComponents; + DocumentReference._(this._delegate, this.firestore) { + platform.DocumentReferencePlatform.verifyExtends(_delegate); + } @override bool operator ==(dynamic o) => o is DocumentReference && o.firestore == firestore && o.path == path; @override - int get hashCode => hashList(_pathComponents); + int get hashCode => hashList(_delegate.path.split("/")); /// Parent returns the containing [CollectionReference]. CollectionReference parent() { - return CollectionReference._( - firestore, - (List.from(_pathComponents)..removeLast()), - ); + return CollectionReference._(_delegate.parent(), firestore); } /// Slash-delimited path representing the database location of this query. - String get path => _pathComponents.join('/'); + String get path => _delegate.path; /// This document's given or generated ID in the collection. - String get documentID => _pathComponents.last; + String get documentID => _delegate.documentID; /// Writes to the document referred to by this [DocumentReference]. /// @@ -48,15 +45,8 @@ class DocumentReference { /// If [merge] is true, the provided data will be merged into an /// existing document instead of overwriting. Future setData(Map data, {bool merge = false}) { - return Firestore.channel.invokeMethod( - 'DocumentReference#setData', - { - 'app': firestore.app.name, - 'path': path, - 'data': data, - 'options': {'merge': merge}, - }, - ); + return _delegate.setData(_CodecUtility.replaceValueWithDelegatesInMap(data), + merge: merge); } /// Updates fields in the document referred to by this [DocumentReference]. @@ -66,45 +56,21 @@ class DocumentReference { /// /// If no document exists yet, the update will fail. Future updateData(Map data) { - return Firestore.channel.invokeMethod( - 'DocumentReference#updateData', - { - 'app': firestore.app.name, - 'path': path, - 'data': data, - }, - ); + return _delegate + .updateData(_CodecUtility.replaceValueWithDelegatesInMap(data)); } /// Reads the document referenced by this [DocumentReference]. /// /// If no document exists, the read will return null. - Future get({Source source = Source.serverAndCache}) async { - final Map data = - await Firestore.channel.invokeMapMethod( - 'DocumentReference#get', - { - 'app': firestore.app.name, - 'path': path, - 'source': _getSourceString(source), - }, - ); - return DocumentSnapshot._( - data['path'], - _asStringKeyedMap(data['data']), - SnapshotMetadata._(data['metadata']['hasPendingWrites'], - data['metadata']['isFromCache']), - firestore, - ); + Future get({ + platform.Source source = platform.Source.serverAndCache, + }) async { + return DocumentSnapshot._(await _delegate.get(source: source), firestore); } /// Deletes the document referred to by this [DocumentReference]. - Future delete() { - return Firestore.channel.invokeMethod( - 'DocumentReference#delete', - {'app': firestore.app.name, 'path': path}, - ); - } + Future delete() => _delegate.delete(); /// Returns the reference of a collection contained inside of this /// document. @@ -115,37 +81,8 @@ class DocumentReference { } /// Notifies of documents at this location - // TODO(jackson): Reduce code duplication with [Query] - Stream snapshots({bool includeMetadataChanges = false}) { - assert(includeMetadataChanges != null); - Future _handle; - // It's fine to let the StreamController be garbage collected once all the - // subscribers have cancelled; this analyzer warning is safe to ignore. - StreamController controller; // ignore: close_sinks - controller = StreamController.broadcast( - onListen: () { - _handle = Firestore.channel.invokeMethod( - 'DocumentReference#addSnapshotListener', - { - 'app': firestore.app.name, - 'path': path, - 'includeMetadataChanges': includeMetadataChanges, - }, - ).then((dynamic result) => result); - _handle.then((int handle) { - Firestore._documentObservers[handle] = controller; - }); - }, - onCancel: () { - _handle.then((int handle) async { - await Firestore.channel.invokeMethod( - 'removeListener', - {'handle': handle}, - ); - Firestore._documentObservers.remove(handle); - }); - }, - ); - return controller.stream; - } + Stream snapshots({bool includeMetadataChanges = false}) => + _delegate + .snapshots(includeMetadataChanges: includeMetadataChanges) + .map((snapshot) => DocumentSnapshot._(snapshot, firestore)); } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/document_snapshot.dart b/packages/cloud_firestore/cloud_firestore/lib/src/document_snapshot.dart index 45508566674a..220efff3960b 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/document_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/document_snapshot.dart @@ -10,36 +10,29 @@ part of cloud_firestore; /// The data can be extracted with the data property or by using subscript /// syntax to access a specific field. class DocumentSnapshot { - DocumentSnapshot._(this._path, this.data, this.metadata, this._firestore); - - final String _path; + platform.DocumentSnapshotPlatform _delegate; final Firestore _firestore; + DocumentSnapshot._(this._delegate, this._firestore); + /// The reference that produced this snapshot - DocumentReference get reference => _firestore.document(_path); + DocumentReference get reference => + _firestore.document(_delegate.reference.path); /// Contains all the data of this snapshot - final Map data; + Map get data => + _CodecUtility.replaceDelegatesWithValueInMap(_delegate.data, _firestore); /// Metadata about this snapshot concerning its source and if it has local /// modifications. - final SnapshotMetadata metadata; + SnapshotMetadata get metadata => SnapshotMetadata._(_delegate.metadata); /// Reads individual values from the snapshot dynamic operator [](String key) => data[key]; /// Returns the ID of the snapshot's document - String get documentID => _path.split('/').last; + String get documentID => _delegate.documentID; /// Returns `true` if the document exists. bool get exists => data != null; } - -Map _asStringKeyedMap(Map map) { - if (map == null) return null; - if (map is Map) { - return map; - } else { - return Map.from(map); - } -} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/field_path.dart b/packages/cloud_firestore/cloud_firestore/lib/src/field_path.dart deleted file mode 100644 index 64cdde8fad31..000000000000 --- a/packages/cloud_firestore/cloud_firestore/lib/src/field_path.dart +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017, 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; - -enum _FieldPathType { - documentId, -} - -/// A [FieldPath] refers to a field in a document. -class FieldPath { - const FieldPath._(this.type); - - @visibleForTesting - final _FieldPathType type; - - /// The path to the document id, which can be used in queries. - static FieldPath get documentId => - const FieldPath._(_FieldPathType.documentId); -} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/field_value.dart b/packages/cloud_firestore/cloud_firestore/lib/src/field_value.dart index 4ab12b0411f3..e278ef651d50 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/field_value.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/field_value.dart @@ -4,26 +4,19 @@ part of cloud_firestore; -@visibleForTesting -enum FieldValueType { - arrayUnion, - arrayRemove, - delete, - serverTimestamp, - incrementDouble, - incrementInteger, -} - /// Sentinel values that can be used when writing document fields with set() or /// update(). -class FieldValue { - FieldValue._(this.type, this.value); - - @visibleForTesting - final FieldValueType type; - - @visibleForTesting - final dynamic value; +/// +/// This class serves as a static factory for [FieldValuePlatform] instances, but also +/// as a facade for the [FieldValue] type, so plugin users don't need to worry about +/// the actual internal implementation of their [FieldValue]s after they're created. +class FieldValue extends platform.FieldValuePlatform { + static final platform.FieldValueFactoryPlatform _factory = + platform.FieldValueFactoryPlatform.instance; + + FieldValue._(platform.FieldValuePlatform delegate) : super(delegate) { + platform.FieldValuePlatform.verifyExtends(delegate); + } /// Returns a special value that tells the server to union the given elements /// with any array value that already exists on the server. @@ -33,7 +26,7 @@ class FieldValue { /// will be overwritten with an array containing exactly the specified /// elements. static FieldValue arrayUnion(List elements) => - FieldValue._(FieldValueType.arrayUnion, elements); + FieldValue._(_factory.arrayUnion(elements)); /// Returns a special value that tells the server to remove the given /// elements from any array value that already exists on the server. @@ -42,27 +35,18 @@ class FieldValue { /// If the field being modified is not already an array it will be overwritten /// with an empty array. static FieldValue arrayRemove(List elements) => - FieldValue._(FieldValueType.arrayRemove, elements); + FieldValue._(_factory.arrayRemove(elements)); /// Returns a sentinel for use with update() to mark a field for deletion. - static FieldValue delete() => FieldValue._(FieldValueType.delete, null); + static FieldValue delete() => FieldValue._(_factory.delete()); /// Returns a sentinel for use with set() or update() to include a /// server-generated timestamp in the written data. static FieldValue serverTimestamp() => - FieldValue._(FieldValueType.serverTimestamp, null); + FieldValue._(_factory.serverTimestamp()); /// Returns a special value for use with set() or update() that tells the /// server to increment the field’s current value by the given value. - static FieldValue increment(num value) { - // It is a compile-time error for any type other than int or double to - // attempt to extend or implement num. - assert(value is int || value is double); - if (value is double) { - return FieldValue._(FieldValueType.incrementDouble, value); - } else if (value is int) { - return FieldValue._(FieldValueType.incrementInteger, value); - } - return null; - } + static FieldValue increment(num value) => + FieldValue._(_factory.increment(value)); } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart index 5c164ed1e122..9a85ad6fe17a 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart @@ -8,94 +8,55 @@ part of cloud_firestore; /// /// You can get an instance by calling [Firestore.instance]. class Firestore { - Firestore({FirebaseApp app}) : app = app ?? FirebaseApp.instance { - if (_initialized) return; - channel.setMethodCallHandler((MethodCall call) async { - if (call.method == 'QuerySnapshot') { - final QuerySnapshot snapshot = QuerySnapshot._(call.arguments, this); - _queryObservers[call.arguments['handle']].add(snapshot); - } else if (call.method == 'DocumentSnapshot') { - final DocumentSnapshot snapshot = DocumentSnapshot._( - call.arguments['path'], - _asStringKeyedMap(call.arguments['data']), - SnapshotMetadata._(call.arguments['metadata']['hasPendingWrites'], - call.arguments['metadata']['isFromCache']), - this, - ); - _documentObservers[call.arguments['handle']].add(snapshot); - } else if (call.method == 'DoTransaction') { - final int transactionId = call.arguments['transactionId']; - final Transaction transaction = Transaction(transactionId, this); - final dynamic result = - await _transactionHandlers[transactionId](transaction); - await transaction._finish(); - return result; - } - }); - _initialized = true; + // Cached and lazily loaded instance of [FirestorePlatform] to avoid + // creating a [MethodChannelFirestore] when not needed or creating an + // instance with the default app before a user specifies an app. + platform.FirestorePlatform _delegatePackingProperty; + + platform.FirestorePlatform get _delegate { + if (_delegatePackingProperty == null) { + _delegatePackingProperty = platform.FirestorePlatform.instance; + } + return _delegatePackingProperty; } + Firestore({FirebaseApp app}) + : _delegatePackingProperty = app != null + ? platform.FirestorePlatform.instanceFor(app: app) + : platform.FirestorePlatform.instance; + /// Gets the instance of Firestore for the default Firebase app. - static final Firestore instance = Firestore(); + static Firestore get instance => Firestore(); - /// The [FirebaseApp] instance to which this [FirebaseDatabase] belongs. + /// The [FirebaseApp] instance to which this [Firestore] belongs. /// /// If null, the default [FirebaseApp] is used. - final FirebaseApp app; - - static bool _initialized = false; - - @visibleForTesting - static const MethodChannel channel = MethodChannel( - 'plugins.flutter.io/cloud_firestore', - StandardMethodCodec(FirestoreMessageCodec()), - ); - - static final Map> _queryObservers = - >{}; - - static final Map> _documentObservers = - >{}; - - static final Map _transactionHandlers = - {}; - static int _transactionHandlerId = 0; + FirebaseApp get app => _delegate.app; - @override - bool operator ==(dynamic o) => o is Firestore && o.app == app; - - @override - int get hashCode => app.hashCode; + /// Creates a write batch, used for performing multiple writes as a single + /// atomic operation. + /// + /// Unlike transactions, write batches are persisted offline and therefore are + /// preferable when you don’t need to condition your writes on read data. + WriteBatch batch() => WriteBatch._(_delegate.batch()); /// Gets a [CollectionReference] for the specified Firestore path. CollectionReference collection(String path) { assert(path != null); - return CollectionReference._(this, path.split('/')); + return CollectionReference._(_delegate.collection(path), this); } /// 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('/'), - ); - } + Query collectionGroup(String path) => + Query._(_delegate.collectionGroup(path), this); /// Gets a [DocumentReference] for the specified Firestore path. - DocumentReference document(String path) { - assert(path != null); - return DocumentReference._(this, path.split('/')); - } + DocumentReference document(String path) => + DocumentReference._(_delegate.document(path), this); - /// Creates a write batch, used for performing multiple writes as a single - /// atomic operation. - /// - /// Unlike transactions, write batches are persisted offline and therefore are - /// preferable when you don’t need to condition your writes on read data. - WriteBatch batch() => WriteBatch._(this); + @Deprecated('Use the persistenceEnabled parameter of the [settings] method') + Future enablePersistence(bool enable) => + _delegate.enablePersistence(enable); /// Executes the given TransactionHandler and then attempts to commit the /// changes applied within an atomic transaction. @@ -120,42 +81,21 @@ class Firestore { /// timeout can be adjusted by setting the timeout parameter. Future> runTransaction( TransactionHandler transactionHandler, - {Duration timeout = const Duration(seconds: 5)}) async { - assert(timeout.inMilliseconds > 0, - 'Transaction timeout must be more than 0 milliseconds'); - final int transactionId = _transactionHandlerId++; - _transactionHandlers[transactionId] = transactionHandler; - final Map result = await channel - .invokeMapMethod( - 'Firestore#runTransaction', { - 'app': app.name, - 'transactionId': transactionId, - 'transactionTimeout': timeout.inMilliseconds - }); - return result ?? {}; - } - - @deprecated - Future enablePersistence(bool enable) async { - assert(enable != null); - await channel - .invokeMethod('Firestore#enablePersistence', { - 'app': app.name, - 'enable': enable, - }); + {Duration timeout = const Duration(seconds: 5)}) { + return _delegate.runTransaction( + (platformTransaction) => + transactionHandler(Transaction._(platformTransaction, this)), + timeout: timeout); } Future settings( - {bool persistenceEnabled, - String host, - bool sslEnabled, - int cacheSizeBytes}) async { - await channel.invokeMethod('Firestore#settings', { - 'app': app.name, - 'persistenceEnabled': persistenceEnabled, - 'host': host, - 'sslEnabled': sslEnabled, - 'cacheSizeBytes': cacheSizeBytes, - }); - } + {bool persistenceEnabled, + String host, + bool sslEnabled, + int cacheSizeBytes}) => + _delegate.settings( + persistenceEnabled: persistenceEnabled, + host: host, + sslEnabled: sslEnabled, + cacheSizeBytes: cacheSizeBytes); } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/firestore_message_codec.dart b/packages/cloud_firestore/cloud_firestore/lib/src/firestore_message_codec.dart deleted file mode 100644 index 9f658cd1c84b..000000000000 --- a/packages/cloud_firestore/cloud_firestore/lib/src/firestore_message_codec.dart +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2017, 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; - -@visibleForTesting -class FirestoreMessageCodec extends StandardMessageCodec { - const FirestoreMessageCodec(); - - static const int _kDateTime = 128; - static const int _kGeoPoint = 129; - static const int _kDocumentReference = 130; - static const int _kBlob = 131; - static const int _kArrayUnion = 132; - static const int _kArrayRemove = 133; - static const int _kDelete = 134; - static const int _kServerTimestamp = 135; - static const int _kTimestamp = 136; - static const int _kIncrementDouble = 137; - static const int _kIncrementInteger = 138; - static const int _kDocumentId = 139; - - static const Map _kFieldValueCodes = - { - FieldValueType.arrayUnion: _kArrayUnion, - FieldValueType.arrayRemove: _kArrayRemove, - FieldValueType.delete: _kDelete, - FieldValueType.serverTimestamp: _kServerTimestamp, - FieldValueType.incrementDouble: _kIncrementDouble, - FieldValueType.incrementInteger: _kIncrementInteger, - }; - - static const Map<_FieldPathType, int> _kFieldPathCodes = - <_FieldPathType, int>{ - _FieldPathType.documentId: _kDocumentId, - }; - - @override - void writeValue(WriteBuffer buffer, dynamic value) { - if (value is DateTime) { - buffer.putUint8(_kDateTime); - buffer.putInt64(value.millisecondsSinceEpoch); - } else if (value is Timestamp) { - buffer.putUint8(_kTimestamp); - buffer.putInt64(value.seconds); - buffer.putInt32(value.nanoseconds); - } else if (value is GeoPoint) { - buffer.putUint8(_kGeoPoint); - buffer.putFloat64(value.latitude); - buffer.putFloat64(value.longitude); - } else if (value is DocumentReference) { - buffer.putUint8(_kDocumentReference); - final List appName = utf8.encoder.convert(value.firestore.app.name); - writeSize(buffer, appName.length); - buffer.putUint8List(appName); - final List bytes = utf8.encoder.convert(value.path); - writeSize(buffer, bytes.length); - buffer.putUint8List(bytes); - } else if (value is Blob) { - buffer.putUint8(_kBlob); - writeSize(buffer, value.bytes.length); - buffer.putUint8List(value.bytes); - } else if (value is FieldValue) { - final int code = _kFieldValueCodes[value.type]; - assert(code != null); - buffer.putUint8(code); - if (value.value != null) writeValue(buffer, value.value); - } else if (value is FieldPath) { - final int code = _kFieldPathCodes[value.type]; - assert(code != null); - buffer.putUint8(code); - } else { - super.writeValue(buffer, value); - } - } - - @override - dynamic readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case _kDateTime: - return DateTime.fromMillisecondsSinceEpoch(buffer.getInt64()); - case _kTimestamp: - return Timestamp(buffer.getInt64(), buffer.getInt32()); - case _kGeoPoint: - return GeoPoint(buffer.getFloat64(), buffer.getFloat64()); - case _kDocumentReference: - final int appNameLength = readSize(buffer); - final String appName = - utf8.decoder.convert(buffer.getUint8List(appNameLength)); - final FirebaseApp app = FirebaseApp(name: appName); - final Firestore firestore = Firestore(app: app); - final int pathLength = readSize(buffer); - final String path = - utf8.decoder.convert(buffer.getUint8List(pathLength)); - return firestore.document(path); - case _kBlob: - final int length = readSize(buffer); - final List bytes = buffer.getUint8List(length); - return Blob(bytes); - case _kArrayUnion: - final List value = readValue(buffer); - return FieldValue.arrayUnion(value); - case _kArrayRemove: - final List value = readValue(buffer); - return FieldValue.arrayRemove(value); - case _kDelete: - return FieldValue.delete(); - case _kServerTimestamp: - return FieldValue.serverTimestamp(); - case _kIncrementDouble: - final double value = readValue(buffer); - return FieldValue.increment(value); - case _kIncrementInteger: - final int value = readValue(buffer); - return FieldValue.increment(value); - case _kDocumentId: - return FieldPath.documentId; - default: - return super.readValueOfType(type, buffer); - } - } -} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/geo_point.dart b/packages/cloud_firestore/cloud_firestore/lib/src/geo_point.dart deleted file mode 100644 index bc91d4d16712..000000000000 --- a/packages/cloud_firestore/cloud_firestore/lib/src/geo_point.dart +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2017, 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; - -class GeoPoint { - const GeoPoint(this.latitude, this.longitude); - - final double latitude; - final double longitude; - - @override - bool operator ==(dynamic o) => - o is GeoPoint && o.latitude == latitude && o.longitude == longitude; - - @override - int get hashCode => hashValues(latitude, longitude); -} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/query.dart b/packages/cloud_firestore/cloud_firestore/lib/src/query.dart index 2da85859ee5a..513cfce5b554 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/query.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/query.dart @@ -6,106 +6,41 @@ part of cloud_firestore; /// Represents a query over the data at a particular location. 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(>[]), - 'orderBy': List>.unmodifiable(>[]), - }), - assert(firestore != null), - assert(pathComponents != null); + final platform.QueryPlatform _delegate; /// The Firestore instance associated with this query final Firestore firestore; - final List _pathComponents; - final Map _parameters; - final bool _isCollectionGroup; + Query._(this._delegate, this.firestore) { + platform.QueryPlatform.verifyExtends(_delegate); + } - String get _path => _pathComponents.join('/'); + List get _pathComponents => _delegate.pathComponents; - Query _copyWithParameters(Map parameters) { - return Query._( - firestore: firestore, - isCollectionGroup: _isCollectionGroup, - pathComponents: _pathComponents, - parameters: Map.unmodifiable( - Map.from(_parameters)..addAll(parameters), - ), - ); - } + String get _path => _pathComponents.join('/'); Map buildArguments() { - return Map.from(_parameters) - ..addAll({ - 'path': _path, - }); + return _delegate.buildArguments(); } /// Notifies of query results at this location - // TODO(jackson): Reduce code duplication with [DocumentReference] - Stream snapshots({bool includeMetadataChanges = false}) { - assert(includeMetadataChanges != null); - Future _handle; - // It's fine to let the StreamController be garbage collected once all the - // subscribers have cancelled; this analyzer warning is safe to ignore. - StreamController controller; // ignore: close_sinks - controller = StreamController.broadcast( - onListen: () { - _handle = Firestore.channel.invokeMethod( - 'Query#addSnapshotListener', - { - 'app': firestore.app.name, - 'path': _path, - 'isCollectionGroup': _isCollectionGroup, - 'parameters': _parameters, - 'includeMetadataChanges': includeMetadataChanges, - }, - ).then((dynamic result) => result); - _handle.then((int handle) { - Firestore._queryObservers[handle] = controller; - }); - }, - onCancel: () { - _handle.then((int handle) async { - await Firestore.channel.invokeMethod( - 'removeListener', - {'handle': handle}, - ); - Firestore._queryObservers.remove(handle); - }); - }, - ); - return controller.stream; - } + Stream snapshots({bool includeMetadataChanges = false}) => + _delegate + .snapshots(includeMetadataChanges: includeMetadataChanges) + .map((item) => QuerySnapshot._(item, firestore)); /// Fetch the documents for this query - Future getDocuments( - {Source source = Source.serverAndCache}) async { + Future getDocuments({ + platform.Source source = platform.Source.serverAndCache, + }) async { assert(source != null); - final Map data = - await Firestore.channel.invokeMapMethod( - 'Query#getDocuments', - { - 'app': firestore.app.name, - 'path': _path, - 'isCollectionGroup': _isCollectionGroup, - 'parameters': _parameters, - 'source': _getSourceString(source), - }, - ); - return QuerySnapshot._(data, firestore); + final docs = await _delegate.getDocuments(source: source); + return QuerySnapshot._(docs, firestore); } /// Obtains a CollectionReference corresponding to this query's location. CollectionReference reference() => - CollectionReference._(firestore, _pathComponents); + CollectionReference._(_delegate.reference(), firestore); /// Creates and returns a new [Query] with additional filter on specified /// [field]. [field] refers to a field in a document. @@ -129,46 +64,19 @@ class Query { List arrayContainsAny, List whereIn, bool isNull, - }) { - assert(field is String || field is FieldPath, - 'Supported [field] types are [String] and [FieldPath].'); - - final ListEquality equality = const ListEquality(); - final List> conditions = - List>.from(_parameters['where']); - - void addCondition(dynamic field, String operator, dynamic value) { - final List condition = [field, operator, value]; - assert( - conditions - .where((List item) => equality.equals(condition, item)) - .isEmpty, - 'Condition $condition already exists in this query.'); - conditions.add(condition); - } - - if (isEqualTo != null) addCondition(field, '==', isEqualTo); - if (isLessThan != null) addCondition(field, '<', isLessThan); - if (isLessThanOrEqualTo != null) - addCondition(field, '<=', isLessThanOrEqualTo); - if (isGreaterThan != null) addCondition(field, '>', isGreaterThan); - if (isGreaterThanOrEqualTo != null) - addCondition(field, '>=', isGreaterThanOrEqualTo); - if (arrayContains != null) - addCondition(field, 'array-contains', arrayContains); - if (arrayContainsAny != null) - addCondition(field, 'array-contains-any', arrayContainsAny); - if (whereIn != null) addCondition(field, 'in', whereIn); - if (isNull != null) { - assert( - isNull, - 'isNull can only be set to true. ' - 'Use isEqualTo to filter on non-null values.'); - addCondition(field, '==', null); - } - - return _copyWithParameters({'where': conditions}); - } + }) => + Query._( + _delegate.where(field, + isEqualTo: isEqualTo, + isLessThan: isLessThan, + isLessThanOrEqualTo: isLessThanOrEqualTo, + isGreaterThan: isGreaterThan, + isGreaterThanOrEqualTo: isGreaterThanOrEqualTo, + arrayContainsAny: arrayContainsAny, + arrayContains: arrayContains, + whereIn: whereIn, + isNull: isNull), + firestore); /// Creates and returns a new [Query] that's additionally sorted by the specified /// [field]. @@ -180,33 +88,8 @@ class Query { /// using [startAfterDocument], [startAtDocument], [endAfterDocument], /// or [endAtDocument] because the order by clause on the document id /// is added by these methods implicitly. - Query orderBy(dynamic field, {bool descending = false}) { - assert(field != null && descending != null); - assert(field is String || field is FieldPath, - 'Supported [field] types are [String] and [FieldPath].'); - - final List> orders = - List>.from(_parameters['orderBy']); - - final List order = [field, descending]; - assert(orders.where((List item) => field == item[0]).isEmpty, - 'OrderBy $field already exists in this query'); - - assert(() { - if (field == FieldPath.documentId) { - return !(_parameters.containsKey('startAfterDocument') || - _parameters.containsKey('startAtDocument') || - _parameters.containsKey('endAfterDocument') || - _parameters.containsKey('endAtDocument')); - } - return true; - }(), - '{start/end}{At/After/Before}Document order by document id themselves. ' - 'Hence, you may not use an order by [FieldPath.documentId] when using any of these methods for a query.'); - - orders.add(order); - return _copyWithParameters({'orderBy': orders}); - } + Query orderBy(dynamic field, {bool descending = false}) => + Query._(_delegate.orderBy(field, descending: descending), firestore); /// Creates and returns a new [Query] that starts after the provided document /// (exclusive). The starting position is relative to the order of the query. @@ -222,26 +105,10 @@ class Query { /// * [endAfterDocument] for a query that ends after a document. /// * [startAtDocument] for a query that starts at a document. /// * [endAtDocument] for a query that ends at a document. - Query startAfterDocument(DocumentSnapshot documentSnapshot) { - assert(documentSnapshot != null); - assert(!_parameters.containsKey('startAfter')); - assert(!_parameters.containsKey('startAt')); - assert(!_parameters.containsKey('startAfterDocument')); - assert(!_parameters.containsKey('startAtDocument')); - assert( - List>.from(_parameters['orderBy']) - .where((List item) => item[0] == FieldPath.documentId) - .isEmpty, - '[startAfterDocument] orders by document id itself. ' - 'Hence, you may not use an order by [FieldPath.documentId] when using [startAfterDocument].'); - return _copyWithParameters({ - 'startAfterDocument': { - 'id': documentSnapshot.documentID, - 'path': documentSnapshot.reference.path, - 'data': documentSnapshot.data - } - }); - } + Query startAfterDocument(DocumentSnapshot documentSnapshot) => Query._( + _delegate.startAfterDocument( + _PlatformUtils.toPlatformDocumentSnapshot(documentSnapshot)), + firestore); /// Creates and returns a new [Query] that starts at the provided document /// (inclusive). The starting position is relative to the order of the query. @@ -257,26 +124,10 @@ class Query { /// * [startAfterDocument] for a query that starts after a document. /// * [endAtDocument] for a query that ends at a document. /// * [endBeforeDocument] for a query that ends before a document. - Query startAtDocument(DocumentSnapshot documentSnapshot) { - assert(documentSnapshot != null); - assert(!_parameters.containsKey('startAfter')); - assert(!_parameters.containsKey('startAt')); - assert(!_parameters.containsKey('startAfterDocument')); - assert(!_parameters.containsKey('startAtDocument')); - assert( - List>.from(_parameters['orderBy']) - .where((List item) => item[0] == FieldPath.documentId) - .isEmpty, - '[startAtDocument] orders by document id itself. ' - 'Hence, you may not use an order by [FieldPath.documentId] when using [startAtDocument].'); - return _copyWithParameters({ - 'startAtDocument': { - 'id': documentSnapshot.documentID, - 'path': documentSnapshot.reference.path, - 'data': documentSnapshot.data - }, - }); - } + Query startAtDocument(DocumentSnapshot documentSnapshot) => Query._( + _delegate.startAtDocument( + _PlatformUtils.toPlatformDocumentSnapshot(documentSnapshot)), + firestore); /// Takes a list of [values], creates and returns a new [Query] that starts /// after the provided fields relative to the order of the query. @@ -286,14 +137,8 @@ class Query { /// Cannot be used in combination with [startAt], [startAfterDocument], or /// [startAtDocument], but can be used in combination with [endAt], /// [endBefore], [endAtDocument] and [endBeforeDocument]. - Query startAfter(List values) { - assert(values != null); - assert(!_parameters.containsKey('startAfter')); - assert(!_parameters.containsKey('startAt')); - assert(!_parameters.containsKey('startAfterDocument')); - assert(!_parameters.containsKey('startAtDocument')); - return _copyWithParameters({'startAfter': values}); - } + Query startAfter(List values) => + Query._(_delegate.startAfter(values), firestore); /// Takes a list of [values], creates and returns a new [Query] that starts at /// the provided fields relative to the order of the query. @@ -303,14 +148,8 @@ class Query { /// Cannot be used in combination with [startAfter], [startAfterDocument], /// or [startAtDocument], but can be used in combination with [endAt], /// [endBefore], [endAtDocument] and [endBeforeDocument]. - Query startAt(List values) { - assert(values != null); - assert(!_parameters.containsKey('startAfter')); - assert(!_parameters.containsKey('startAt')); - assert(!_parameters.containsKey('startAfterDocument')); - assert(!_parameters.containsKey('startAtDocument')); - return _copyWithParameters({'startAt': values}); - } + Query startAt(List values) => + Query._(_delegate.startAt(values), firestore); /// Creates and returns a new [Query] that ends at the provided document /// (inclusive). The end position is relative to the order of the query. @@ -326,26 +165,10 @@ class Query { /// * [startAfterDocument] for a query that starts after a document. /// * [startAtDocument] for a query that starts at a document. /// * [endBeforeDocument] for a query that ends before a document. - Query endAtDocument(DocumentSnapshot documentSnapshot) { - assert(documentSnapshot != null); - assert(!_parameters.containsKey('endBefore')); - assert(!_parameters.containsKey('endAt')); - assert(!_parameters.containsKey('endBeforeDocument')); - assert(!_parameters.containsKey('endAtDocument')); - assert( - List>.from(_parameters['orderBy']) - .where((List item) => item[0] == FieldPath.documentId) - .isEmpty, - '[endAtDocument] orders by document id itself. ' - 'Hence, you may not use an order by [FieldPath.documentId] when using [endAtDocument].'); - return _copyWithParameters({ - 'endAtDocument': { - 'id': documentSnapshot.documentID, - 'path': documentSnapshot.reference.path, - 'data': documentSnapshot.data - }, - }); - } + Query endAtDocument(DocumentSnapshot documentSnapshot) => Query._( + _delegate.endAtDocument( + _PlatformUtils.toPlatformDocumentSnapshot(documentSnapshot)), + firestore); /// Takes a list of [values], creates and returns a new [Query] that ends at the /// provided fields relative to the order of the query. @@ -355,14 +178,8 @@ class Query { /// Cannot be used in combination with [endBefore], [endBeforeDocument], or /// [endAtDocument], but can be used in combination with [startAt], /// [startAfter], [startAtDocument] and [startAfterDocument]. - Query endAt(List values) { - assert(values != null); - assert(!_parameters.containsKey('endBefore')); - assert(!_parameters.containsKey('endAt')); - assert(!_parameters.containsKey('endBeforeDocument')); - assert(!_parameters.containsKey('endAtDocument')); - return _copyWithParameters({'endAt': values}); - } + Query endAt(List values) => + Query._(_delegate.endAt(values), firestore); /// Creates and returns a new [Query] that ends before the provided document /// (exclusive). The end position is relative to the order of the query. @@ -378,26 +195,10 @@ class Query { /// * [startAfterDocument] for a query that starts after document. /// * [startAtDocument] for a query that starts at a document. /// * [endAtDocument] for a query that ends at a document. - Query endBeforeDocument(DocumentSnapshot documentSnapshot) { - assert(documentSnapshot != null); - assert(!_parameters.containsKey('endBefore')); - assert(!_parameters.containsKey('endAt')); - assert(!_parameters.containsKey('endBeforeDocument')); - assert(!_parameters.containsKey('endAtDocument')); - assert( - List>.from(_parameters['orderBy']) - .where((List item) => item[0] == FieldPath.documentId) - .isEmpty, - '[endBeforeDocument] orders by document id itself. ' - 'Hence, you may not use an order by [FieldPath.documentId] when using [endBeforeDocument].'); - return _copyWithParameters({ - 'endBeforeDocument': { - 'id': documentSnapshot.documentID, - 'path': documentSnapshot.reference.path, - 'data': documentSnapshot.data, - }, - }); - } + Query endBeforeDocument(DocumentSnapshot documentSnapshot) => Query._( + _delegate.endBeforeDocument( + _PlatformUtils.toPlatformDocumentSnapshot(documentSnapshot)), + firestore); /// Takes a list of [values], creates and returns a new [Query] that ends before /// the provided fields relative to the order of the query. @@ -407,19 +208,10 @@ class Query { /// Cannot be used in combination with [endAt], [endBeforeDocument], or /// [endBeforeDocument], but can be used in combination with [startAt], /// [startAfter], [startAtDocument] and [startAfterDocument]. - Query endBefore(List values) { - assert(values != null); - assert(!_parameters.containsKey('endBefore')); - assert(!_parameters.containsKey('endAt')); - assert(!_parameters.containsKey('endBeforeDocument')); - assert(!_parameters.containsKey('endAtDocument')); - return _copyWithParameters({'endBefore': values}); - } + Query endBefore(List values) => + Query._(_delegate.endBefore(values), firestore); /// Creates and returns a new Query that's additionally limited to only return up /// to the specified number of documents. - Query limit(int length) { - assert(!_parameters.containsKey('limit')); - return _copyWithParameters({'limit': length}); - } + Query limit(int length) => Query._(_delegate.limit(length), firestore); } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/query_snapshot.dart b/packages/cloud_firestore/cloud_firestore/lib/src/query_snapshot.dart index 9d6a53fb6f2d..51bde9c9cc6d 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/query_snapshot.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/query_snapshot.dart @@ -6,37 +6,23 @@ part of cloud_firestore; /// A QuerySnapshot contains zero or more DocumentSnapshot objects. class QuerySnapshot { - QuerySnapshot._(Map data, Firestore firestore) - : documents = List.generate(data['documents'].length, - (int index) { - return DocumentSnapshot._( - data['paths'][index], - _asStringKeyedMap(data['documents'][index]), - SnapshotMetadata._( - data['metadatas'][index]['hasPendingWrites'], - data['metadatas'][index]['isFromCache'], - ), - firestore, - ); - }), - documentChanges = List.generate( - data['documentChanges'].length, (int index) { - return DocumentChange._( - data['documentChanges'][index], - firestore, - ); - }), - metadata = SnapshotMetadata._( - data['metadata']['hasPendingWrites'], - data['metadata']['isFromCache'], - ); + final platform.QuerySnapshotPlatform _delegate; + final Firestore _firestore; + + QuerySnapshot._(this._delegate, this._firestore) { + platform.QuerySnapshotPlatform.verifyExtends(_delegate); + } /// Gets a list of all the documents included in this snapshot - final List documents; + List get documents => _delegate.documents + .map((item) => DocumentSnapshot._(item, _firestore)) + .toList(); /// An array of the documents that changed since the last snapshot. If this /// is the first snapshot, all documents will be in the list as Added changes. - final List documentChanges; + List get documentChanges => _delegate.documentChanges + .map((item) => DocumentChange._(item, _firestore)) + .toList(); - final SnapshotMetadata metadata; + SnapshotMetadata get metadata => SnapshotMetadata._(_delegate.metadata); } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/snapshot_metadata.dart b/packages/cloud_firestore/cloud_firestore/lib/src/snapshot_metadata.dart index 4fbae75ac84c..b1e8b0c83705 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/snapshot_metadata.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/snapshot_metadata.dart @@ -6,7 +6,8 @@ part of cloud_firestore; /// Metadata about a snapshot, describing the state of the snapshot. class SnapshotMetadata { - SnapshotMetadata._(this.hasPendingWrites, this.isFromCache); + platform.SnapshotMetadataPlatform _delegate; + SnapshotMetadata._(this._delegate); /// Whether the snapshot contains the result of local writes that have not yet /// been committed to the backend. @@ -15,7 +16,7 @@ class SnapshotMetadata { /// `includeMetadataChanges` parameter set to `true` you will receive another /// snapshot with `hasPendingWrites` equal to `false` once the writes have been /// committed to the backend. - final bool hasPendingWrites; + bool get hasPendingWrites => _delegate.hasPendingWrites; /// Whether the snapshot was created from cached data rather than guaranteed /// up-to-date server data. @@ -24,5 +25,5 @@ class SnapshotMetadata { /// `includeMetadataChanges` parameter set to `true` you will receive another /// snapshot with `isFomCache` equal to `false` once the client has received /// up-to-date data from the backend. - final bool isFromCache; + bool get isFromCache => _delegate.isFromCache; } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/source.dart b/packages/cloud_firestore/cloud_firestore/lib/src/source.dart deleted file mode 100644 index 0a0a43eef8f3..000000000000 --- a/packages/cloud_firestore/cloud_firestore/lib/src/source.dart +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2017, 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; - -/// An enumeration of firestore source types. -enum Source { - /// Causes Firestore to try to retrieve an up-to-date (server-retrieved) snapshot, but fall back to - /// returning cached data if the server can't be reached. - serverAndCache, - - /// Causes Firestore to avoid the cache, generating an error if the server cannot be reached. Note - /// that the cache will still be updated if the server request succeeds. Also note that - /// latency-compensation still takes effect, so any pending write operations will be visible in the - /// returned data (merged into the server-provided data). - server, - - /// Causes Firestore to immediately return a value from the cache, ignoring the server completely - /// (implying that the returned value may be stale with respect to the value on the server). If - /// there is no data in the cache to satisfy the [get()] or [getDocuments()] call, - /// [DocumentReference.get()] will return an error and [Query.getDocuments()] will return an empty - /// [QuerySnapshot] with no documents. - cache, -} - -/// Converts [Source] to [String] -String _getSourceString(Source source) { - assert(source != null); - if (source == Source.server) { - return 'server'; - } - if (source == Source.cache) { - return 'cache'; - } - return 'default'; -} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/timestamp.dart b/packages/cloud_firestore/cloud_firestore/lib/src/timestamp.dart deleted file mode 100644 index 027f5a55ed76..000000000000 --- a/packages/cloud_firestore/cloud_firestore/lib/src/timestamp.dart +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2018, 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; - -const int _kThousand = 1000; -const int _kMillion = 1000000; -const int _kBillion = 1000000000; - -void _check(bool expr, String name, int value) { - if (!expr) { - throw ArgumentError("Timestamp $name out of range: $value"); - } -} - -/// A Timestamp represents a point in time independent of any time zone or calendar, -/// represented as seconds and fractions of seconds at nanosecond resolution in UTC -/// Epoch time. It is encoded using the Proleptic Gregorian Calendar which extends -/// the Gregorian calendar backwards to year one. It is encoded assuming all minutes -/// are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second table -/// is needed for interpretation. Range is from 0001-01-01T00:00:00Z to -/// 9999-12-31T23:59:59.999999999Z. By restricting to that range, we ensure that we -/// can convert to and from RFC 3339 date strings. -/// -/// For more information, see [the reference timestamp definition](https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto) -class Timestamp implements Comparable { - Timestamp(this._seconds, this._nanoseconds) { - _validateRange(_seconds, _nanoseconds); - } - - factory Timestamp.fromMillisecondsSinceEpoch(int milliseconds) { - final int seconds = (milliseconds / _kThousand).floor(); - final int nanoseconds = (milliseconds - seconds * _kThousand) * _kMillion; - return Timestamp(seconds, nanoseconds); - } - - factory Timestamp.fromMicrosecondsSinceEpoch(int microseconds) { - final int seconds = (microseconds / _kMillion).floor(); - final int nanoseconds = (microseconds - seconds * _kMillion) * _kThousand; - return Timestamp(seconds, nanoseconds); - } - - factory Timestamp.fromDate(DateTime date) { - return Timestamp.fromMicrosecondsSinceEpoch(date.microsecondsSinceEpoch); - } - - factory Timestamp.now() { - return Timestamp.fromMicrosecondsSinceEpoch( - DateTime.now().microsecondsSinceEpoch); - } - - final int _seconds; - final int _nanoseconds; - - static const int _kStartOfTime = -62135596800; - static const int _kEndOfTime = 253402300800; - - int get seconds => _seconds; - - int get nanoseconds => _nanoseconds; - - int get millisecondsSinceEpoch => - (seconds * _kThousand + nanoseconds / _kMillion).floor(); - - int get microsecondsSinceEpoch => - (seconds * _kMillion + nanoseconds / _kThousand).floor(); - - DateTime toDate() { - return DateTime.fromMicrosecondsSinceEpoch(microsecondsSinceEpoch); - } - - @override - int get hashCode => hashValues(seconds, nanoseconds); - @override - bool operator ==(dynamic o) => - o is Timestamp && o.seconds == seconds && o.nanoseconds == nanoseconds; - @override - int compareTo(Timestamp other) { - if (seconds == other.seconds) { - return nanoseconds.compareTo(other.nanoseconds); - } - - return seconds.compareTo(other.seconds); - } - - @override - String toString() { - return "Timestamp(seconds=$seconds, nanoseconds=$nanoseconds)"; - } - - static void _validateRange(int seconds, int nanoseconds) { - _check(nanoseconds >= 0, 'nanoseconds', nanoseconds); - _check(nanoseconds < _kBillion, 'nanoseconds', nanoseconds); - _check(seconds >= _kStartOfTime, 'seconds', seconds); - _check(seconds < _kEndOfTime, 'seconds', seconds); - } -} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/transaction.dart b/packages/cloud_firestore/cloud_firestore/lib/src/transaction.dart index d13e3f19f9b2..8c89ae8491e9 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/transaction.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/transaction.dart @@ -4,38 +4,27 @@ part of cloud_firestore; +/// The [TransactionHandler] may be executed multiple times; it should be able +/// to handle multiple executions. typedef Future TransactionHandler(Transaction transaction); class Transaction { - @visibleForTesting - Transaction(this._transactionId, this._firestore); + final Firestore _firestore; - int _transactionId; - Firestore _firestore; - List> _pendingResults = >[]; - Future _finish() => Future.wait(_pendingResults); - - /// Reads the document referenced by the provided DocumentReference. - Future get(DocumentReference documentReference) { - final Future result = _get(documentReference); - _pendingResults.add(result); - return result; + Transaction._(this._delegate, this._firestore) { + platform.TransactionPlatform.verifyExtends(_delegate); } - Future _get(DocumentReference documentReference) async { - final Map result = await Firestore.channel - .invokeMapMethod('Transaction#get', { - 'app': _firestore.app.name, - 'transactionId': _transactionId, - 'path': documentReference.path, - }); + platform.TransactionPlatform _delegate; + + // ignore: unused_element + Future _finish() => _delegate.finish(); + + /// Reads the document referenced by the provided DocumentReference. + Future get(DocumentReference documentReference) async { + final result = await _delegate.get(documentReference._delegate); if (result != null) { - return DocumentSnapshot._( - documentReference.path, - result['data']?.cast(), - SnapshotMetadata._(result['metadata']['hasPendingWrites'], - result['metadata']['isFromCache']), - _firestore); + return DocumentSnapshot._(result, _firestore); } else { return null; } @@ -46,18 +35,7 @@ class Transaction { /// Awaiting the returned [Future] is optional and will be done automatically /// when the transaction handler completes. Future delete(DocumentReference documentReference) { - final Future result = _delete(documentReference); - _pendingResults.add(result); - return result; - } - - Future _delete(DocumentReference documentReference) async { - return Firestore.channel - .invokeMethod('Transaction#delete', { - 'app': _firestore.app.name, - 'transactionId': _transactionId, - 'path': documentReference.path, - }); + return _delegate.delete(documentReference._delegate); } /// Updates fields in the document referred to by [documentReference]. @@ -67,20 +45,8 @@ class Transaction { /// when the transaction handler completes. Future update( DocumentReference documentReference, Map data) async { - final Future result = _update(documentReference, data); - _pendingResults.add(result); - return result; - } - - Future _update( - DocumentReference documentReference, Map data) async { - return Firestore.channel - .invokeMethod('Transaction#update', { - 'app': _firestore.app.name, - 'transactionId': _transactionId, - 'path': documentReference.path, - 'data': data, - }); + return _delegate.update(documentReference._delegate, + _CodecUtility.replaceValueWithDelegatesInMap(data)); } /// Writes to the document referred to by the provided [DocumentReference]. @@ -91,19 +57,7 @@ class Transaction { /// when the transaction handler completes. Future set( DocumentReference documentReference, Map data) { - final Future result = _set(documentReference, data); - _pendingResults.add(result); - return result; - } - - Future _set( - DocumentReference documentReference, Map data) async { - return Firestore.channel - .invokeMethod('Transaction#set', { - 'app': _firestore.app.name, - 'transactionId': _transactionId, - 'path': documentReference.path, - 'data': data, - }); + return _delegate.set(documentReference._delegate, + _CodecUtility.replaceValueWithDelegatesInMap(data)); } } diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/utils/auto_id_generator.dart b/packages/cloud_firestore/cloud_firestore/lib/src/utils/auto_id_generator.dart deleted file mode 100644 index e2e1cc57138a..000000000000 --- a/packages/cloud_firestore/cloud_firestore/lib/src/utils/auto_id_generator.dart +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2017, 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 'dart:math'; - -/// Utility class for generating Firebase child node keys. -/// -/// Since the Flutter plugin API is asynchronous, there's no way for us -/// to use the native SDK to generate the node key synchronously and we -/// have to do it ourselves if we want to be able to reference the -/// newly-created node synchronously. -/// -/// This code is based largely on the Android implementation and ported to Dart. - -class AutoIdGenerator { - static const int _AUTO_ID_LENGTH = 20; - - static const String _AUTO_ID_ALPHABET = - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - - static final Random _random = Random(); - - static String autoId() { - final StringBuffer stringBuffer = StringBuffer(); - final int maxRandom = _AUTO_ID_ALPHABET.length; - - for (int i = 0; i < _AUTO_ID_LENGTH; ++i) { - stringBuffer.write(_AUTO_ID_ALPHABET[_random.nextInt(maxRandom)]); - } - - return stringBuffer.toString(); - } -} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/utils/codec_utility.dart b/packages/cloud_firestore/cloud_firestore/lib/src/utils/codec_utility.dart new file mode 100644 index 000000000000..bd090bab5eb0 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/utils/codec_utility.dart @@ -0,0 +1,66 @@ +// Copyright 2017, 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; + +class _CodecUtility { + static Map replaceValueWithDelegatesInMap( + Map data) { + if (data == null) { + return null; + } + Map output = Map.from(data); + output.updateAll((_, value) => valueEncode(value)); + return output; + } + + static List replaceValueWithDelegatesInArray(List data) { + if (data == null) { + return null; + } + return List.from(data).map((value) => valueEncode(value)).toList(); + } + + static Map replaceDelegatesWithValueInMap( + Map data, Firestore firestore) { + if (data == null) { + return null; + } + Map output = Map.from(data); + output.updateAll((_, value) => valueDecode(value, firestore)); + return output; + } + + static List replaceDelegatesWithValueInArray( + List data, Firestore firestore) { + if (data == null) { + return null; + } + return List.from(data) + .map((value) => valueDecode(value, firestore)) + .toList(); + } + + static dynamic valueEncode(dynamic value) { + if (value is DocumentReference) { + return value._delegate; + } else if (value is List) { + return replaceValueWithDelegatesInArray(value); + } else if (value is Map) { + return replaceValueWithDelegatesInMap(value); + } + return value; + } + + static dynamic valueDecode(dynamic value, Firestore firestore) { + if (value is platform.DocumentReferencePlatform) { + return DocumentReference._(value, firestore); + } else if (value is List) { + return replaceDelegatesWithValueInArray(value, firestore); + } else if (value is Map) { + return replaceDelegatesWithValueInMap(value, firestore); + } + return value; + } +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/utils/platform_utils.dart b/packages/cloud_firestore/cloud_firestore/lib/src/utils/platform_utils.dart new file mode 100644 index 000000000000..c9880257c67a --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/utils/platform_utils.dart @@ -0,0 +1,18 @@ +// Copyright 2017, 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; + +class _PlatformUtils { + static platform.DocumentSnapshotPlatform toPlatformDocumentSnapshot( + DocumentSnapshot documentSnapshot) => + platform.DocumentSnapshotPlatform( + documentSnapshot.reference.path, + documentSnapshot.data, + platform.SnapshotMetadataPlatform( + documentSnapshot.metadata.hasPendingWrites, + documentSnapshot.metadata.isFromCache, + ), + platform.FirestorePlatform.instance); +} diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/write_batch.dart b/packages/cloud_firestore/cloud_firestore/lib/src/write_batch.dart index 6a41ee7bfec3..92c6eb147864 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/write_batch.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/write_batch.dart @@ -10,103 +10,26 @@ part of cloud_firestore; /// /// Once committed, no further operations can be performed on the [WriteBatch], /// nor can it be committed again. -class WriteBatch { - WriteBatch._(this._firestore) - : _handle = Firestore.channel.invokeMethod( - 'WriteBatch#create', {'app': _firestore.app.name}); - - final Firestore _firestore; - Future _handle; - final List> _actions = >[]; - /// Indicator to whether or not this [WriteBatch] has been committed. - bool _committed = false; +class WriteBatch { + final platform.WriteBatchPlatform _delegate; - /// Commits all of the writes in this write batch as a single atomic unit. - /// - /// Calling this method prevents any future operations from being added. - Future commit() async { - if (!_committed) { - _committed = true; - await Future.wait(_actions); - await Firestore.channel.invokeMethod( - 'WriteBatch#commit', {'handle': await _handle}); - } else { - throw StateError("This batch has already been committed."); - } + WriteBatch._(this._delegate) { + platform.WriteBatchPlatform.verifyExtends(_delegate); } - /// Deletes the document referred to by [document]. - void delete(DocumentReference document) { - if (!_committed) { - _handle.then((dynamic handle) { - _actions.add( - Firestore.channel.invokeMethod( - 'WriteBatch#delete', - { - 'app': _firestore.app.name, - 'handle': handle, - 'path': document.path, - }, - ), - ); - }); - } else { - throw StateError( - "This batch has been committed and can no longer be changed."); - } - } + Future commit() => _delegate.commit(); - /// Writes to the document referred to by [document]. - /// - /// If the document does not yet exist, it will be created. - /// - /// If [merge] is true, the provided data will be merged into an - /// existing document instead of overwriting. - void setData(DocumentReference document, Map data, - {bool merge = false}) { - if (!_committed) { - _handle.then((dynamic handle) { - _actions.add( - Firestore.channel.invokeMethod( - 'WriteBatch#setData', - { - 'app': _firestore.app.name, - 'handle': handle, - 'path': document.path, - 'data': data, - 'options': {'merge': merge}, - }, - ), - ); - }); - } else { - throw StateError( - "This batch has been committed and can no longer be changed."); - } - } + void delete(DocumentReference document) => + _delegate.delete(document._delegate); - /// Updates fields in the document referred to by [document]. - /// - /// If the document does not exist, the operation will fail. - void updateData(DocumentReference document, Map data) { - if (!_committed) { - _handle.then((dynamic handle) { - _actions.add( - Firestore.channel.invokeMethod( - 'WriteBatch#updateData', - { - 'app': _firestore.app.name, - 'handle': handle, - 'path': document.path, - 'data': data, - }, - ), - ); - }); - } else { - throw StateError( - "This batch has been committed and can no longer be changed."); - } - } + void setData(DocumentReference document, Map data, + {bool merge = false}) => + _delegate.setData(document._delegate, + _CodecUtility.replaceValueWithDelegatesInMap(data), + merge: merge); + + void updateData(DocumentReference document, Map data) => + _delegate.updateData(document._delegate, + _CodecUtility.replaceValueWithDelegatesInMap(data)); } diff --git a/packages/cloud_firestore/cloud_firestore/pubspec.yaml b/packages/cloud_firestore/cloud_firestore/pubspec.yaml index 47c6061427a0..3b2af29f6834 100755 --- a/packages/cloud_firestore/cloud_firestore/pubspec.yaml +++ b/packages/cloud_firestore/cloud_firestore/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Cloud Firestore, a cloud-hosted, noSQL database with live synchronization and offline support on Android and iOS. homepage: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/cloud_firestore/cloud_firestore -version: 0.13.0+2 +version: 0.13.1 flutter: plugin: @@ -18,8 +18,8 @@ dependencies: flutter: sdk: flutter meta: "^1.0.5" - collection: "^1.14.3" firebase_core: "^0.4.0" + cloud_firestore_platform_interface: "^1.0.0" dev_dependencies: flutter_test: diff --git a/packages/cloud_firestore/cloud_firestore/test/cloud_firestore_test.dart b/packages/cloud_firestore/cloud_firestore/test/cloud_firestore_test.dart deleted file mode 100755 index 328427f46e04..000000000000 --- a/packages/cloud_firestore/cloud_firestore/test/cloud_firestore_test.dart +++ /dev/null @@ -1,1437 +0,0 @@ -// Copyright 2017 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 'dart:async'; -import 'dart:typed_data'; - -import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:firebase_core/firebase_core.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group('$Firestore', () { - int mockHandleId = 0; - FirebaseApp app; - Firestore firestore; - final List log = []; - CollectionReference collectionReference; - Query collectionGroupQuery; - Transaction transaction; - const Map kMockDocumentSnapshotData = { - '1': 2 - }; - const Map kMockSnapshotMetadata = { - "hasPendingWrites": false, - "isFromCache": false, - }; - const MethodChannel firebaseCoreChannel = - MethodChannel('plugins.flutter.io/firebase_core'); - - setUp(() async { - mockHandleId = 0; - // Required for FirebaseApp.configure - firebaseCoreChannel.setMockMethodCallHandler( - (MethodCall methodCall) async {}, - ); - app = await FirebaseApp.configure( - name: 'testApp', - options: const FirebaseOptions( - googleAppID: '1:1234567890:ios:42424242424242', - gcmSenderID: '1234567890', - ), - ); - 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); - switch (methodCall.method) { - case 'Query#addSnapshotListener': - final int handle = mockHandleId++; - // Wait before sending a message back. - // Otherwise the first request didn't have the time to finish. - Future.delayed(Duration.zero).then((_) { - // TODO(hterkelsen): Remove this when defaultBinaryMessages is in stable. - // https://github.com/flutter/flutter/issues/33446 - // ignore: deprecated_member_use - BinaryMessages.handlePlatformMessage( - Firestore.channel.name, - Firestore.channel.codec.encodeMethodCall( - MethodCall('QuerySnapshot', { - 'app': app.name, - 'handle': handle, - 'paths': ["${methodCall.arguments['path']}/0"], - 'documents': [kMockDocumentSnapshotData], - 'metadatas': >[kMockSnapshotMetadata], - 'metadata': kMockSnapshotMetadata, - 'documentChanges': [ - { - 'oldIndex': -1, - 'newIndex': 0, - 'type': 'DocumentChangeType.added', - 'document': kMockDocumentSnapshotData, - 'metadata': kMockSnapshotMetadata, - }, - ], - }), - ), - (_) {}, - ); - }); - return handle; - case 'DocumentReference#addSnapshotListener': - final int handle = mockHandleId++; - // Wait before sending a message back. - // Otherwise the first request didn't have the time to finish. - Future.delayed(Duration.zero).then((_) { - // TODO(hterkelsen): Remove this when defaultBinaryMessages is in stable. - // https://github.com/flutter/flutter/issues/33446 - // ignore: deprecated_member_use - BinaryMessages.handlePlatformMessage( - Firestore.channel.name, - Firestore.channel.codec.encodeMethodCall( - MethodCall('DocumentSnapshot', { - 'handle': handle, - 'path': methodCall.arguments['path'], - 'data': kMockDocumentSnapshotData, - 'metadata': kMockSnapshotMetadata, - }), - ), - (_) {}, - ); - }); - return handle; - case 'Query#getDocuments': - return { - 'paths': ["${methodCall.arguments['path']}/0"], - 'documents': [kMockDocumentSnapshotData], - 'metadatas': >[kMockSnapshotMetadata], - 'metadata': kMockSnapshotMetadata, - 'documentChanges': [ - { - 'oldIndex': -1, - 'newIndex': 0, - 'type': 'DocumentChangeType.added', - 'document': kMockDocumentSnapshotData, - 'metadata': kMockSnapshotMetadata, - }, - ], - }; - case 'DocumentReference#setData': - return true; - case 'DocumentReference#get': - if (methodCall.arguments['path'] == 'foo/bar') { - return { - 'path': 'foo/bar', - 'data': {'key1': 'val1'}, - 'metadata': kMockSnapshotMetadata, - }; - } else if (methodCall.arguments['path'] == 'foo/notExists') { - return { - 'path': 'foo/notExists', - 'data': null, - 'metadata': kMockSnapshotMetadata, - }; - } - throw PlatformException(code: 'UNKNOWN_PATH'); - case 'Firestore#runTransaction': - return {'1': 3}; - case 'Transaction#get': - if (methodCall.arguments['path'] == 'foo/bar') { - return { - 'path': 'foo/bar', - 'data': {'key1': 'val1'}, - 'metadata': kMockSnapshotMetadata, - }; - } else if (methodCall.arguments['path'] == 'foo/notExists') { - return { - 'path': 'foo/notExists', - 'data': null, - 'metadata': kMockSnapshotMetadata, - }; - } - throw PlatformException(code: 'UNKNOWN_PATH'); - case 'Transaction#set': - return null; - case 'Transaction#update': - return null; - case 'Transaction#delete': - return null; - case 'WriteBatch#create': - return 1; - default: - return null; - } - }); - log.clear(); - }); - - test('multiple apps', () async { - expect(Firestore.instance, equals(Firestore())); - final FirebaseApp app = FirebaseApp(name: firestore.app.name); - expect(firestore, equals(Firestore(app: app))); - }); - - test('settings', () async { - final FirebaseApp app = FirebaseApp(name: "testApp2"); - final Firestore firestoreWithSettings = Firestore(app: app); - await firestoreWithSettings.settings( - persistenceEnabled: true, - host: null, - sslEnabled: true, - cacheSizeBytes: 500000, - ); - expect(log, [ - isMethodCall('Firestore#settings', arguments: { - 'app': firestoreWithSettings.app.name, - 'persistenceEnabled': true, - 'host': null, - 'sslEnabled': true, - 'cacheSizeBytes': 500000, - }), - ]); - }); - - group('Transaction', () { - test('runTransaction', () async { - final Map result = await firestore.runTransaction( - (Transaction tx) async {}, - timeout: const Duration(seconds: 3)); - - expect(log, [ - isMethodCall('Firestore#runTransaction', arguments: { - 'app': app.name, - 'transactionId': 0, - 'transactionTimeout': 3000 - }), - ]); - expect(result, equals({'1': 3})); - }); - - test('get', () async { - final DocumentReference documentReference = - firestore.document('foo/bar'); - final DocumentSnapshot snapshot = - await transaction.get(documentReference); - expect(snapshot.reference.firestore, firestore); - expect(log, [ - isMethodCall('Transaction#get', arguments: { - 'app': app.name, - 'transactionId': 0, - 'path': documentReference.path - }) - ]); - }); - - test('get notExists', () async { - final DocumentReference documentReference = - firestore.document('foo/notExists'); - await transaction.get(documentReference); - expect(log, [ - isMethodCall('Transaction#get', arguments: { - 'app': app.name, - 'transactionId': 0, - 'path': documentReference.path - }) - ]); - }); - - test('delete', () async { - final DocumentReference documentReference = - firestore.document('foo/bar'); - await transaction.delete(documentReference); - expect(log, [ - isMethodCall('Transaction#delete', arguments: { - 'app': app.name, - 'transactionId': 0, - 'path': documentReference.path - }) - ]); - }); - - test('update', () async { - final DocumentReference documentReference = - firestore.document('foo/bar'); - final DocumentSnapshot documentSnapshot = await documentReference.get(); - final Map data = documentSnapshot.data; - data['key2'] = 'val2'; - await transaction.set(documentReference, data); - expect(log, [ - isMethodCall('DocumentReference#get', arguments: { - 'app': app.name, - 'path': 'foo/bar', - 'source': 'default', - }), - isMethodCall('Transaction#set', arguments: { - 'app': app.name, - 'transactionId': 0, - 'path': documentReference.path, - 'data': {'key1': 'val1', 'key2': 'val2'} - }) - ]); - }); - - test('set', () async { - final DocumentReference documentReference = - firestore.document('foo/bar'); - final DocumentSnapshot documentSnapshot = await documentReference.get(); - final Map data = documentSnapshot.data; - data['key2'] = 'val2'; - await transaction.set(documentReference, data); - expect(log, [ - isMethodCall('DocumentReference#get', arguments: { - 'app': app.name, - 'path': 'foo/bar', - 'source': 'default', - }), - isMethodCall('Transaction#set', arguments: { - 'app': app.name, - 'transactionId': 0, - 'path': documentReference.path, - 'data': {'key1': 'val1', 'key2': 'val2'} - }) - ]); - }); - }); - - group('Blob', () { - test('hashCode equality', () async { - final Uint8List bytesA = Uint8List(8); - bytesA.setAll(0, [0, 2, 4, 6, 8, 10, 12, 14]); - final Blob a = Blob(bytesA); - final Uint8List bytesB = Uint8List(8); - bytesB.setAll(0, [0, 2, 4, 6, 8, 10, 12, 14]); - final Blob b = Blob(bytesB); - expect(a.hashCode == b.hashCode, isTrue); - }); - test('hashCode not equal', () async { - final Uint8List bytesA = Uint8List(8); - bytesA.setAll(0, [0, 2, 4, 6, 8, 10, 12, 14]); - final Blob a = Blob(bytesA); - final Uint8List bytesB = Uint8List(8); - bytesB.setAll(0, [1, 2, 4, 6, 8, 10, 12, 14]); - final Blob b = Blob(bytesB); - expect(a.hashCode == b.hashCode, isFalse); - }); - }); - - group('CollectionsReference', () { - test('id', () async { - expect(collectionReference.id, equals('foo')); - }); - test('parent', () async { - final DocumentReference docRef = collectionReference.document('bar'); - expect(docRef.parent().id, equals('foo')); - expect(collectionReference.parent(), isNull); - }); - test('path', () async { - expect(collectionReference.path, equals('foo')); - }); - test('listen', () async { - final QuerySnapshot snapshot = await collectionReference - .snapshots(includeMetadataChanges: true) - .first; - final DocumentSnapshot document = snapshot.documents[0]; - expect(document.documentID, equals('0')); - expect(document.reference.path, equals('foo/0')); - expect(document.data, equals(kMockDocumentSnapshotData)); - // Flush the async removeListener call - await Future.delayed(Duration.zero); - expect(log, [ - isMethodCall( - 'Query#addSnapshotListener', - arguments: { - 'app': app.name, - 'path': 'foo', - 'isCollectionGroup': false, - 'parameters': { - 'where': >[], - 'orderBy': >[], - }, - 'includeMetadataChanges': true, - }, - ), - isMethodCall( - 'removeListener', - arguments: {'handle': 0}, - ), - ]); - }); - test('where', () async { - final StreamSubscription subscription = - collectionReference - .where('createdAt', isLessThan: 100) - .snapshots() - .listen((QuerySnapshot querySnapshot) {}); - subscription.cancel(); - await Future.delayed(Duration.zero); - expect( - log, - equals([ - isMethodCall( - 'Query#addSnapshotListener', - arguments: { - 'app': app.name, - 'path': 'foo', - 'isCollectionGroup': false, - 'parameters': { - 'where': >[ - ['createdAt', '<', 100], - ], - 'orderBy': >[], - }, - 'includeMetadataChanges': false, - }, - ), - isMethodCall( - 'removeListener', - arguments: {'handle': 0}, - ), - ]), - ); - }); - test('where in', () async { - final StreamSubscription subscription = - collectionReference - .where('country', whereIn: ['USA', 'Japan']) - .snapshots() - .listen((QuerySnapshot querySnapshot) {}); - subscription.cancel(); - await Future.delayed(Duration.zero); - expect( - log, - equals([ - isMethodCall( - 'Query#addSnapshotListener', - arguments: { - 'app': app.name, - 'path': 'foo', - 'isCollectionGroup': false, - 'parameters': { - 'where': >[ - [ - 'country', - 'in', - ['USA', 'Japan'] - ], - ], - 'orderBy': >[], - }, - 'includeMetadataChanges': false, - }, - ), - isMethodCall( - 'removeListener', - arguments: {'handle': 0}, - ), - ]), - ); - }); - test('where array-contains-any', () async { - final StreamSubscription subscription = - collectionReference - .where('regions', - arrayContainsAny: ['west-coast', 'east-coast']) - .snapshots() - .listen((QuerySnapshot querySnapshot) {}); - subscription.cancel(); - await Future.delayed(Duration.zero); - expect( - log, - equals([ - isMethodCall( - 'Query#addSnapshotListener', - arguments: { - 'app': app.name, - 'path': 'foo', - 'isCollectionGroup': false, - 'parameters': { - 'where': >[ - [ - 'regions', - 'array-contains-any', - ['west-coast', 'east-coast'] - ], - ], - 'orderBy': >[], - }, - 'includeMetadataChanges': false, - }, - ), - isMethodCall( - 'removeListener', - arguments: {'handle': 0}, - ), - ]), - ); - }); - test('where field isNull', () async { - final StreamSubscription subscription = - collectionReference - .where('profile', isNull: true) - .snapshots() - .listen((QuerySnapshot querySnapshot) {}); - subscription.cancel(); - await Future.delayed(Duration.zero); - expect( - log, - equals([ - isMethodCall( - 'Query#addSnapshotListener', - arguments: { - 'app': app.name, - 'path': 'foo', - 'isCollectionGroup': false, - 'parameters': { - 'where': >[ - ['profile', '==', null], - ], - 'orderBy': >[], - }, - 'includeMetadataChanges': false, - }, - ), - isMethodCall( - 'removeListener', - arguments: {'handle': 0}, - ), - ]), - ); - }); - test('orderBy', () async { - final StreamSubscription subscription = - collectionReference - .orderBy('createdAt') - .snapshots() - .listen((QuerySnapshot querySnapshot) {}); - subscription.cancel(); - await Future.delayed(Duration.zero); - expect( - log, - equals([ - isMethodCall( - 'Query#addSnapshotListener', - arguments: { - 'app': app.name, - 'path': 'foo', - 'isCollectionGroup': false, - 'parameters': { - 'where': >[], - 'orderBy': >[ - ['createdAt', false] - ], - }, - 'includeMetadataChanges': false, - }, - ), - isMethodCall( - 'removeListener', - arguments: {'handle': 0}, - ), - ]), - ); - }); - }); - - group('DocumentReference', () { - test('listen', () async { - final DocumentSnapshot snapshot = await firestore - .document('path/to/foo') - .snapshots(includeMetadataChanges: true) - .first; - expect(snapshot.documentID, equals('foo')); - expect(snapshot.reference.path, equals('path/to/foo')); - expect(snapshot.data, equals(kMockDocumentSnapshotData)); - // Flush the async removeListener call - await Future.delayed(Duration.zero); - expect( - log, - [ - isMethodCall( - 'DocumentReference#addSnapshotListener', - arguments: { - 'app': app.name, - 'path': 'path/to/foo', - 'includeMetadataChanges': true, - }, - ), - isMethodCall( - 'removeListener', - arguments: {'handle': 0}, - ), - ], - ); - }); - test('set', () async { - await collectionReference - .document('bar') - .setData({'bazKey': 'quxValue'}); - expect( - log, - [ - isMethodCall( - 'DocumentReference#setData', - arguments: { - 'app': app.name, - 'path': 'foo/bar', - 'data': {'bazKey': 'quxValue'}, - 'options': {'merge': false}, - }, - ), - ], - ); - }); - test('merge set', () async { - await collectionReference - .document('bar') - .setData({'bazKey': 'quxValue'}, merge: true); - expect( - log, - [ - isMethodCall( - 'DocumentReference#setData', - arguments: { - 'app': app.name, - 'path': 'foo/bar', - 'data': {'bazKey': 'quxValue'}, - 'options': {'merge': true}, - }, - ), - ], - ); - }); - test('update', () async { - await collectionReference - .document('bar') - .updateData({'bazKey': 'quxValue'}); - expect( - log, - [ - isMethodCall( - 'DocumentReference#updateData', - arguments: { - 'app': app.name, - 'path': 'foo/bar', - 'data': {'bazKey': 'quxValue'}, - }, - ), - ], - ); - }); - test('delete', () async { - await collectionReference.document('bar').delete(); - expect( - log, - equals([ - isMethodCall( - 'DocumentReference#delete', - arguments: { - 'app': app.name, - 'path': 'foo/bar', - }, - ), - ]), - ); - }); - test('get', () async { - final DocumentSnapshot snapshot = - await collectionReference.document('bar').get(source: Source.cache); - expect(snapshot.reference.firestore, firestore); - expect( - log, - equals([ - isMethodCall( - 'DocumentReference#get', - arguments: { - 'app': app.name, - 'path': 'foo/bar', - 'source': 'cache', - }, - ), - ]), - ); - log.clear(); - expect(snapshot.reference.path, equals('foo/bar')); - expect(snapshot.data.containsKey('key1'), equals(true)); - expect(snapshot.data['key1'], equals('val1')); - expect(snapshot.exists, isTrue); - - final DocumentSnapshot snapshot2 = await collectionReference - .document('notExists') - .get(source: Source.serverAndCache); - expect(snapshot2.data, isNull); - expect(snapshot2.exists, isFalse); - expect( - log, - equals([ - isMethodCall( - 'DocumentReference#get', - arguments: { - 'app': app.name, - 'path': 'foo/notExists', - 'source': 'default', - }, - ), - ]), - ); - - try { - await collectionReference.document('baz').get(); - } on PlatformException catch (e) { - expect(e.code, equals('UNKNOWN_PATH')); - } - }); - test('collection', () async { - final CollectionReference colRef = - collectionReference.document('bar').collection('baz'); - expect(colRef.path, equals('foo/bar/baz')); - }); - test('parent', () async { - final CollectionReference colRef = - collectionReference.document('bar').collection('baz'); - expect(colRef.parent().documentID, equals('bar')); - }); - }); - - group('Query', () { - test('getDocumentsFromCollection', () async { - QuerySnapshot snapshot = - await collectionReference.getDocuments(source: Source.server); - expect(snapshot.metadata.hasPendingWrites, - equals(kMockSnapshotMetadata['hasPendingWrites'])); - expect(snapshot.metadata.isFromCache, - equals(kMockSnapshotMetadata['isFromCache'])); - DocumentSnapshot document = snapshot.documents.first; - expect(document.documentID, equals('0')); - expect(document.reference.path, equals('foo/0')); - expect(document.data, equals(kMockDocumentSnapshotData)); - - // startAtDocument - snapshot = - await collectionReference.startAtDocument(document).getDocuments(); - document = snapshot.documents.first; - expect(document.documentID, equals('0')); - expect(document.reference.path, equals('foo/0')); - expect(document.data, equals(kMockDocumentSnapshotData)); - - // startAfterDocument - snapshot = await collectionReference - .startAfterDocument(document) - .getDocuments(); - document = snapshot.documents.first; - expect(document.documentID, equals('0')); - expect(document.reference.path, equals('foo/0')); - expect(document.data, equals(kMockDocumentSnapshotData)); - - // endAtDocument - snapshot = - await collectionReference.endAtDocument(document).getDocuments(); - document = snapshot.documents.first; - expect(document.documentID, equals('0')); - expect(document.reference.path, equals('foo/0')); - expect(document.data, equals(kMockDocumentSnapshotData)); - - // endBeforeDocument - snapshot = await collectionReference - .endBeforeDocument(document) - .getDocuments(); - document = snapshot.documents.first; - expect(document.documentID, equals('0')); - expect(document.reference.path, equals('foo/0')); - expect(document.data, equals(kMockDocumentSnapshotData)); - - // startAtDocument - endAtDocument - snapshot = await collectionReference - .startAtDocument(document) - .endAtDocument(document) - .getDocuments(); - document = snapshot.documents.first; - expect(document.documentID, equals('0')); - expect(document.reference.path, equals('foo/0')); - expect(document.data, equals(kMockDocumentSnapshotData)); - - expect( - log, - equals( - [ - isMethodCall( - 'Query#getDocuments', - arguments: { - 'app': app.name, - 'path': 'foo', - 'isCollectionGroup': false, - 'source': 'server', - 'parameters': { - 'where': >[], - 'orderBy': >[], - }, - }, - ), - isMethodCall( - 'Query#getDocuments', - arguments: { - 'app': app.name, - 'path': 'foo', - 'isCollectionGroup': false, - 'source': 'default', - 'parameters': { - 'where': >[], - 'orderBy': >[], - 'startAtDocument': { - 'id': '0', - 'path': 'foo/0', - 'data': kMockDocumentSnapshotData, - }, - }, - }, - ), - isMethodCall( - 'Query#getDocuments', - arguments: { - 'app': app.name, - 'path': 'foo', - 'isCollectionGroup': false, - 'source': 'default', - 'parameters': { - 'where': >[], - 'orderBy': >[], - 'startAfterDocument': { - 'id': '0', - 'path': 'foo/0', - 'data': kMockDocumentSnapshotData, - }, - }, - }, - ), - isMethodCall( - 'Query#getDocuments', - arguments: { - 'app': app.name, - 'path': 'foo', - 'isCollectionGroup': false, - 'source': 'default', - 'parameters': { - 'where': >[], - 'orderBy': >[], - 'endAtDocument': { - 'id': '0', - 'path': 'foo/0', - 'data': kMockDocumentSnapshotData, - }, - }, - }, - ), - isMethodCall( - 'Query#getDocuments', - arguments: { - 'app': app.name, - 'path': 'foo', - 'isCollectionGroup': false, - 'source': 'default', - 'parameters': { - 'where': >[], - 'orderBy': >[], - 'endBeforeDocument': { - 'id': '0', - 'path': 'foo/0', - 'data': kMockDocumentSnapshotData, - }, - }, - }, - ), - isMethodCall( - 'Query#getDocuments', - arguments: { - 'app': app.name, - 'path': 'foo', - 'isCollectionGroup': false, - 'source': 'default', - 'parameters': { - 'where': >[], - 'orderBy': >[], - 'startAtDocument': { - 'id': '0', - 'path': 'foo/0', - 'data': kMockDocumentSnapshotData, - }, - 'endAtDocument': { - 'id': '0', - 'path': 'foo/0', - 'data': kMockDocumentSnapshotData, - }, - }, - }, - ), - ], - ), - ); - }); - test('getDocumentsFromCollectionGroup', () async { - QuerySnapshot snapshot = await collectionGroupQuery.getDocuments(); - expect(snapshot.metadata.hasPendingWrites, - equals(kMockSnapshotMetadata['hasPendingWrites'])); - expect(snapshot.metadata.isFromCache, - equals(kMockSnapshotMetadata['isFromCache'])); - 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)); - - // startAtDocument - endAtDocument - snapshot = await collectionGroupQuery - .startAtDocument(document) - .endAtDocument(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', - 'path': 'bar/0', - 'data': kMockDocumentSnapshotData, - }, - }, - 'source': 'default', - }, - ), - isMethodCall( - 'Query#getDocuments', - arguments: { - 'app': app.name, - 'path': 'bar', - 'isCollectionGroup': true, - 'parameters': { - 'where': >[], - 'orderBy': >[], - 'startAfterDocument': { - 'id': '0', - 'path': 'bar/0', - 'data': kMockDocumentSnapshotData, - }, - }, - 'source': 'default', - }, - ), - isMethodCall( - 'Query#getDocuments', - arguments: { - 'app': app.name, - 'path': 'bar', - 'isCollectionGroup': true, - 'parameters': { - 'where': >[], - 'orderBy': >[], - 'endAtDocument': { - 'id': '0', - 'path': 'bar/0', - 'data': kMockDocumentSnapshotData, - }, - }, - 'source': 'default', - }, - ), - isMethodCall( - 'Query#getDocuments', - arguments: { - 'app': app.name, - 'path': 'bar', - 'isCollectionGroup': true, - 'source': 'default', - 'parameters': { - 'where': >[], - 'orderBy': >[], - 'endBeforeDocument': { - 'id': '0', - 'path': 'bar/0', - 'data': kMockDocumentSnapshotData, - }, - }, - 'source': 'default', - }, - ), - isMethodCall( - 'Query#getDocuments', - arguments: { - 'app': app.name, - 'path': 'bar', - 'isCollectionGroup': true, - 'source': 'default', - 'parameters': { - 'where': >[], - 'orderBy': >[], - 'startAtDocument': { - 'id': '0', - 'path': 'bar/0', - 'data': kMockDocumentSnapshotData, - }, - 'endAtDocument': { - 'id': '0', - 'path': 'bar/0', - 'data': kMockDocumentSnapshotData, - }, - }, - 'source': 'default', - }, - ), - ], - ), - ); - }); - - test('FieldPath', () async { - await collectionReference - .where(FieldPath.documentId, isEqualTo: 'bar') - .getDocuments(); - expect( - log, - equals([ - isMethodCall( - 'Query#getDocuments', - arguments: { - 'app': app.name, - 'path': 'foo', - 'isCollectionGroup': false, - 'parameters': { - 'where': >[ - [FieldPath.documentId, '==', 'bar'], - ], - 'orderBy': >[], - }, - 'source': 'default', - }, - ), - ]), - ); - }); - test('orderBy assertions', () async { - // Can only order by the same field once. - expect(() { - firestore.collection('foo').orderBy('bar').orderBy('bar'); - }, throwsAssertionError); - // Cannot order by unsupported types. - expect(() { - firestore.collection('foo').orderBy(0); - }, throwsAssertionError); - // Parameters cannot be null. - expect(() { - firestore.collection('foo').orderBy(null); - }, throwsAssertionError); - expect(() { - firestore.collection('foo').orderBy('bar', descending: null); - }, throwsAssertionError); - - // Cannot order by document id when paginating with documents. - final DocumentReference documentReference = - firestore.document('foo/bar'); - final DocumentSnapshot snapshot = await documentReference.get(); - expect(() { - firestore - .collection('foo') - .startAfterDocument(snapshot) - .orderBy(FieldPath.documentId); - }, throwsAssertionError); - }); - test('document pagination FieldPath assertions', () async { - final DocumentReference documentReference = - firestore.document('foo/bar'); - final DocumentSnapshot snapshot = await documentReference.get(); - final Query query = - firestore.collection('foo').orderBy(FieldPath.documentId); - - expect(() { - query.startAfterDocument(snapshot); - }, throwsAssertionError); - expect(() { - query.startAtDocument(snapshot); - }, throwsAssertionError); - expect(() { - query.endAtDocument(snapshot); - }, throwsAssertionError); - expect(() { - query.endBeforeDocument(snapshot); - }, throwsAssertionError); - }); - }); - - group('FirestoreMessageCodec', () { - const MessageCodec codec = FirestoreMessageCodec(); - final DateTime testTime = DateTime(2015, 10, 30, 11, 16); - final Timestamp timestamp = Timestamp.fromDate(testTime); - test('should encode and decode simple messages', () { - _checkEncodeDecode(codec, testTime); - _checkEncodeDecode(codec, timestamp); - _checkEncodeDecode( - codec, const GeoPoint(37.421939, -122.083509)); - _checkEncodeDecode(codec, firestore.document('foo/bar')); - }); - test('should encode and decode composite message', () { - final List message = [ - testTime, - const GeoPoint(37.421939, -122.083509), - firestore.document('foo/bar'), - ]; - _checkEncodeDecode(codec, message); - }); - test('encode and decode blob', () { - final Uint8List bytes = Uint8List(4); - bytes[0] = 128; - final Blob message = Blob(bytes); - _checkEncodeDecode(codec, message); - }); - - test('encode and decode FieldValue', () { - _checkEncodeDecode(codec, FieldValue.arrayUnion([123])); - _checkEncodeDecode(codec, FieldValue.arrayRemove([123])); - _checkEncodeDecode(codec, FieldValue.delete()); - _checkEncodeDecode(codec, FieldValue.serverTimestamp()); - _checkEncodeDecode(codec, FieldValue.increment(1.0)); - _checkEncodeDecode(codec, FieldValue.increment(1)); - }); - - test('encode and decode FieldPath', () { - _checkEncodeDecode(codec, FieldPath.documentId); - }); - }); - - group('Timestamp', () { - test('is accurate for dates after epoch', () { - final DateTime date = DateTime.fromMillisecondsSinceEpoch(22501); - final Timestamp timestamp = Timestamp.fromDate(date); - - expect(timestamp.seconds, equals(22)); - expect(timestamp.nanoseconds, equals(501000000)); - }); - - test('is accurate for dates before epoch', () { - final DateTime date = DateTime.fromMillisecondsSinceEpoch(-1250); - final Timestamp timestamp = Timestamp.fromDate(date); - - expect(timestamp.seconds, equals(-2)); - expect(timestamp.nanoseconds, equals(750000000)); - }); - - test('creates equivalent timestamps regardless of factory', () { - const int kMilliseconds = 22501; - const int kMicroseconds = 22501000; - final DateTime date = - DateTime.fromMicrosecondsSinceEpoch(kMicroseconds); - - final Timestamp timestamp = Timestamp(22, 501000000); - final Timestamp milliTimestamp = - Timestamp.fromMillisecondsSinceEpoch(kMilliseconds); - final Timestamp microTimestamp = - Timestamp.fromMicrosecondsSinceEpoch(kMicroseconds); - final Timestamp dateTimestamp = Timestamp.fromDate(date); - - expect(timestamp, equals(milliTimestamp)); - expect(milliTimestamp, equals(microTimestamp)); - expect(microTimestamp, equals(dateTimestamp)); - }); - - test('correctly compares timestamps', () { - final Timestamp alpha = Timestamp.fromDate(DateTime(2017, 5, 11)); - final Timestamp beta1 = Timestamp.fromDate(DateTime(2018, 2, 19)); - final Timestamp beta2 = Timestamp.fromDate(DateTime(2018, 4, 2)); - final Timestamp beta3 = Timestamp.fromDate(DateTime(2018, 4, 20)); - final Timestamp preview = Timestamp.fromDate(DateTime(2018, 6, 20)); - final List inOrder = [ - alpha, - beta1, - beta2, - beta3, - preview - ]; - - final List timestamps = [ - beta2, - beta3, - alpha, - preview, - beta1 - ]; - timestamps.sort(); - expect(_deepEqualsList(timestamps, inOrder), isTrue); - }); - - test('rejects dates outside RFC 3339 range', () { - final List invalidDates = [ - DateTime.fromMillisecondsSinceEpoch(-70000000000000), - DateTime.fromMillisecondsSinceEpoch(300000000000000), - ]; - - invalidDates.forEach((DateTime date) { - expect(() => Timestamp.fromDate(date), throwsArgumentError); - }); - }); - }); - - group('WriteBatch', () { - test('set', () async { - final WriteBatch batch = firestore.batch(); - batch.setData( - collectionReference.document('bar'), - {'bazKey': 'quxValue'}, - ); - await batch.commit(); - expect( - log, - [ - isMethodCall('WriteBatch#create', arguments: { - 'app': app.name, - }), - isMethodCall( - 'WriteBatch#setData', - arguments: { - 'app': app.name, - 'handle': 1, - 'path': 'foo/bar', - 'data': {'bazKey': 'quxValue'}, - 'options': {'merge': false}, - }, - ), - isMethodCall( - 'WriteBatch#commit', - arguments: { - 'handle': 1, - }, - ), - ], - ); - }); - test('merge set', () async { - final WriteBatch batch = firestore.batch(); - batch.setData( - collectionReference.document('bar'), - {'bazKey': 'quxValue'}, - merge: true, - ); - await batch.commit(); - expect( - log, - [ - isMethodCall('WriteBatch#create', arguments: { - 'app': app.name, - }), - isMethodCall('WriteBatch#setData', arguments: { - 'app': app.name, - 'handle': 1, - 'path': 'foo/bar', - 'data': {'bazKey': 'quxValue'}, - 'options': {'merge': true}, - }), - isMethodCall( - 'WriteBatch#commit', - arguments: { - 'handle': 1, - }, - ), - ], - ); - }); - test('update', () async { - final WriteBatch batch = firestore.batch(); - batch.updateData( - collectionReference.document('bar'), - {'bazKey': 'quxValue'}, - ); - await batch.commit(); - expect( - log, - [ - isMethodCall( - 'WriteBatch#create', - arguments: { - 'app': app.name, - }, - ), - isMethodCall( - 'WriteBatch#updateData', - arguments: { - 'app': app.name, - 'handle': 1, - 'path': 'foo/bar', - 'data': {'bazKey': 'quxValue'}, - }, - ), - isMethodCall( - 'WriteBatch#commit', - arguments: { - 'handle': 1, - }, - ), - ], - ); - }); - test('delete', () async { - final WriteBatch batch = firestore.batch(); - batch.delete(collectionReference.document('bar')); - await batch.commit(); - expect( - log, - [ - isMethodCall( - 'WriteBatch#create', - arguments: { - 'app': app.name, - }, - ), - isMethodCall( - 'WriteBatch#delete', - arguments: { - 'app': app.name, - 'handle': 1, - 'path': 'foo/bar', - }, - ), - isMethodCall( - 'WriteBatch#commit', - arguments: { - 'handle': 1, - }, - ), - ], - ); - }); - }); - }); -} - -void _checkEncodeDecode(MessageCodec codec, T message) { - final ByteData encoded = codec.encodeMessage(message); - final T decoded = codec.decodeMessage(encoded); - if (message == null) { - expect(encoded, isNull); - expect(decoded, isNull); - } else { - expect(_deepEquals(message, decoded), isTrue); - final ByteData encodedAgain = codec.encodeMessage(decoded); - expect( - encodedAgain.buffer.asUint8List(), - orderedEquals(encoded.buffer.asUint8List()), - ); - } -} - -bool _deepEquals(dynamic valueA, dynamic valueB) { - if (valueA is TypedData) - return valueB is TypedData && _deepEqualsTypedData(valueA, valueB); - if (valueA is List) return valueB is List && _deepEqualsList(valueA, valueB); - if (valueA is Map) return valueB is Map && _deepEqualsMap(valueA, valueB); - if (valueA is double && valueA.isNaN) return valueB is double && valueB.isNaN; - if (valueA is FieldValue) { - return valueB is FieldValue && _deepEqualsFieldValue(valueA, valueB); - } - if (valueA is FieldPath) - return valueB is FieldPath && valueA.type == valueB.type; - return valueA == valueB; -} - -bool _deepEqualsTypedData(TypedData valueA, TypedData valueB) { - if (valueA is ByteData) { - return valueB is ByteData && - _deepEqualsList( - valueA.buffer.asUint8List(), valueB.buffer.asUint8List()); - } - if (valueA is Uint8List) - return valueB is Uint8List && _deepEqualsList(valueA, valueB); - if (valueA is Int32List) - return valueB is Int32List && _deepEqualsList(valueA, valueB); - if (valueA is Int64List) - return valueB is Int64List && _deepEqualsList(valueA, valueB); - if (valueA is Float64List) - return valueB is Float64List && _deepEqualsList(valueA, valueB); - throw 'Unexpected typed data: $valueA'; -} - -bool _deepEqualsList(List valueA, List valueB) { - if (valueA.length != valueB.length) return false; - for (int i = 0; i < valueA.length; i++) { - if (!_deepEquals(valueA[i], valueB[i])) return false; - } - return true; -} - -bool _deepEqualsMap( - Map valueA, Map valueB) { - if (valueA.length != valueB.length) return false; - for (final dynamic key in valueA.keys) { - if (!valueB.containsKey(key) || !_deepEquals(valueA[key], valueB[key])) - return false; - } - return true; -} - -bool _deepEqualsFieldValue(FieldValue valueA, FieldValue valueB) { - if (valueA.type != valueB.type) return false; - if (valueA.value == null) return valueB.value == null; - if (valueA.value is List) return _deepEqualsList(valueA.value, valueB.value); - if (valueA.value is Map) return _deepEqualsMap(valueA.value, valueB.value); - return valueA.value == valueB.value; -}