-
Notifications
You must be signed in to change notification settings - Fork 4k
🐛 [cloud_firestore] old data retrieved if get is called just after a transaction when a stream is active #10153
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
Comments
Thanks for the report @pldelattre
Is above an expected result you are looking for ? Or do you see any other output ? I am unable to verify this on web due to |
A bit more investigation:
Future<void> transactThenGet() async {
final docRef = FirebaseFirestore.instance.collection("test").doc("test");
var newPopulation;
await FirebaseFirestore.instance.runTransaction((transaction) async {
final snapshot = await transaction.get(docRef);
final prevPopulation = snapshot.get("population");
newPopulation = prevPopulation + 1;
transaction.update(docRef, {"population": newPopulation});
});
await Future.delayed(const Duration(milliseconds: 300)); // <-- WORKAROUND
Map<String, dynamic> updated = (await docRef.get()).data()!;
debugPrint('Expecting : ${newPopulation.toString()}');
debugPrint('Got : ${updated['population'].toString()}');
} |
@darshankawar
However if I reload the app and click only on 'Transact then Get', I get
It seems the problem is that the Firestore get action will retrieve the previous record until the stream has finished serving the new record. And that looks like a bug. The issue is also happening on Android on my side. Maybe you can try to reproduce on this platform ? |
Thanks for the feedback. Using above mentioned details and running on Android, seeing same behavior as reported. Keeping this issue open and labeling for team's attention on expected behavior. |
@Lyokone Thanks for working on this. You mention that your PR close the issue, however, your code seem to only update IOS files. |
Thanks for the precision. I was unable to reproduce on Android, I might have missed something I'll try again 👍 |
@Lyokone Have you tried on a firestore cloud instance ? It is is more difficult to reproduce with emulator. |
I updated to the latest versions :
And refactored the repro code to the bare minimum, everything in a single dart file, including the main() function. Also added the "const GetOptions(source: Source.server)" in the Get call : import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(MaterialApp(
title: 'Bug Repro',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const TestBugWidget()));
}
class TestBugWidget extends StatefulWidget {
const TestBugWidget({super.key});
@override
State<TestBugWidget> createState() => _TestBugWidgetState();
}
class _TestBugWidgetState extends State<TestBugWidget> {
StreamSubscription? _sub;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Bug repro')),
body: Column(children: [
ListTile(
title: const Text('Listen to the stream'),
onTap: streamListen,
),
ListTile(
title: const Text('Transact then Get'),
onTap: transactThenGet,
)
]),
);
}
void streamListen() {
debugPrint('*** User clicked on \'Listen to the stream\' ***');
_sub?.cancel();
debugPrint('Let\'s listen to test document!');
_sub = FirebaseFirestore.instance
.collection('test')
.doc('test')
.snapshots()
.listen((event) {
debugPrint('new test document has arrived');
});
}
Future<void> transactThenGet() async {
debugPrint('*** User clicked on \'Transact then Get\' ***');
final docRef = FirebaseFirestore.instance.collection("test").doc("test");
var newPopulation;
await FirebaseFirestore.instance.runTransaction((transaction) async {
final snapshot = await transaction.get(docRef);
final prevPopulation = snapshot.get("population");
newPopulation = prevPopulation + 1;
debugPrint('updating population to : ${newPopulation.toString()}');
transaction.update(docRef, {"population": newPopulation});
});
Map<String, dynamic> updated = (await docRef.get(const GetOptions(source: Source.server))).data()!;
debugPrint('Expecting : ${newPopulation.toString()}');
debugPrint('Got : ${updated['population'].toString()}');
}
} Unfortunately, it does not solve the issue for me. Here is what I get when running on web :
Here is what I get when running on android
We can notice that:
I also tried to compile web on another computer with a fresh install of flutter to see if result would be different. Unfortunately it is not. And we have also @darshankawar that confirmed he was able to reproduce the issue on Android. Another info that I didn't mention is that I am using EUR3 Firestore instance. Maybe it is worth trying to reproduce on that instance ? |
After a lot of different tests, I was able to also reproduce it in the JS SDK with the following code: import { initializeApp } from "firebase/app";
import {
doc,
getDoc,
getFirestore,
onSnapshot,
runTransaction,
} from "firebase/firestore";
var config = {
...
};
// Initialize Firebase
var app = initializeApp(config);
const db = getFirestore(app);
const test_doc = doc(db, "test_10153", "test");
const unsub = onSnapshot(test_doc, (doc) => {
console.log("Current data: ", doc.data());
});
for (let i = 0; i < 10; i++) {
var newPopulation;
await runTransaction(db, async (transaction) => {
const sfDoc = await transaction.get(test_doc);
if (!sfDoc.exists()) {
throw "Document does not exist!";
}
newPopulation = sfDoc.data().population + 1;
console.log("Updating to:", newPopulation);
transaction.update(test_doc, { population: newPopulation });
});
const data = await getDoc(test_doc);
console.log("Expected: ", newPopulation);
console.log("Actual: ", data.data().population);
console.log("---------");
}
unsub(); I'll post an issue to the JS SDK repository. |
Thanks for following up on this @Lyokone and for opening an issue on the JS SDK repository. |
@pldelattre I have opened the issue only on the JS SDK repository because I think this is not a bug but an expected behaviour (transaction taking some time to be applied on the backend properly). My guess is that the If this end up being a bug, I will open issues on other repositories |
@Lyokone |
@Lyokone your theory and my last comment gave me the idea of a new test :
In that context, I only see the issue in the first tab but not in the second tab. IMHO, this tends to prove that the issue is not with the backend (that would not be awaiting the transaction to be applied) but with the frontend. |
@Lyokone @pldelattre We're experiencing a similar issue. In our case, we run a transaction where we delete a document and remove the reference to it in another. The stream listening to the parent collection emits two events after the transaction has run. The second one includes both changes. The first one however, includes the removed reference but the deleted document is still in the snapshot. Isn't the idea of transactions that exactly this doesn't happen, e.g. that the changes are atomic? |
I'll close this as "will not fix" according to this response from a Googler: firebase/firebase-js-sdk#6915 (comment) |
Bug report
Describe the bug
Firestore doesn't retrieve the latest data when using 'get' on a document just after this document was modified through a transaction and a stream to listen on the same document is active
Steps to reproduce
Steps to reproduce the behavior:
Expected behavior
Content of the retrieved document in the 'Get' call should be updated but it is not !
This issue is not happening if we are not listening to the document OR if the document is not updated through a transaction.
Sample project
To reproduce the problem.
Additional context
cloud_firestore: ^4.0.5
firebase_core: ^2.2.0
I tested both on Android and Web. Issue happens in both cases.
The text was updated successfully, but these errors were encountered: