Skip to content

Commit

Permalink
feat(firestore): add support for listening snapshot from cache (#12585)
Browse files Browse the repository at this point in the history
* feat(firestore): add support for listening snapshot from cache

* web

* android

* android

* ios

* add tests

* fix ios

* android

* windows

* format

* add tests and document reference tests
  • Loading branch information
Lyokone authored Apr 12, 2024
1 parent 8966f48 commit f2cef8c
Show file tree
Hide file tree
Showing 38 changed files with 464 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,7 @@ public void querySnapshot(
@NonNull GeneratedAndroidFirebaseFirestore.PigeonQueryParameters parameters,
@NonNull GeneratedAndroidFirebaseFirestore.PigeonGetOptions options,
@NonNull Boolean includeMetadataChanges,
@NonNull GeneratedAndroidFirebaseFirestore.ListenSource source,
@NonNull GeneratedAndroidFirebaseFirestore.Result<String> result) {
Query query =
PigeonParser.parseQuery(getFirestoreFromPigeon(app), path, isCollectionGroup, parameters);
Expand All @@ -914,14 +915,16 @@ public void querySnapshot(
query,
includeMetadataChanges,
PigeonParser.parsePigeonServerTimestampBehavior(
options.getServerTimestampBehavior()))));
options.getServerTimestampBehavior()),
PigeonParser.parseListenSource(source))));
}

@Override
public void documentReferenceSnapshot(
@NonNull GeneratedAndroidFirebaseFirestore.FirestorePigeonFirebaseApp app,
@NonNull GeneratedAndroidFirebaseFirestore.DocumentReferenceRequest parameters,
@NonNull Boolean includeMetadataChanges,
@NonNull GeneratedAndroidFirebaseFirestore.ListenSource source,
@NonNull GeneratedAndroidFirebaseFirestore.Result<String> result) {
FirebaseFirestore firestore = getFirestoreFromPigeon(app);
DocumentReference documentReference =
Expand All @@ -935,6 +938,7 @@ public void documentReferenceSnapshot(
documentReference,
includeMetadataChanges,
PigeonParser.parsePigeonServerTimestampBehavior(
parameters.getServerTimestampBehavior()))));
parameters.getServerTimestampBehavior()),
PigeonParser.parseListenSource(source))));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,35 @@ private Source(final int index) {
}
}

/**
* The listener retrieves data and listens to updates from the local Firestore cache only. If the
* cache is empty, an empty snapshot will be returned. Snapshot events will be triggered on cache
* updates, like local mutations or load bundles.
*
* <p>Note that the data might be stale if the cache hasn't synchronized with recent server-side
* changes.
*/
public enum ListenSource {
/**
* The default behavior. The listener attempts to return initial snapshot from cache and
* retrieve up-to-date snapshots from the Firestore server. Snapshot events will be triggered on
* local mutations and server side updates.
*/
DEFAULT_SOURCE(0),
/**
* The listener retrieves data and listens to updates from the local Firestore cache only. If
* the cache is empty, an empty snapshot will be returned. Snapshot events will be triggered on
* cache updates, like local mutations or load bundles.
*/
CACHE(1);

final int index;

private ListenSource(final int index) {
this.index = index;
}
}

public enum ServerTimestampBehavior {
/** Return null for [FieldValue.serverTimestamp()] values that have not yet */
NONE(0),
Expand Down Expand Up @@ -1776,12 +1805,14 @@ void querySnapshot(
@NonNull PigeonQueryParameters parameters,
@NonNull PigeonGetOptions options,
@NonNull Boolean includeMetadataChanges,
@NonNull ListenSource source,
@NonNull Result<String> result);

void documentReferenceSnapshot(
@NonNull FirestorePigeonFirebaseApp app,
@NonNull DocumentReferenceRequest parameters,
@NonNull Boolean includeMetadataChanges,
@NonNull ListenSource source,
@NonNull Result<String> result);

/** The codec used by FirebaseFirestoreHostApi. */
Expand Down Expand Up @@ -2476,6 +2507,7 @@ public void error(Throwable error) {
PigeonQueryParameters parametersArg = (PigeonQueryParameters) args.get(3);
PigeonGetOptions optionsArg = (PigeonGetOptions) args.get(4);
Boolean includeMetadataChangesArg = (Boolean) args.get(5);
ListenSource sourceArg = ListenSource.values()[(int) args.get(6)];
Result<String> resultCallback =
new Result<String>() {
public void success(String result) {
Expand All @@ -2496,6 +2528,7 @@ public void error(Throwable error) {
parametersArg,
optionsArg,
includeMetadataChangesArg,
sourceArg,
resultCallback);
});
} else {
Expand All @@ -2516,6 +2549,7 @@ public void error(Throwable error) {
FirestorePigeonFirebaseApp appArg = (FirestorePigeonFirebaseApp) args.get(0);
DocumentReferenceRequest parametersArg = (DocumentReferenceRequest) args.get(1);
Boolean includeMetadataChangesArg = (Boolean) args.get(2);
ListenSource sourceArg = ListenSource.values()[(int) args.get(3)];
Result<String> resultCallback =
new Result<String>() {
public void success(String result) {
Expand All @@ -2530,7 +2564,7 @@ public void error(Throwable error) {
};

api.documentReferenceSnapshot(
appArg, parametersArg, includeMetadataChangesArg, resultCallback);
appArg, parametersArg, includeMetadataChangesArg, sourceArg, resultCallback);
});
} else {
channel.setMessageHandler(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.ListenSource;
import com.google.firebase.firestore.ListenerRegistration;
import com.google.firebase.firestore.MetadataChanges;
import com.google.firebase.firestore.SnapshotListenOptions;
import io.flutter.plugin.common.EventChannel.EventSink;
import io.flutter.plugin.common.EventChannel.StreamHandler;
import io.flutter.plugins.firebase.firestore.utils.ExceptionConverter;
Expand All @@ -27,24 +29,31 @@ public class DocumentSnapshotsStreamHandler implements StreamHandler {
MetadataChanges metadataChanges;

DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior;
ListenSource source;

public DocumentSnapshotsStreamHandler(
FirebaseFirestore firestore,
DocumentReference documentReference,
Boolean includeMetadataChanges,
DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior) {
DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior,
ListenSource source) {
this.firestore = firestore;
this.documentReference = documentReference;
this.metadataChanges =
includeMetadataChanges ? MetadataChanges.INCLUDE : MetadataChanges.EXCLUDE;
this.serverTimestampBehavior = serverTimestampBehavior;
this.source = source;
}

@Override
public void onListen(Object arguments, EventSink events) {
SnapshotListenOptions.Builder optionsBuilder = new SnapshotListenOptions.Builder();
optionsBuilder.setMetadataChanges(metadataChanges);
optionsBuilder.setSource(source);

listenerRegistration =
documentReference.addSnapshotListener(
metadataChanges,
optionsBuilder.build(),
(documentSnapshot, exception) -> {
if (exception != null) {
Map<String, String> exceptionDetails = ExceptionConverter.createDetails(exception);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@

import com.google.firebase.firestore.DocumentChange;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.ListenSource;
import com.google.firebase.firestore.ListenerRegistration;
import com.google.firebase.firestore.MetadataChanges;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.SnapshotListenOptions;
import io.flutter.plugin.common.EventChannel.EventSink;
import io.flutter.plugin.common.EventChannel.StreamHandler;
import io.flutter.plugins.firebase.firestore.utils.ExceptionConverter;
Expand All @@ -28,21 +30,29 @@ public class QuerySnapshotsStreamHandler implements StreamHandler {
MetadataChanges metadataChanges;
DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior;

ListenSource source;

public QuerySnapshotsStreamHandler(
Query query,
Boolean includeMetadataChanges,
DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior) {
DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior,
ListenSource source) {
this.query = query;
this.metadataChanges =
includeMetadataChanges ? MetadataChanges.INCLUDE : MetadataChanges.EXCLUDE;
this.serverTimestampBehavior = serverTimestampBehavior;
this.source = source;
}

@Override
public void onListen(Object arguments, EventSink events) {
SnapshotListenOptions.Builder optionsBuilder = new SnapshotListenOptions.Builder();
optionsBuilder.setMetadataChanges(metadataChanges);
optionsBuilder.setSource(source);

listenerRegistration =
query.addSnapshotListener(
metadataChanges,
optionsBuilder.build(),
(querySnapshot, exception) -> {
if (exception != null) {
Map<String, String> exceptionDetails = ExceptionConverter.createDetails(exception);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.google.firebase.firestore.FieldPath;
import com.google.firebase.firestore.Filter;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.ListenSource;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.Source;
import io.flutter.plugins.firebase.firestore.GeneratedAndroidFirebaseFirestore;
Expand Down Expand Up @@ -116,6 +117,18 @@ public static GeneratedAndroidFirebaseFirestore.DocumentChangeType toPigeonDocum
}
}

public static ListenSource parseListenSource(
GeneratedAndroidFirebaseFirestore.ListenSource source) {
switch (source) {
case DEFAULT_SOURCE:
return ListenSource.DEFAULT;
case CACHE:
return ListenSource.CACHE;
default:
throw new IllegalArgumentException("Unknown ListenSource value: " + source);
}
}

public static GeneratedAndroidFirebaseFirestore.PigeonDocumentSnapshot toPigeonDocumentSnapshot(
com.google.firebase.firestore.DocumentSnapshot documentSnapshot,
DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,29 @@ void runDocumentReferenceTests() {
});
});

testWidgets('listens to a single response from cache', (_) async {
DocumentReference<Map<String, dynamic>> document =
await initializeTest('document-snapshot');
Stream<DocumentSnapshot<Map<String, dynamic>>> stream =
document.snapshots(source: ListenSource.cache);
StreamSubscription<DocumentSnapshot<Map<String, dynamic>>>?
subscription;

subscription = stream.listen(
expectAsync1(
(DocumentSnapshot<Map<String, dynamic>> snapshot) {
expect(snapshot.exists, isFalse);
},
count: 1,
reason: 'Stream should only have been called once.',
),
);

addTearDown(() async {
await subscription?.cancel();
});
});

testWidgets('listens to multiple documents', (_) async {
DocumentReference<Map<String, dynamic>> doc1 =
await initializeTest('document-snapshot-1');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,32 @@ void runQueryTests() {
});
});

testWidgets('listens to a single response from cache', (_) async {
CollectionReference<Map<String, dynamic>> collection =
await initializeTest('get-single-cache');
await collection.add({'foo': 'bar'});
Stream<QuerySnapshot<Map<String, dynamic>>> stream =
collection.snapshots(source: ListenSource.cache);
StreamSubscription<QuerySnapshot<Map<String, dynamic>>>? subscription;

subscription = stream.listen(
expectAsync1(
(QuerySnapshot<Map<String, dynamic>> snapshot) {
expect(snapshot.docs.length, equals(1));
expect(snapshot.docs[0], isA<QueryDocumentSnapshot>());
QueryDocumentSnapshot<Map<String, dynamic>> documentSnapshot =
snapshot.docs[0];
expect(documentSnapshot.data()['foo'], equals('bar'));
},
count: 1,
reason: 'Stream should only have been called once.',
),
);
addTearDown(() async {
await subscription?.cancel();
});
});

testWidgets('listens to multiple queries', (_) async {
CollectionReference<Map<String, dynamic>> collection1 =
await initializeTest('document-snapshot-1');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
<string>12.0</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, '11.0'
platform :ios, '12.0'

require 'yaml'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
279EA8199A12C4F77765546D /* [CP] Embed Pods Frameworks */,
7C56E6EB2829AAB5C53203A3 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
Expand All @@ -159,7 +158,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
Expand Down Expand Up @@ -235,23 +234,6 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
7C56E6EB2829AAB5C53203A3 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
Expand Down Expand Up @@ -364,7 +346,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down Expand Up @@ -442,7 +424,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand Down Expand Up @@ -491,7 +473,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Loading

0 comments on commit f2cef8c

Please sign in to comment.