Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(firestore): serverTimestampBehaviour #9590

Merged
merged 8 commits into from Dec 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -139,9 +139,16 @@ private void writeQuerySnapshot(ByteArrayOutputStream stream, QuerySnapshot valu
List<Map<String, Object>> documents = new ArrayList<>();
List<SnapshotMetadata> metadatas = new ArrayList<>();

DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior =
FlutterFirebaseFirestorePlugin.serverTimestampBehaviorHashMap.get(value.hashCode());

for (DocumentSnapshot document : value.getDocuments()) {
paths.add(document.getReference().getPath());
documents.add(document.getData());
if (serverTimestampBehavior != null) {
documents.add(document.getData(serverTimestampBehavior));
} else {
documents.add(document.getData());
}
metadatas.add(document.getMetadata());
}

Expand All @@ -151,6 +158,7 @@ private void writeQuerySnapshot(ByteArrayOutputStream stream, QuerySnapshot valu
querySnapshotMap.put("documentChanges", value.getDocumentChanges());
querySnapshotMap.put("metadata", value.getMetadata());

FlutterFirebaseFirestorePlugin.serverTimestampBehaviorHashMap.remove(value.hashCode());
writeValue(stream, querySnapshotMap);
}

Expand Down Expand Up @@ -190,13 +198,20 @@ private void writeDocumentSnapshot(ByteArrayOutputStream stream, DocumentSnapsho
snapshotMap.put("path", value.getReference().getPath());

if (value.exists()) {
snapshotMap.put("data", value.getData());
DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior =
FlutterFirebaseFirestorePlugin.serverTimestampBehaviorHashMap.get(value.hashCode());
if (serverTimestampBehavior != null) {
snapshotMap.put("data", value.getData(serverTimestampBehavior));
} else {
snapshotMap.put("data", value.getData());
}
} else {
snapshotMap.put("data", null);
}

snapshotMap.put("metadata", value.getMetadata());

FlutterFirebaseFirestorePlugin.serverTimestampBehaviorHashMap.remove(value.hashCode());
writeValue(stream, snapshotMap);
}

Expand Down
Expand Up @@ -42,6 +42,7 @@
import io.flutter.plugins.firebase.firestore.streamhandler.SnapshotsInSyncStreamHandler;
import io.flutter.plugins.firebase.firestore.streamhandler.TransactionStreamHandler;
import io.flutter.plugins.firebase.firestore.utils.ExceptionConverter;
import io.flutter.plugins.firebase.firestore.utils.ServerTimestampBehaviorConverter;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -73,6 +74,10 @@ public class FlutterFirebaseFirestorePlugin
private final Map<String, StreamHandler> streamHandlers = new HashMap<>();
private final Map<String, OnTransactionResultListener> transactionHandlers = new HashMap<>();

// Used in the decoder to know which ServerTimestampBehavior to use
public static final Map<Integer, DocumentSnapshot.ServerTimestampBehavior>
serverTimestampBehaviorHashMap = new HashMap<>();

protected static FirebaseFirestore getCachedFirebaseFirestoreInstanceForKey(String key) {
synchronized (firestoreInstanceCache) {
return firestoreInstanceCache.get(key);
Expand Down Expand Up @@ -294,8 +299,10 @@ private Task<QuerySnapshot> queryGet(Map<String, Object> arguments) {
"An error occurred while parsing query arguments, see native logs for more information. Please report this issue."));
return;
}
final QuerySnapshot querySnapshot = Tasks.await(query.get(source));
saveTimestampBehavior(arguments, querySnapshot.hashCode());

taskCompletionSource.setResult(Tasks.await(query.get(source)));
taskCompletionSource.setResult(querySnapshot);
} catch (Exception e) {
taskCompletionSource.setException(e);
}
Expand All @@ -314,7 +321,10 @@ private Task<DocumentSnapshot> documentGet(Map<String, Object> arguments) {
DocumentReference documentReference =
(DocumentReference) Objects.requireNonNull(arguments.get("reference"));

taskCompletionSource.setResult(Tasks.await(documentReference.get(source)));
final DocumentSnapshot documentSnapshot = Tasks.await(documentReference.get(source));
saveTimestampBehavior(arguments, documentSnapshot.hashCode());

taskCompletionSource.setResult(documentSnapshot);
} catch (Exception e) {
taskCompletionSource.setException(e);
}
Expand Down Expand Up @@ -343,7 +353,10 @@ private Task<QuerySnapshot> namedQueryGet(Map<String, Object> arguments) {
return;
}

taskCompletionSource.setResult(Tasks.await(query.get(source)));
final QuerySnapshot querySnapshot = Tasks.await(query.get(source));
saveTimestampBehavior(arguments, querySnapshot.hashCode());

taskCompletionSource.setResult(querySnapshot);
} catch (Exception e) {
taskCompletionSource.setException(e);
}
Expand All @@ -352,6 +365,14 @@ private Task<QuerySnapshot> namedQueryGet(Map<String, Object> arguments) {
return taskCompletionSource.getTask();
}

private void saveTimestampBehavior(Map<String, Object> arguments, int hashCode) {
String serverTimestampBehaviorString = (String) arguments.get("serverTimestampBehavior");
DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior =
ServerTimestampBehaviorConverter.toServerTimestampBehavior(serverTimestampBehaviorString);

serverTimestampBehaviorHashMap.put(hashCode, serverTimestampBehavior);
}

private Task<Void> documentSet(Map<String, Object> arguments) {
TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();

Expand Down
Expand Up @@ -8,12 +8,15 @@

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 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;

Expand All @@ -32,6 +35,9 @@ public void onListen(Object arguments, EventSink events) {
: 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(
Expand All @@ -49,6 +55,10 @@ public void onListen(Object arguments, EventSink events) {

onCancel(null);
} else {
if (querySnapshot != null) {
FlutterFirebaseFirestorePlugin.serverTimestampBehaviorHashMap.put(
querySnapshot.hashCode(), serverTimestampBehavior);
}
events.success(querySnapshot);
}
});
Expand Down
@@ -0,0 +1,28 @@
/*
* 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.utils;

import androidx.annotation.Nullable;
import com.google.firebase.firestore.DocumentSnapshot;

public class ServerTimestampBehaviorConverter {
public static DocumentSnapshot.ServerTimestampBehavior toServerTimestampBehavior(
@Nullable String serverTimestampBehavior) {
if (serverTimestampBehavior == null) {
return DocumentSnapshot.ServerTimestampBehavior.NONE;
}
switch (serverTimestampBehavior) {
case "estimate":
return DocumentSnapshot.ServerTimestampBehavior.ESTIMATE;
case "previous":
return DocumentSnapshot.ServerTimestampBehavior.PREVIOUS;
case "none":
default:
return DocumentSnapshot.ServerTimestampBehavior.NONE;
}
}
}
Expand Up @@ -145,6 +145,28 @@ void runQueryTests() {
expect(qs.metadata.isFromCache, isFalse);
});

test('uses [GetOptions] serverTimestampBehavior previous', () async {
CollectionReference<Map<String, dynamic>> collection =
await initializeTest('get');
QuerySnapshot<Map<String, dynamic>> qs = await collection.get(
const GetOptions(
serverTimestampBehavior: ServerTimestampBehavior.previous,
),
);
expect(qs, isA<QuerySnapshot<Map<String, dynamic>>>());
});

test('uses [GetOptions] serverTimestampBehavior estimate', () async {
CollectionReference<Map<String, dynamic>> collection =
await initializeTest('get');
QuerySnapshot<Map<String, dynamic>> qs = await collection.get(
const GetOptions(
serverTimestampBehavior: ServerTimestampBehavior.estimate,
),
);
expect(qs, isA<QuerySnapshot<Map<String, dynamic>>>());
});

test('throws a [FirebaseException]', () async {
CollectionReference<Map<String, dynamic>> collection =
firestore.collection('not-allowed');
Expand Down
Expand Up @@ -190,7 +190,12 @@ class _FilmListState extends State<FilmList> {
}

Future<void> _resetLikes() async {
final movies = await moviesRef.get();
final movies = await moviesRef.get(
const GetOptions(
serverTimestampBehavior: ServerTimestampBehavior.previous,
),
);

WriteBatch batch = FirebaseFirestore.instance.batch();

for (final movie in movies.docs) {
Expand Down
Expand Up @@ -60,6 +60,8 @@ - (NSString *)registerEventChannelWithPrefix:(NSString *)prefix
streamHandler:(NSObject<FlutterStreamHandler> *)handler;
@end

static NSMutableDictionary<NSNumber *, NSString *> *_serverTimestampMap;

@implementation FLTFirebaseFirestorePlugin {
NSMutableDictionary<NSString *, FlutterEventChannel *> *_eventChannels;
NSMutableDictionary<NSString *, NSObject<FlutterStreamHandler> *> *_streamHandlers;
Expand All @@ -69,6 +71,10 @@ @implementation FLTFirebaseFirestorePlugin {

FlutterStandardMethodCodec *_codec;

+ (NSMutableDictionary<NSNumber *, NSString *> *)serverTimestampMap {
return _serverTimestampMap;
}

+ (void)initialize {
_codec =
[FlutterStandardMethodCodec codecWithReaderWriter:[FLTFirebaseFirestoreReaderWriter new]];
Expand Down Expand Up @@ -98,6 +104,7 @@ - (instancetype)init:(NSObject<FlutterBinaryMessenger> *)messenger {
_eventChannels = [NSMutableDictionary dictionary];
_streamHandlers = [NSMutableDictionary dictionary];
_transactionHandlers = [NSMutableDictionary dictionary];
_serverTimestampMap = [NSMutableDictionary dictionary];
}
return self;
}
Expand Down Expand Up @@ -457,10 +464,12 @@ - (void)documentDelete:(id)arguments withMethodCallResult:(FLTFirebaseMethodCall
- (void)documentGet:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result {
FIRDocumentReference *document = arguments[@"reference"];
FIRFirestoreSource source = [FLTFirebaseFirestoreUtils FIRFirestoreSourceFromArguments:arguments];
NSString *serverTimestampBehaviorString = arguments[@"serverTimestampBehavior"];
id completion = ^(FIRDocumentSnapshot *_Nullable snapshot, NSError *_Nullable error) {
if (error != nil) {
result.error(nil, nil, nil, error);
} else {
[_serverTimestampMap setObject:serverTimestampBehaviorString forKey:@([snapshot hash])];
result.success(snapshot);
}
};
Expand All @@ -479,12 +488,16 @@ - (void)queryGet:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult
return;
}

NSString *serverTimestampBehaviorString = arguments[@"serverTimestampBehavior"];

FIRFirestoreSource source = [FLTFirebaseFirestoreUtils FIRFirestoreSourceFromArguments:arguments];
[query getDocumentsWithSource:source
completion:^(FIRQuerySnapshot *_Nullable snapshot, NSError *_Nullable error) {
if (error != nil) {
result.error(nil, nil, nil, error);
} else {
[_serverTimestampMap setObject:serverTimestampBehaviorString
forKey:@([snapshot hash])];
result.success(snapshot);
}
}];
Expand All @@ -495,6 +508,7 @@ - (void)namedQueryGet:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallR
NSString *name = arguments[@"name"];

FIRFirestoreSource source = [FLTFirebaseFirestoreUtils FIRFirestoreSourceFromArguments:arguments];
NSString *serverTimestampBehaviorString = arguments[@"serverTimestampBehavior"];

[firestore getQueryNamed:name
completion:^(FIRQuery *_Nullable query) {
Expand All @@ -511,6 +525,9 @@ - (void)namedQueryGet:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallR
if (error != nil) {
result.error(nil, nil, nil, error);
} else {
[_serverTimestampMap
setObject:serverTimestampBehaviorString
forKey:@([snapshot hash])];
result.success(snapshot);
}
}];
Expand Down
Expand Up @@ -7,6 +7,7 @@

#import "Private/FLTFirebaseFirestoreUtils.h"
#import "Private/FLTFirebaseFirestoreWriter.h"
#import "Public/FLTFirebaseFirestorePlugin.h"

@implementation FLTFirebaseFirestoreWriter : FlutterStandardWriter
- (void)writeValue:(id)value {
Expand Down Expand Up @@ -133,10 +134,32 @@ - (NSDictionary *)FIRDocumentChange:(FIRDocumentChange *)documentChange {
};
}

- (FIRServerTimestampBehavior)toServerTimestampBehavior:(NSString *)serverTimestampBehavior {
if (serverTimestampBehavior == nil) {
return FIRServerTimestampBehaviorNone;
}

if ([serverTimestampBehavior isEqualToString:@"estimate"]) {
return FIRServerTimestampBehaviorEstimate;
} else if ([serverTimestampBehavior isEqualToString:@"previous"]) {
return FIRServerTimestampBehaviorPrevious;
} else {
return FIRServerTimestampBehaviorNone;
}
}

- (NSDictionary *)FIRDocumentSnapshot:(FIRDocumentSnapshot *)documentSnapshot {
FIRServerTimestampBehavior serverTimestampBehavior =
[self toServerTimestampBehavior:FLTFirebaseFirestorePlugin
.serverTimestampMap[@([documentSnapshot hash])]];

[FLTFirebaseFirestorePlugin.serverTimestampMap removeObjectForKey:@([documentSnapshot hash])];

return @{
@"path" : documentSnapshot.reference.path,
@"data" : documentSnapshot.exists ? (id)documentSnapshot.data : [NSNull null],
@"data" : documentSnapshot.exists
? (id)[documentSnapshot dataWithServerTimestampBehavior:serverTimestampBehavior]
: [NSNull null],
@"metadata" : documentSnapshot.metadata,
};
}
Expand Down Expand Up @@ -168,10 +191,15 @@ - (NSDictionary *)FIRQuerySnapshot:(FIRQuerySnapshot *)querySnapshot {
NSMutableArray *paths = [NSMutableArray array];
NSMutableArray *documents = [NSMutableArray array];
NSMutableArray *metadatas = [NSMutableArray array];
FIRServerTimestampBehavior serverTimestampBehavior =
[self toServerTimestampBehavior:FLTFirebaseFirestorePlugin
.serverTimestampMap[@([querySnapshot hash])]];

[FLTFirebaseFirestorePlugin.serverTimestampMap removeObjectForKey:@([querySnapshot hash])];

for (FIRDocumentSnapshot *document in querySnapshot.documents) {
[paths addObject:document.reference.path];
[documents addObject:document.data];
[documents addObject:[document dataWithServerTimestampBehavior:serverTimestampBehavior]];
[metadatas addObject:document.metadata];
}

Expand Down
Expand Up @@ -13,4 +13,5 @@
#import <firebase_core/FLTFirebasePlugin.h>

@interface FLTFirebaseFirestorePlugin : FLTFirebasePlugin <FlutterPlugin, FLTFirebasePlugin>
+ (NSMutableDictionary<NSNumber *, NSString *> *)serverTimestampMap;
@end
Expand Up @@ -25,6 +25,7 @@ export 'package:cloud_firestore_platform_interface/cloud_firestore_platform_inte
Timestamp,
Source,
GetOptions,
ServerTimestampBehavior,
SetOptions,
DocumentChangeType,
PersistenceSettings,
Expand Down