diff --git a/packages/cloud_firestore/cloud_firestore/android/build.gradle b/packages/cloud_firestore/cloud_firestore/android/build.gradle index cc975db8ed5c..2fd22bc58fcf 100755 --- a/packages/cloud_firestore/cloud_firestore/android/build.gradle +++ b/packages/cloud_firestore/cloud_firestore/android/build.gradle @@ -50,7 +50,8 @@ android { dependencies { api firebaseCoreProject implementation platform("com.google.firebase:firebase-bom:${getRootProjectExtOrCoreProperty("FirebaseSDKVersion", firebaseCoreProject)}") - implementation 'com.google.firebase:firebase-firestore' +// implementation 'com.google.firebase:firebase-firestore' + implementation 'com.google.firebase:firebase-firestore:24.6.1-a' implementation 'androidx.annotation:annotation:1.2.0' } } diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestoreMessageCodec.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestoreMessageCodec.java index 0138968592f4..a9adf24b950b 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestoreMessageCodec.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestoreMessageCodec.java @@ -22,6 +22,7 @@ import com.google.firebase.firestore.QuerySnapshot; import com.google.firebase.firestore.SnapshotMetadata; import io.flutter.plugin.common.StandardMessageCodec; +import io.flutter.plugins.firebase.firestore.streamhandler.QuerySnapshotWrapper; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -74,6 +75,8 @@ protected void writeValue(ByteArrayOutputStream stream, Object value) { writeValue(stream, ((DocumentReference) value).getPath()); } else if (value instanceof DocumentSnapshot) { writeDocumentSnapshot(stream, (DocumentSnapshot) value); + } else if (value instanceof QuerySnapshotWrapper) { + writeQuerySnapshotWrapper(stream, (QuerySnapshotWrapper) value); } else if (value instanceof QuerySnapshot) { writeQuerySnapshot(stream, (QuerySnapshot) value); } else if (value instanceof DocumentChange) { @@ -163,6 +166,15 @@ private void writeQuerySnapshot(ByteArrayOutputStream stream, QuerySnapshot valu writeValue(stream, querySnapshotMap); } + private void writeQuerySnapshotWrapper(ByteArrayOutputStream stream, QuerySnapshotWrapper value) { + Map querySnapshotChangesMap = new HashMap<>(); + + querySnapshotChangesMap.put("documentChanges", value.getDocumentChanges()); + querySnapshotChangesMap.put("metadata", value.getMetadata()); + + writeValue(stream, querySnapshotChangesMap); + } + private void writeLoadBundleTaskProgress( ByteArrayOutputStream stream, LoadBundleTaskProgress snapshot) { Map snapshotMap = new HashMap<>(); diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java index 9bc4bb89d0a7..c8b3acd7dcef 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/FlutterFirebaseFirestorePlugin.java @@ -39,6 +39,7 @@ import io.flutter.plugins.firebase.firestore.streamhandler.LoadBundleStreamHandler; import io.flutter.plugins.firebase.firestore.streamhandler.OnTransactionResultListener; import io.flutter.plugins.firebase.firestore.streamhandler.QuerySnapshotsStreamHandler; +import io.flutter.plugins.firebase.firestore.streamhandler.QuerySnapshotChangesStreamHandler; import io.flutter.plugins.firebase.firestore.streamhandler.SnapshotsInSyncStreamHandler; import io.flutter.plugins.firebase.firestore.streamhandler.TransactionStreamHandler; import io.flutter.plugins.firebase.firestore.utils.ExceptionConverter; @@ -184,6 +185,42 @@ private Task enableNetwork(Map arguments) { return taskCompletionSource.getTask(); } + private Task enableDebugging(Map arguments) { + TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); + + cachedThreadPool.execute( + () -> { + try { + FirebaseFirestore firestore = + (FirebaseFirestore) Objects.requireNonNull(arguments.get("firestore")); + firestore.setLoggingEnabled(true); + taskCompletionSource.setResult(null); + } catch (Exception e) { + taskCompletionSource.setException(e); + } + }); + + return taskCompletionSource.getTask(); + } + + private Task disableDebugging(Map arguments) { + TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); + + cachedThreadPool.execute( + () -> { + try { + FirebaseFirestore firestore = + (FirebaseFirestore) Objects.requireNonNull(arguments.get("firestore")); + firestore.setLoggingEnabled(false); + taskCompletionSource.setResult(null); + } catch (Exception e) { + taskCompletionSource.setException(e); + } + }); + + return taskCompletionSource.getTask(); + } + private Task transactionGet(Map arguments) { TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); @@ -582,6 +619,12 @@ public void onMethodCall(MethodCall call, @NonNull final MethodChannel.Result re case "Firestore#enableNetwork": methodCallTask = enableNetwork(call.arguments()); break; + case "Firestore#enableDebugging": + methodCallTask = enableDebugging(call.arguments()); + break; + case "Firestore#disableDebugging": + methodCallTask = disableDebugging(call.arguments()); + break; case "Transaction#get": methodCallTask = transactionGet(call.arguments()); break; @@ -610,6 +653,11 @@ public void onMethodCall(MethodCall call, @NonNull final MethodChannel.Result re registerEventChannel( METHOD_CHANNEL_NAME + "/query", new QuerySnapshotsStreamHandler())); return; + case "Query#snapshotChanges": + result.success( + registerEventChannel( + METHOD_CHANNEL_NAME + "/query", new QuerySnapshotChangesStreamHandler())); + return; case "DocumentReference#snapshots": result.success( registerEventChannel( diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/streamhandler/QuerySnapshotChangesStreamHandler.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/streamhandler/QuerySnapshotChangesStreamHandler.java new file mode 100644 index 000000000000..50711d8f848f --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/streamhandler/QuerySnapshotChangesStreamHandler.java @@ -0,0 +1,82 @@ +/* + * Copyright 2022, the Chromium project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +package io.flutter.plugins.firebase.firestore.streamhandler; + +import static io.flutter.plugins.firebase.firestore.FlutterFirebaseFirestorePlugin.DEFAULT_ERROR_CODE; + +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.ListenerRegistration; +import com.google.firebase.firestore.MetadataChanges; +import com.google.firebase.firestore.Query; +import com.google.firebase.firestore.QuerySnapshot; +import io.flutter.plugin.common.EventChannel.EventSink; +import io.flutter.plugin.common.EventChannel.StreamHandler; +import io.flutter.plugins.firebase.firestore.FlutterFirebaseFirestorePlugin; +import io.flutter.plugins.firebase.firestore.utils.ExceptionConverter; +import io.flutter.plugins.firebase.firestore.utils.ServerTimestampBehaviorConverter; +import java.util.Map; +import java.util.Objects; +import android.util.Log; + +public class QuerySnapshotChangesStreamHandler implements StreamHandler { + + ListenerRegistration listenerRegistration; + + @Override + public void onListen(Object arguments, EventSink events) { + @SuppressWarnings("unchecked") + Map argumentsMap = (Map) arguments; + + MetadataChanges metadataChanges = + (Boolean) Objects.requireNonNull(argumentsMap.get("includeMetadataChanges")) + ? MetadataChanges.INCLUDE + : MetadataChanges.EXCLUDE; + + Query query = (Query) argumentsMap.get("query"); + String serverTimestampBehaviorString = (String) argumentsMap.get("serverTimestampBehavior"); + DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior = + ServerTimestampBehaviorConverter.toServerTimestampBehavior(serverTimestampBehaviorString); + + if (query == null) { + throw new IllegalArgumentException( + "An error occurred while parsing query arguments, see native logs for more information. Please report this issue."); + } + + listenerRegistration = + query.addSnapshotListener( + metadataChanges, + (querySnapshot, exception) -> { + if (exception != null) { + Map exceptionDetails = ExceptionConverter.createDetails(exception); + events.error(DEFAULT_ERROR_CODE, exception.getMessage(), exceptionDetails); + events.endOfStream(); + + Log.e( + "QuerySnapshotChangesStreamHandler", + "OnListen exception: " + exception.getMessage()); + + onCancel(null); + } else { + if (querySnapshot != null) { + FlutterFirebaseFirestorePlugin.serverTimestampBehaviorHashMap.put( + querySnapshot.hashCode(), serverTimestampBehavior); + } + + QuerySnapshotWrapper wrappedQuerySnapshot = new QuerySnapshotWrapper(querySnapshot); + events.success((QuerySnapshotWrapper) wrappedQuerySnapshot); + } + }); + } + + @Override + public void onCancel(Object arguments) { + if (listenerRegistration != null) { + listenerRegistration.remove(); + listenerRegistration = null; + } + } +} diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/streamhandler/QuerySnapshotWrapper.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/streamhandler/QuerySnapshotWrapper.java new file mode 100644 index 000000000000..3cd040c6bb2a --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/streamhandler/QuerySnapshotWrapper.java @@ -0,0 +1,145 @@ +// Wrapper for com.google.firebase.firestore.QuerySnapshot, since it does +// not have a public constructor at this time. + +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package io.flutter.plugins.firebase.firestore.streamhandler; + +import com.google.firebase.firestore.QuerySnapshot; +import static com.google.firebase.firestore.util.Preconditions.checkNotNull; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.firebase.firestore.DocumentChange; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.MetadataChanges; +import com.google.firebase.firestore.Query; +import com.google.firebase.firestore.QueryDocumentSnapshot; +import com.google.firebase.firestore.SnapshotMetadata; +import com.google.firebase.firestore.core.ViewSnapshot; +import com.google.firebase.firestore.model.Document; + +import java.util.Iterator; +import java.util.List; + +public class QuerySnapshotWrapper { + private final QuerySnapshot querySnapshot; + + public QuerySnapshotWrapper(QuerySnapshot querySnapshot) { + this.querySnapshot = checkNotNull(querySnapshot); + } + + @NonNull + public Query getQuery() { + return querySnapshot.getQuery(); + } + + /** @return The metadata for this query snapshot. */ + @NonNull + public SnapshotMetadata getMetadata() { + return querySnapshot.getMetadata(); + } + + /** + * Returns the list of documents that changed since the last snapshot. If it's the first snapshot + * all documents will be in the list as added changes. + * + *

Documents with changes only to their metadata will not be included. + * + * @return The list of document changes since the last snapshot. + */ + @NonNull + public List getDocumentChanges() { + return querySnapshot.getDocumentChanges(); + } + + /** + * Returns the list of documents that changed since the last snapshot. If it's the first snapshot + * all documents will be in the list as added changes. + * + * @param metadataChanges Indicates whether metadata-only changes (i.e. only {@code + * DocumentSnapshot.getMetadata()} changed) should be included. + * @return The list of document changes since the last snapshot. + */ + @NonNull + public List getDocumentChanges(@NonNull MetadataChanges metadataChanges) { + + return querySnapshot.getDocumentChanges(metadataChanges); + } + + /** + * Returns the documents in this {@code QuerySnapshot} as a List in order of the query. + * + * @return The list of documents. + */ + @NonNull + public List getDocuments() { + + return querySnapshot.getDocuments(); + } + + /** Returns true if there are no documents in the {@code QuerySnapshot}. */ + public boolean isEmpty() { + return querySnapshot.isEmpty(); + } + + /** Returns the number of documents in the {@code QuerySnapshot}. */ + public int size() { + return querySnapshot.size(); + } + + @NonNull + public Iterator iterator() { + return querySnapshot.iterator(); + } + + /** + * Returns the contents of the documents in the {@code QuerySnapshot}, converted to the provided + * class, as a list. + * + * @param clazz The POJO type used to convert the documents in the list. + */ + @NonNull + public List toObjects(@NonNull Class clazz) { + return querySnapshot.toObjects(clazz); + } + + /** + * Returns the contents of the documents in the {@code QuerySnapshot}, converted to the provided + * class, as a list. + * + * @param clazz The POJO type used to convert the documents in the list. + * @param serverTimestampBehavior Configures the behavior for server timestamps that have not yet + * been set to their final value. + */ + @NonNull + public List toObjects( + @NonNull Class clazz, + @NonNull DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior) { + + return querySnapshot.toObjects(clazz, serverTimestampBehavior); + } + + @Override + public boolean equals(@Nullable Object obj) { + return querySnapshot.equals(obj); + } + + @Override + public int hashCode() { + return querySnapshot.hashCode(); + } + +} diff --git a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/streamhandler/QuerySnapshotsStreamHandler.java b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/streamhandler/QuerySnapshotsStreamHandler.java index e9f217382a7d..23bd6d0c908d 100644 --- a/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/streamhandler/QuerySnapshotsStreamHandler.java +++ b/packages/cloud_firestore/cloud_firestore/android/src/main/java/io/flutter/plugins/firebase/firestore/streamhandler/QuerySnapshotsStreamHandler.java @@ -19,6 +19,7 @@ import io.flutter.plugins.firebase.firestore.utils.ServerTimestampBehaviorConverter; import java.util.Map; import java.util.Objects; +import android.util.Log; public class QuerySnapshotsStreamHandler implements StreamHandler { @@ -53,12 +54,17 @@ public void onListen(Object arguments, EventSink events) { events.error(DEFAULT_ERROR_CODE, exception.getMessage(), exceptionDetails); events.endOfStream(); + Log.e( + "QuerySnapshotsStreamHandler", + "OnListen exception: " + exception.getMessage()); + onCancel(null); } else { if (querySnapshot != null) { FlutterFirebaseFirestorePlugin.serverTimestampBehaviorHashMap.put( querySnapshot.hashCode(), serverTimestampBehavior); } + events.success(querySnapshot); } }); diff --git a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart index e03b4cb35374..2730e024edcd 100755 --- a/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/cloud_firestore.dart @@ -55,6 +55,7 @@ part 'src/load_bundle_task_snapshot.dart'; part 'src/query.dart'; part 'src/query_document_snapshot.dart'; part 'src/query_snapshot.dart'; +part 'src/query_snapshot_changes.dart'; part 'src/snapshot_metadata.dart'; part 'src/transaction.dart'; part 'src/utils/codec_utility.dart'; diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart index cbba0d660c4b..615779f8dcff 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/firestore.dart @@ -206,6 +206,16 @@ class FirebaseFirestore extends FirebasePluginPlatform { return _delegate.enableNetwork(); } + /// Enables debugging for this instance. + Future enableDebugging() { + return _delegate.enableDebugging(); + } + + /// Instructs [FirebaseFirestore] to disable the debugging for the instance. + Future disableDebugging() { + return _delegate.disableDebugging(); + } + /// Returns a [Stream] which is called each time all of the active listeners /// have been synchronized. Stream snapshotsInSync() { diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/query.dart b/packages/cloud_firestore/cloud_firestore/lib/src/query.dart index de94076eb93a..b05f6bad9314 100644 --- a/packages/cloud_firestore/cloud_firestore/lib/src/query.dart +++ b/packages/cloud_firestore/cloud_firestore/lib/src/query.dart @@ -86,6 +86,11 @@ abstract class Query { /// Notifies of query results at this location. Stream> snapshots({bool includeMetadataChanges = false}); + /// Notifies of query changes results at this location. + Stream> snapshotChanges({ + bool includeMetadataChanges = false, + }); + /// Creates and returns a new [Query] that's additionally sorted by the specified /// [field]. /// The field may be a [String] representing a single field name or a [FieldPath]. @@ -431,6 +436,16 @@ class _JsonQuery implements Query> { .map((item) => _JsonQuerySnapshot(firestore, item)); } + /// Notifies of query results at this location. + @override + Stream>> snapshotChanges({ + bool includeMetadataChanges = false, + }) { + return _delegate + .snapshotChanges(includeMetadataChanges: includeMetadataChanges) + .map((item) => _JsonQuerySnapshotChanges(firestore, item)); + } + /// Creates and returns a new [Query] that's additionally sorted by the specified /// [field]. /// The field may be a [String] representing a single field name or a [FieldPath]. @@ -893,6 +908,20 @@ class _WithConverterQuery implements Query { ); } + @override + Stream> snapshotChanges( + {bool includeMetadataChanges = false,}) { + return _originalQuery + .snapshotChanges(includeMetadataChanges: includeMetadataChanges) + .map( + (snapshot) => _WithConverterQuerySnapshotChanges( + snapshot, + _fromFirestore, + _toFirestore, + ), + ); + } + @override Query endAt(Iterable values) { return _mapQuery(_originalQuery.endAt(values)); diff --git a/packages/cloud_firestore/cloud_firestore/lib/src/query_snapshot_changes.dart b/packages/cloud_firestore/cloud_firestore/lib/src/query_snapshot_changes.dart new file mode 100644 index 000000000000..9919299c8dfb --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore/lib/src/query_snapshot_changes.dart @@ -0,0 +1,78 @@ +// Copyright 2020, 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; + +/// Contains the results of a query. +/// It can contain zero or more [DocumentSnapshot] objects. +abstract class QuerySnapshotChanges { + /// 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. + List> get docChanges; + + /// Returns the [SnapshotMetadata] for this snapshot. + SnapshotMetadata get metadata; + + /// Returns the size (number of documents) of this snapshot. + int get size; +} + +/// Contains the results of a query. +/// It can contain zero or more [DocumentSnapshot] objects. +class _JsonQuerySnapshotChanges + implements QuerySnapshotChanges> { + _JsonQuerySnapshotChanges(this._firestore, this._delegate) { + QuerySnapshotChangesPlatform.verify(_delegate); + } + + final FirebaseFirestore _firestore; + final QuerySnapshotChangesPlatform _delegate; + + @override + List>> get docChanges { + return _delegate.docChanges.map((documentDelegate) { + return _JsonDocumentChange(_firestore, documentDelegate); + }).toList(); + } + + @override + SnapshotMetadata get metadata => SnapshotMetadata._(_delegate.metadata); + + @override + int get size => _delegate.size; +} + +/// Contains the results of a query. +/// It can contain zero or more [DocumentSnapshot] objects. +class _WithConverterQuerySnapshotChanges + implements QuerySnapshotChanges { + _WithConverterQuerySnapshotChanges( + this._originalQuerySnapshotChanges, + this._fromFirestore, + this._toFirestore, + ); + + final QuerySnapshotChanges> + _originalQuerySnapshotChanges; + final FromFirestore _fromFirestore; + final ToFirestore _toFirestore; + + @override + List> get docChanges { + return [ + for (final change in _originalQuerySnapshotChanges.docChanges) + _WithConverterDocumentChange( + change, + _fromFirestore, + _toFirestore, + ), + ]; + } + + @override + SnapshotMetadata get metadata => _originalQuerySnapshotChanges.metadata; + + @override + int get size => _originalQuerySnapshotChanges.size; +} diff --git a/packages/cloud_firestore/cloud_firestore/pubspec.yaml b/packages/cloud_firestore/cloud_firestore/pubspec.yaml index ca8d58be842d..4e0a0c6887a5 100755 --- a/packages/cloud_firestore/cloud_firestore/pubspec.yaml +++ b/packages/cloud_firestore/cloud_firestore/pubspec.yaml @@ -15,11 +15,22 @@ environment: flutter: ">=1.12.13+hotfix.5" dependencies: - cloud_firestore_platform_interface: ^5.12.1 - cloud_firestore_web: ^3.4.2 + cloud_firestore_platform_interface: + git: + url: https://github.com/bswhite1/flutterfire.git + path: packages/cloud_firestore/cloud_firestore_platform_interface + ref: snapshot_changes_git_paths + + cloud_firestore_web: + git: + url: https://github.com/bswhite1/flutterfire.git + path: packages/cloud_firestore/cloud_firestore_web + ref: snapshot_changes_git_paths + collection: ^1.0.0 firebase_core: ^2.10.0 firebase_core_platform_interface: ^4.6.0 + flutter: sdk: flutter meta: ^1.3.0 diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart index 74c1e03601cb..2354e1a68495 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/cloud_firestore_platform_interface.dart @@ -31,6 +31,7 @@ export 'src/platform_interface/platform_interface_load_bundle_task.dart'; export 'src/platform_interface/platform_interface_load_bundle_task_snapshot.dart'; export 'src/platform_interface/platform_interface_query.dart'; export 'src/platform_interface/platform_interface_query_snapshot.dart'; +export 'src/platform_interface/platform_interface_query_snapshot_changes.dart'; export 'src/platform_interface/platform_interface_transaction.dart'; export 'src/platform_interface/platform_interface_write_batch.dart'; export 'src/platform_interface/utils/load_bundle_task_state.dart'; diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart index 89cfadef9ea8..81fef97c9e6d 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_firestore.dart @@ -192,6 +192,30 @@ class MethodChannelFirebaseFirestore extends FirebaseFirestorePlatform { } } + @override + Future enableDebugging() async { + try { + await channel + .invokeMethod('Firestore#enableDebugging', { + 'firestore': this, + }); + } catch (e, stack) { + convertPlatformException(e, stack); + } + } + + @override + Future disableDebugging() async { + try { + await channel + .invokeMethod('Firestore#disableDebugging', { + 'firestore': this, + }); + } catch (e, stack) { + convertPlatformException(e, stack); + } + } + @override Stream snapshotsInSync() { StreamSubscription? snapshotStreamSubscription; diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query.dart index 466d9d475bfc..5afff72a4ded 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query.dart @@ -14,6 +14,7 @@ import 'package:flutter/services.dart'; import 'method_channel_aggregate_query.dart'; import 'method_channel_firestore.dart'; import 'method_channel_query_snapshot.dart'; +import 'method_channel_query_snapshot_changes.dart'; import 'utils/exception.dart'; import 'utils/source.dart'; @@ -176,6 +177,51 @@ class MethodChannelQuery extends QueryPlatform { return controller.stream; } + @override + Stream snapshotChanges({ + bool includeMetadataChanges = false, + ServerTimestampBehavior serverTimestampBehavior = + ServerTimestampBehavior.none, + }) { + // It's fine to let the StreamController be garbage collected once all the + // subscribers have cancelled; this analyzer warning is safe to ignore. + late StreamController + controller; // ignore: close_sinks + + StreamSubscription? snapshotChangesStreamSubscription; + + controller = StreamController.broadcast( + onListen: () async { + final observerId = await MethodChannelFirebaseFirestore.channel + .invokeMethod('Query#snapshotChanges'); + + snapshotChangesStreamSubscription = + MethodChannelFirebaseFirestore.querySnapshotChannel(observerId!) + .receiveGuardedBroadcastStream( + arguments: { + 'query': this, + 'includeMetadataChanges': includeMetadataChanges, + 'serverTimestampBehavior': getServerTimestampBehaviorString( + serverTimestampBehavior, + ), + }, + onError: convertPlatformException, + ).listen( + (snapshotChanges) { + controller.add( + MethodChannelQuerySnapshotChanges(firestore, snapshotChanges)); + }, + onError: controller.addError, + ); + }, + onCancel: () { + snapshotChangesStreamSubscription?.cancel(); + }, + ); + + return controller.stream; + } + @override QueryPlatform orderBy(Iterable> orders) { return _copyWithParameters({'orderBy': orders}); diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query_snapshot_changes.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query_snapshot_changes.dart new file mode 100644 index 000000000000..3bb394c538fb --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_query_snapshot_changes.dart @@ -0,0 +1,28 @@ +// ignore_for_file: require_trailing_commas +// 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 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; + +import 'method_channel_document_change.dart'; + +/// An implementation of [QuerySnapshotChangesPlatform] that uses [MethodChannel] to +/// communicate with Firebase plugins. +class MethodChannelQuerySnapshotChanges extends QuerySnapshotChangesPlatform { + /// Creates a [MethodChannelQuerySnapshotChanges] from the given [data] + MethodChannelQuerySnapshotChanges( + FirebaseFirestorePlatform firestore, Map data) + : super( + List.generate( + data['documentChanges'].length, (int index) { + return MethodChannelDocumentChange( + firestore, + Map.from(data['documentChanges'][index]), + ); + }), + SnapshotMetadataPlatform( + data['metadata']['hasPendingWrites'], + data['metadata']['isFromCache'], + )); +} diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_write_batch.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_write_batch.dart index 37c6dd17156e..b0454e6c4722 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_write_batch.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/method_channel/method_channel_write_batch.dart @@ -42,15 +42,11 @@ class MethodChannelWriteBatch extends WriteBatchPlatform { return; } - try { - await MethodChannelFirebaseFirestore.channel - .invokeMethod('WriteBatch#commit', { - 'firestore': _firestore, - 'writes': _writes, - }); - } catch (e, stack) { - convertPlatformException(e, stack); - } + await MethodChannelFirebaseFirestore.channel + .invokeMethod('WriteBatch#commit', { + 'firestore': _firestore, + 'writes': _writes, + }).catchError(convertPlatformException); } @override diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart index 44a908989f70..17d2c1fbb1c5 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_firestore.dart @@ -114,6 +114,14 @@ abstract class FirebaseFirestorePlatform extends PlatformInterface { throw UnimplementedError('enableNetwork() is not implemented'); } + Future enableDebugging() { + throw UnimplementedError('enableDebugging() is not implemented'); + } + + Future disableDebugging() { + throw UnimplementedError('disableDebugging() is not implemented'); + } + /// Returns a [Stream] which is called each time all of the active listeners /// have been synchronised. Stream snapshotsInSync() { diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query.dart index 3db6b98b4efd..4817827e598c 100644 --- a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query.dart +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query.dart @@ -139,6 +139,13 @@ abstract class QueryPlatform extends PlatformInterface { throw UnimplementedError('snapshots() is not implemented'); } + /// Notifies of query change results at this location + Stream snapshotChanges({ + bool includeMetadataChanges = false, + }) { + throw UnimplementedError('snapshotChanges() is not implemented'); + } + /// Creates and returns a new [QueryPlatform] that's additionally sorted by the specified /// [field]. /// The field may be a [String] representing a single field name or a [FieldPath]. diff --git a/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query_snapshot_changes.dart b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query_snapshot_changes.dart new file mode 100644 index 000000000000..3c654568cb07 --- /dev/null +++ b/packages/cloud_firestore/cloud_firestore_platform_interface/lib/src/platform_interface/platform_interface_query_snapshot_changes.dart @@ -0,0 +1,42 @@ +// ignore_for_file: require_trailing_commas +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore_platform_interface/cloud_firestore_platform_interface.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +/// A interface that contains zero or more [DocumentSnapshotPlatform] objects +/// representing the results of a query. +/// +/// The documents can be accessed as a list by calling [docs()] and the number of documents +/// can be determined by calling [size()]. +class QuerySnapshotChangesPlatform extends PlatformInterface { + /// Create a [QuerySnapshotChangesPlatform] + QuerySnapshotChangesPlatform( + this.docChanges, + this.metadata, + ) : super(token: _token); + + static final Object _token = Object(); + + /// Throws an [AssertionError] if [instance] does not extend + /// [QuerySnapshotChangesPlatform]. + /// + /// This is used by the app-facing [QuerySnapshotChanges] to ensure that + /// the object in which it's going to delegate calls has been + /// constructed properly. + static void verify(QuerySnapshotChangesPlatform instance) { + PlatformInterface.verify(instance, _token); + } + + /// 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 docChanges; + + /// Metadata for the document + final SnapshotMetadataPlatform metadata; + + /// The number of documents with changes in this [QuerySnapshotChangesPlatform]. + int get size => docChanges.length; +} diff --git a/packages/cloud_firestore/cloud_firestore_web/pubspec.yaml b/packages/cloud_firestore/cloud_firestore_web/pubspec.yaml index ed340ea21d84..ea5556bed89d 100644 --- a/packages/cloud_firestore/cloud_firestore_web/pubspec.yaml +++ b/packages/cloud_firestore/cloud_firestore_web/pubspec.yaml @@ -11,7 +11,11 @@ environment: dependencies: _flutterfire_internals: ^1.1.1 - cloud_firestore_platform_interface: ^5.12.1 + cloud_firestore_platform_interface: + git: + url: https://github.com/bswhite1/flutterfire.git + path: packages/cloud_firestore/cloud_firestore_platform_interface + ref: snapshot_changes_git_paths collection: ^1.0.0 firebase_core: ^2.10.0 firebase_core_web: ^2.3.0