Describe the bug
App crash when adding new data. It doesn't print any error in flutter developement. But if you open android log it show:
java.lang.RuntimeException: An error occurred while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:318)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
at java.util.concurrent.FutureTask.run(FutureTask.java:242)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
Caused by: java.lang.AssertionError: INTERNAL ASSERTION FAILED: A transaction object cannot be used after its update callback has been invoked.
at com.google.firebase.firestore.util.Assert.fail(com.google.firebase:firebase-firestore@@21.3.0:46)
at com.google.firebase.firestore.util.Assert.hardAssert(com.google.firebase:firebase-firestore@@21.3.0:31)
at com.google.firebase.firestore.core.Transaction.ensureCommitNotCalled(com.google.firebase:firebase-firestore@@21.3.0:246)
at com.google.firebase.firestore.core.Transaction.lookup(com.google.firebase:firebase-firestore@@21.3.0:81)
at com.google.firebase.firestore.Transaction.getAsync(com.google.firebase:firebase-firestore@@21.3.0:191)
at com.google.firebase.firestore.Transaction.get(com.google.firebase:firebase-firestore@@21.3.0:228)
at io.flutter.plugins.firebase.cloudfirestore.CloudFirestorePlugin$5.doInBackground(CloudFirestorePlugin.java:569)
at io.flutter.plugins.firebase.cloudfirestore.CloudFirestorePlugin$5.doInBackground(CloudFirestorePlugin.java:564)
at android.os.AsyncTask$2.call(AsyncTask.java:304)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)聽
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)聽
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)聽
at java.lang.Thread.run(Thread.java:761)聽
To Reproduce
Steps to reproduce the behavior:
This is so occasional.
Expected behavior
App should not crash
Seems to same here. iOS is good. Only android appear.
2020-02-11 15:19:18.335 13830-14111/? E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #6
Process: live.effy.app.dev, PID: 13830
java.lang.RuntimeException: An error occurred while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:354)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
at java.util.concurrent.FutureTask.run(FutureTask.java:271)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
Caused by: java.lang.AssertionError: INTERNAL ASSERTION FAILED: A transaction object cannot be used after its update callback has been invoked.
at com.google.firebase.firestore.util.Assert.fail(com.google.firebase:firebase-firestore@@21.3.0:46)
at com.google.firebase.firestore.util.Assert.hardAssert(com.google.firebase:firebase-firestore@@21.3.0:31)
at com.google.firebase.firestore.core.Transaction.ensureCommitNotCalled(com.google.firebase:firebase-firestore@@21.3.0:246)
at com.google.firebase.firestore.core.Transaction.lookup(com.google.firebase:firebase-firestore@@21.3.0:81)
at com.google.firebase.firestore.Transaction.getAsync(com.google.firebase:firebase-firestore@@21.3.0:191)
at com.google.firebase.firestore.Transaction.get(com.google.firebase:firebase-firestore@@21.3.0:228)
at io.flutter.plugins.firebase.cloudfirestore.CloudFirestorePlugin$5.doInBackground(CloudFirestorePlugin.java:569)
at io.flutter.plugins.firebase.cloudfirestore.CloudFirestorePlugin$5.doInBackground(CloudFirestorePlugin.java:564)
at android.os.AsyncTask$2.call(AsyncTask.java:333)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)聽
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)聽
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)聽
at java.lang.Thread.run(Thread.java:764)聽
This happens to me when running multiple transactions.
This also happens to me when running the same transaction on two android devices
I see this as well - and unfortunately including catchError() on runTransaction does not seem to protect against this crash.
Hi, anybody found a solution for this ?
Experiencing the same issue (see #1216)
Despite the title of this issue, it happens on both android and iOS
Tracked this down to Firebase offline use and Transactions.
This will happen when you are using either BATCHWRITES or runTransaction in ANDROID and you have no INTERNET connection/limited Internet connection/or unstable internet connection at the point of where the transaction runs.
So to reproduce, run your code that uses either a runTransaction (Ensure that internet is not on). By default, TX require ONLINE access and should FAIL immediately where you can catch the exception. For iOS an exception is raised. For Android - no exception is raised and the transaction trys to run regardless.
No wait a while (5-10 secs) , then turn on INTERNET - after about 10 or so seconds when connection re-establishes itself, FLUTTER crashes.
I dont get this happening in iOS. I recall this working some time back so expect this to be a configuration/regression issue with cloud_firestore and potentially other plugins.
This is my setup :
cloud_firestore: 0.13.4
firebase_auth: 0.15.4
and :
dependency_overrides:
firebase_core: 0.4.4
Build gradle (Snapshot)
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0' 3.5.3
classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0-beta02'
}
and my build.gradle in Android/app :
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'com.google.firebase:firebase-analytics:17.2.2'
implementation 'com.google.firebase:firebase-crashlytics:17.0.0-beta01'
}
This is CRITICAL. I cannot release to the PLAYSTORE with this issue.
This happens to me when running multiple transactions.
Try not putting things unrelated with transaction inside it, it actually solved my issue.
I made the Transaction function non-async (chained futures using then instead) and it solved this problem for me. Eg. changed this:
Firestore.instance.runTransaction((Transaction tx) async {
DocumentSnapshot recipeeSnapshot = await tx.get(recipee.reference);
if (recipeeSnapshot.exists) {
await tx.update(recipee.reference, <String, dynamic>{
'in_favorites_of': FieldValue.arrayUnion([userReference]),
});
}
})
into this:
Firestore.instance
.runTransaction((Transaction tx) {
return tx.get(recipee.reference).then((recipeeSnapshot) {
if (recipeeSnapshot.exists) {
return tx.update(recipee.reference, <String, dynamic>{
'in_favorites_of': FieldValue.arrayUnion([userReference]),
});
}
});
})
I just noticed that the example on running transactions on the official readme uses an async transaction, I'm not sure if this is the best way to do it since concurrent transactions with async will cause this crash. Eg. tapping a button very fast that triggers the same transaction.
I made the Transaction function non-async (chained futures using then instead) and it solved this problem for me. Eg. changed this:
Firestore.instance.runTransaction((Transaction tx) async { DocumentSnapshot recipeeSnapshot = await tx.get(recipee.reference); if (recipeeSnapshot.exists) { await tx.update(recipee.reference, <String, dynamic>{ 'in_favorites_of': FieldValue.arrayUnion([userReference]), }); } })into this:
Firestore.instance .runTransaction((Transaction tx) { return tx.get(recipee.reference).then((recipeeSnapshot) { if (recipeeSnapshot.exists) { return tx.update(recipee.reference, <String, dynamic>{ 'in_favorites_of': FieldValue.arrayUnion([userReference]), }); } }); })
This did not work for me. I tried to set the transactions to be non-async, but running the transactions simultaneously from two different devices still would crash one of them.
I'm having the same issue. Just a thought, but should we try putting await in front of the transaction call? Like this:
await Firestore.instance.runTransaction((Transaction tx) async { DocumentSnapshot recipeeSnapshot = await tx.get(recipee.reference); if (recipeeSnapshot.exists) { await tx.update(recipee.reference, <String, dynamic>{ 'in_favorites_of': FieldValue.arrayUnion([userReference]), }); } })
I am also having the same issue when I try to run the same transaction simultaneously on two android devices. It crashes on one of the android device whose transaction starts a little later than that of other.
E/AndroidRuntime(24675): Process: com.toodle, PID: 24675
E/AndroidRuntime(24675): java.lang.RuntimeException: An error occurred while executing doInBackground()
E/AndroidRuntime(24675): at android.os.AsyncTask$3.done(AsyncTask.java:354)
E/AndroidRuntime(24675): at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
E/AndroidRuntime(24675): at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
E/AndroidRuntime(24675): at java.util.concurrent.FutureTask.run(FutureTask.java:271)
E/AndroidRuntime(24675): at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
E/AndroidRuntime(24675): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
E/AndroidRuntime(24675): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
E/AndroidRuntime(24675): at java.lang.Thread.run(Thread.java:764)
E/AndroidRuntime(24675): Caused by: java.lang.AssertionError: INTERNAL ASSERTION FAILED: A transaction object cannot be
used after its update callback has been invoked.
E/AndroidRuntime(24675): at com.google.firebase.firestore.util.Assert.fail(com.google.firebase:firebase-firestore@@21.3.0:46)
E/AndroidRuntime(24675): at com.google.firebase.firestore.util.Assert.hardAssert(com.google.firebase:firebase-firestore@@21.3.0:31)
E/AndroidRuntime(24675): at com.google.firebase.firestore.core.Transaction.ensureCommitNotCalled(com.google.firebase:firebase-firestore@@21.3.0:246)
E/AndroidRuntime(24675): at com.google.firebase.firestore.core.Transaction.lookup(com.google.firebase:firebase-firestore@@21.3.0:81)
E/AndroidRuntime(24675): at com.google.firebase.firestore.Transaction.getAsync(com.google.firebase:firebase-firestore@@21.3.0:191)
E/AndroidRuntime(24675): at com.google.firebase.firestore.Transaction.get(com.google.firebase:firebase-firestore@@21.3.0:228)
E/AndroidRuntime(24675): at io.flutter.plugins.firebase.cloudfirestore.CloudFirestorePlugin$5.doInBackground(ClouE/AndroidRuntime(24675): at io.flutter.plugins.firebase.cloudfirestore.CloudFirestorePlugin$5.doInBackground(CloudFirestorePlugin.java:608)
E/AndroidRuntime(24675): at android.os.AsyncTask$2.call(AsyncTask.java:333)
E/AndroidRuntime(24675): at java.util.concurrent.FutureTask.run(FutureTask.java:266)
E/AndroidRuntime(24675): ... 4 more
W/OPDiagnose(24675): getService:OPDiagnoseService NULL
D/OSTracker(24675): OS Event: crash
Despite what others are saying here, such as :-
1) Dont use await on runTransaction
2) Avoid unrelated TX code
etc - I'd just like to add that TX worked completely for me for pretty much most part of 2019. This included use of BatchWrites & runTransaction and using await on a runTransaction
The proper way one should be doing things
Furthermore, I used TX heavily in the app I am writing for both iOS and Android and worked completely in offline mode.
Additionally - worked concurrently across multiple disparate devices and platforms.
One device was saving (say) 400 documents and another device was deleting or editing some of those documents. This all happened without issue.
Now in 2020 - it Seems Cloud Firestore and/or Firebase core or another dependent plugin- has brought with it regression issues.
To repeat - my issues only present itself when an ANDROID client attempts to write a large batch (100+ documents) and then looses signal during the TX.
One thing I have noticed, if you extend the DEFAULT TX TIMEOUT - this dramatically improves the TX writing and there is a higher percentage of your TX completing rather than you experiencing the
A transaction object cannot be used after its update callback has been invoked.
error
I believe there is a mismatch between Firebase core, Cloud Firestore & Firebase Auth and do recall that I temporarily fixed this problem by changing versions from 1 of the above.
Since there has been nobody from the official Firebase or Flutter team commenting on this issue in the last 3 months , I expect this to be quietly swept under the carpet for the rest of the year.....
Experiencing the same issue but only on Android. Either physical or emulator. It just crashes. On iOS there's no issue in either physical device or emulator.
Urgently need an update
I just noticed that the example on running transactions on the official readme uses an async transaction, I'm not sure if this is the best way to do it since concurrent transactions with async will cause this crash. Eg. tapping a button very fast that triggers the same transaction.
do you have crashes when 2 devices execute transaction at the same time after you changed to .then() ? This problem is a pain
Here's a snippet of my code:
It only crashes on Android when more then one device run the transaction at the same time. I've tried Android-Android and Android-iOS.
DocumentReference orderNumberDocument = Firestore.instance.collection(orderNumberPath).document(orderNumberDocumentId);
try {
await Firestore.instance.runTransaction((transaction) async {
DocumentSnapshot freshSnapOrderNumber = await transaction.get(orderNumberDocument);
int newOrderNumber;
if (freshSnapOrderNumber.exists) {
newOrderNumber = freshSnapOrderNumber['$orderNumberFieldName'] + 1;
await transaction.update(freshSnapOrderNumber.reference, {
'$orderNumberFieldName': newOrderNumber,
}).catchError((e) {
print('Order Number Update Failed: $newOrderNumber');
}).whenComplete(() {
print('Order Number Update completed: $newOrderNumber');
});
} else {
newOrderNumber = 1;
await transaction.set(orderNumberDocument, {
'$orderNumberFieldName': newOrderNumber,
}).catchError((e) {
print('Order Number Set Failed: $newOrderNumber');
}).whenComplete(() {
print('Order Number Set Completed: $newOrderNumber');
});
}
} catch (e) {
print('Try catch failed: $e');
rethrow;
}
Same issue here. This actually happens with both iOS and Android. Live devices and Simulators.
Note as per https://github.com/FirebaseExtended/flutterfire/issues/1969#issuecomment-625882372
Remember - transactions should :
A. Fail when in OFFLINE mode
B. If the TX is repeated 5 times and doesnt complete
C. If it times out
This issue now occurs on both iOS and Android and the assertion now takes precedence on A-C which is where the bug is.
What does work :
What always fails :
Run your TX code code on your device/simulator with NO INTERNET - internally, your TX will be repeated 5 times, then turn on your internet. Your TX will fail and you will get the assertion error.
Note - to simulate the issue above, you need to have (say) a routine that inserts/delete lots of documents (try 20,30,50 etc) within a Single TX.
3 Months on and no Fix ???
THIS IS A HUGE ISSUE FLUTTER/FIREBASE TEAM - A REGRESSION ISSUE AS THIS USED TO WORK FLAWLESSLY.
Problem persists for me in version 0.13.6
I am facing the same issue when running the multiple transactions on the same document.
I'm seeing the same issue when a device is offline.
The last version I've tried that's without the error is 0.12.10 and the first one where it fails is 0.12.11.
As far as I can see, the commit https://github.com/FirebaseExtended/flutterfire/commit/5076f045c5b01798ddb4f62ffdb031afbc90ff03 added in version 0.12.11 does not have relevant changes in the plugin but it changes version of com.google.firebase:firebase-firestore from 19.0.0 to 21.3.0.
One of the changes introduced in the new version is the new hardAssert that we're hitting: https://github.com/firebase/firebase-android-sdk/commit/dd692603b6c8d4a4b7b2715b3925de8eded85ea6
private void ensureCommitNotCalled() {
hardAssert(
!committed,
"A transaction object cannot be used after its update callback has been invoked.");
}
same issue here here
I am now hitting this problem in iOS as well as Android.
I previously thought this was a problem only in Android in OFFLINE mode but thats not the case.
Now the situation in iOS crashes my app when I set a timeout of 5 seconds and try and remove a large number of documents as part of a TX. So If I try and delete 50 documents in a collection and then try and remove the parent document it crashes.
In Android - the error is different, repeating the same test I get a failed TX with the error :
'Every document read in a transaction must also be written'
If I increase the timeout to say 60 seconds then both iOS and Android work without any issue under this test.
Its worrying that nobody has been assigned to look at this severe issue - Flutter is nothing without a stable backend . There is no point making Flutter look pretty if I cannot save data consistently !
Hi All - I can confirm this is a problem and is being fixed as part of #2582
On Android at least, I have recently refactored a lot of the transaction code to handle various user issues. Work is still in progress on the iOS side of things.
The overall observations are:
runTransaction promise, however any non-primitive values (such as a DocumentSnapshot) were causing a crash (since they cannot be transferred to native and back).I'd say for now, try and stick with WriteBatches & FieldValue support until the stable/tested update lands.
@Ehesp When you say :
I'd say for now, try and stick with WriteBatches & FieldValue support until the stable/tested update lands.
Unfortunately - during my tests - I'd just like to add that Transaction support whether its via :
runTransaction (Transactions) or
BatchedWrites
are broken and BOTH exhibit the behaviour discussed in this thread. Thus if we need to have the benefit of transactions when dealing with documents, collections & collection groups - if the primary transaction api's in Firebase (Transaction & BatchWrites) are broken then are you saying we should implement our own internal transaction handling by making use of FieldValue.serverTimestamp() until this issue is fixed ?
@sallypeters If I increase the timeout to say 60 seconds then both iOS and Android work without any issue under this test.
That's correct, the timeout is there to protect yourself and the OS. It defaults to 5 seconds, for each transaction attempt. Upping the limit is fine for a large volume of transaction operations.
I'm also not too sure about write batch, I feel that's a separate issue as they're both totally different implementations.
@Ehesp
Setting a very high timeout value may help some apps based on their operating context but other apps may need immediate notification when offline or a transaction implicitly fails - if a very high timeout is set, these last two conditions will not be met until the timeout ends which is not practical.
This issue that we are all facing is actually huge since it prevents firebase data-centric apps being deployed to the various stores.
If at all possible, can we say with certainty when this regression issue crept in so that we can all rollback to using a more stable version of the firebase API's that offer a seamless error free transaction experience across both iOS and Android
For my use case, I need to know the most stable version of cloud firestore/firebase core that will work with Firebase API's for :
Cloud firestore
Cloud functions
Firebase_auth
Firebase_core
Firebase_storage
Google_sign_in
Firebase_remote_config
Lastly - This issue has been around since Nov/Dec 2019 - I consider this to be an urgent issue, yet I don't see any tags/labels here stating its severity ? Can you therefore advise what the community needs to do to bump this up the triage process so that it gets looked at much sooner ?
Interestingly I'm not actually able to get transactions to abort when offline (using disableNetwork), it will always trigger which is frustrating to test.
Lastly - This issue has been around since Nov/Dec 2019 - I consider this to be an urgent issue, yet I don't see any tags/labels here stating its severity ? Can you therefore advise what the community needs to do to bump this up the triage process so that it gets looked at much sooner ?
We have this tracked internally and has the relevant labels. In-fact, transactions have already been reworked as part of #2582 on Dart & Android, we're currently working through the iOS side of things.
There's more issues which have to be resolved with transactions on-top of this, such as being able to bail out with errors, handle these on Dart and also return non-primitive values from the transaction. The only one I'm unsure of is properly testing the offline behaviour (since disableNetwork doesn't seem to effect transactions).
@Ehesp
Pre Oct/Nov 2019 - Transaction support worked well in both iOS and Android. When offline, a transaction would always (and rightly so) fail and one would be able to capture the exception. The API for transactions always stated that :
Transactions will fail when the client is offline.
This is no longer the case which means if a users device is fluctuating between online/offline and a transaction is in-flight, there is now, no longer any mechanism for an app to gracefully handle these scenarios - the app just crashes (under Android) when the device comes back on line.
I suspect a fix for what we are experiencing is actually many months away, and if we are lucky probably we will get by the end of the year - so, coming back to my previous question , it would be great if you can help identify where the regression error crept in so that we can all rollback to a more stable build.
If at all possible, can we say with certainty when this regression issue crept in so that we can all rollback to using a more stable version of the firebase API's that offer a seamless error free transaction experience across both iOS and Android
@Ehesp Do you have any info regarding @alexda12 last comment - I would also like to know which version of the Firebase plugins work so that we can downgrade to that version.
I am so glad I found this thread and didn't put my fist through my monitor. The Transaction issues have rendered my social app unusable over the past month. A social app inherently depends on user content being shared and the stability of those transactions. I have tried various workaround with no success. I cannot figure out where to rollback to for stable transactions. This isn't just an issue to "sweep under the rug", this is a legitimate show stopper for our app development. My app can't run more than a minute of basic usage without crashing.
If a fix isn't coming soon, could you at least make the issue more known? As a developer, for weeks I thought I was doing something wrong. I was prepared to throw away my entire project and even fathomed building my own backend because of this.
Asking us to avoid using transactions is like asking us to take three wheels off our car.
@Ehesp Firstly - can you shed some light on what was asked by @alexda12 a few days back please - I'm certain this information is critical in developers deciding whether to park development with Flutter/Firebase or to persevere and attempt to roll with the current pain points of this severe regression issue.
Secondly , as per your message stating as a workaround :
I'd say for now, try and stick with WriteBatches & FieldValue support until the stable/tested update lands
Adopting Firebase batches aka batch Writes will not help the majority of transaction based code since READS will be paramount especially when dealing with complicated queries, collection groups and transactions that span multiple documents and collections.
This issue has actually be around since late Aug 2019. From my analysis - It originally surfaced in Cloud Firestore 0.13.0+1.
0.13.4 actually fixed the crashing in Android in Offline mode but then there was another regression issue with Firebase_core and 0.4.4 was meant to fix this issue along with another regression issue with Firebase_auth 0.15.4. Unfortunately - the whole Firebase API eco system is a mess with respect to versioning and dependencies. Generally , a developer might take the approach to always upgrade to the latest version of the FlutterFire SDK's but in my experience, I've found this counterproductive - and my advice to any Flutter developer reading this thread is to stay put with what you have. If your app works and you dont have any bugs in any of the standard Flutter/Firebase API's and you dont have a compelling reason to upgrade , then stay put. Its not worth the headache and regression issues that you will inevitably experience. I have not updated to Flutter 1.17.2. I am still on 1.12.13 hotfix 5. It works, I am soon to release my app (have been waiting 8 months for this TX issue to be resolved and then I can release) and I really don't have the time to go through the pain again of upgrading Flutter (1.78 to 1.9x was a major headache) - So will either upgrade Flutter after I go live or potentially go back to Xamarin which I didn't want to do (and investigate Xamarin MAUI).
Regarding this TX issue - everyone might want to try and go back to 0.12.10+2 or 0.12. Currently I am having some success with this version.
@MsXam you're right about 0.12.10+2. I downgraded and I'm no longer having any transaction issues. Thanks!
Any potential downside to downgrading to that low a version? I see that 0.12.10+2 was released in November.
@mohisham I have spent a considerably amount of time going back for the past year of updates/changes on the FlutterFire plugins (Storage, Auth, Firestore etc) to try and ascertain at what point the transaction handling was broken and can now say with some certainty that adopting 0.12.10.x would provide better stability then what we are currently seeing in the present versions.
If you dont need Mac OS or WEB Support, or you do not require comprehensive Querying capabilities then I would advise moving past this version for pretty much most of this year unless other dependent FlutterFire plugins bump up their minimum dependent versions of Cloud_firestore above 0.12.10.x.
As a sidepoint, I'm sure everybody is doing this but so that we are not locked into a particular vendor back-end , it would be prudent to construct your backend code such that you can easily swap Firebase/Firestore for say AWS or AZURE should the timeframe for Invertase developers to fix/restructure/refactor the existing FlutterFire plugins exceed your expectations - they have been somewhat quiet to numerous requests for clarification so I think its safe to say that this current problem will not be effectively fixed anytime soon.
I'm currently testing Amazon DynamoDB which so far is yielding great results/performance and is more mature than Cloud Firestore and stable and so the issues we are experiencing with Cloud Firestore are definitely not present in DynamoDB.
Firstly - can you shed some light on what was asked by @alexda12 a few days back please - I'm certain this information is critical in developers deciding whether to park development with Flutter/Firebase or to persevere and attempt to roll with the current pain points of this severe regression issue.
they have been somewhat quiet to numerous requests for clarification so I think its safe to say that this current problem will not be effectively fixed anytime soon.
We don't have much history with this library, so I'd be guessing (unless I spend a bunch of time sifting through commits/code) to give an exact version.
ETA on a Core/Firestore/Auth usable update is 4-6 weeks. It will probably be as a dev release as there are large changes we'll need to make sure devs can work with before we ship as a full release.
The plan is also to have the other plugins be compatible with these changes, even though they'll be getting QOL updates later.
@MsXam
Thanks for your tip about downgrading version. There is no more crash in 0.12.10+2 when trying to reload transaction again.
Unfortunately, it still doesn't work what I expected.
I thought if transaction failed, it rollback and retry to run transaction section fully.
But, as I checked, rollback is working well, but running transaction is stopped in getting document.
(ex. transaction.get(ref))
Anyway, it doesn't matter in my app flow.
So, I solved this problem temporarily with your help. Thanks.
@butyear
The version 0.12.10+2 should address the following issues and provide stability for BatchWrites and runTransaction API calls :-
If a client (Android or iOS) is offline - the TX will immediately fail and a PlatformException can be caught. This is the correct behaviour.
If your TX performs reads which are used in updates/deletes and your reads then change - YOUR ENTIRE TX will be re-executed for upto 5 retries. If the TX still doesn鈥檛 complete within 5 retries, you can catch a PlatformException. This is the correct behaviour.
If your TX handling involves >500 documents, your TX will fail. This can be captured in a PlatformException. This is the correct behaviour.
If your TX uses the timeout parameter and your TX does not complete within this time, your TX will fail and a PlatformException can be caught. This is the correct behaviour.
If your TX performs a mixture of reads, updates, deletes, inserts etc and you mix the reads throughout your TX code. Your TX will fail. You can catch the PlatformException. You need to structure your TX handling code such that READS are done up front and values that are used in your updates/deletes/inserts are cached accordingly. This is the correct behaviour.
Despite conflicting information regarding the correct usage of runTransaction etc . You need to always AWAIT the runTransaction. Failure todo so can cause potential race conditions when the same TX (that is in-flight) is re-executed again from the client because of rebuilds that are out of your control.
Upfront caching, querying, getting snapshot information or collection group info should be done outside of the runTransaction. Unless you want these operations to be PART of the READ phase for a TX , it is best to place outside of the TX for performance and potential TX failures. This is the correct behaviour.
BatchWrites are faster than code wrapped in a runTransaction. If you have no need for READS, restructure your code in a BATCHWRITE. BatchWrites never FAIL when a client is offline and will always succeed. When a client is back online, the local TX cache will be applied on Cloud Firestore.
If your TX size is >10MiB, it will; fail. If you are writing deep-nested complex transaction code that can potentially encompass >1 and <500 documents - you may want to write the equivalent of a sizeOf() so that you can detect if your TX will exceed this limitation. Remember fieldnames will also add to the limitation so its best you structure your Cloud Firestore schema such that very long field names are not used (They will also cost you more money in the long run). If your TX code exceeds 10
Finally - ensure your document writes are limited at most to 1 per second. Anything more than this will result in a failed TX which can be caught via PlatformException. This is the correct behaviour.
Example formatting code for PlatformException :
....
on PlatformException catch (pe) {
String mess;
if (await CommonsHelper.isOffline()) {
mess = 'Are you offline ?';
} else {
mess = 'Your custom message ....';
}
var formatted = formattedPlatformException(pe);
if (formatted != null) {
mess = formatted;
}
/// Can be any custom message that gets displayed on the client
tupleReturned = Tuple2('TX failed.\n$mess', false);
} on Exception catch (e) {
/// Can be any custom message that gets displayed on the client
tupleReturned = Tuple2('TX failed\n${e.toString()}', false);
}
return tupleReturned;
....
And FormattedPlatformException
/// Takes a PlatformException and creates a friendlier message
String formattedPlatformException(PlatformException pe) {
if (pe != null) {
String code = pe.code == null ? '' : pe.code.toLowerCase();
String message = pe.message == null ? '' : pe.message.toLowerCase();
/// Check for various codes here and provide a friendlier message ...
if (code == '3' &&
message.contains(
'every document read in a transaction must also be written in that transaction')) {
return Strings.tooManyRowsSelected;
}
if (code == '14' && message.contains('dns resolution')) {
return Strings.operationFailedNoInternet;
}
if (code == '3' && message.contains('maximum 500 writes'))
return Strings.txDocumentLimitReached;
}
return null;
}
@MsXam
In my case, number 2 condition is not working well.
Here is a simplified sample code.
// Here is the scenario.
// Read A, B user, and check isRunning.
// If A, B is not running, do some related task, and change isRunning to true in transaction.
// Then if C user is trying to run with B, can't connect because B is already running with A.
Firestore.instance.runTransaction((transaction) async {
// read document using transaction.get
DocumentSnapshot aDocumentSnapshot = await transaction.get(aDocRef);
DocumentSnapshot bDocumentSnapshot = await transaction.get(bDocRef);
// Point 1 : Can't reach here after transaction error.
User a = User.fromJson(aDocumentSnapshot.data);
User b = User.fromJson(bDocumentSnapshot.data);
if (a.isRunning == false && b.isRunning == false) {
roomName = makeRoomName(_myId, yourId);
// give delay for testing transaction
// !! while delaying, change user B's isRunning to "true" in firestore console page.
// it's like another user is connected with B while A is trying to connect with B.
await Future.any([
Future.delayed(const Duration(seconds: 3))
])
// task 1
transaction.set(~~);
// task 2
transaction.delete(~~);
transaction.delete(~~);
// task 3
await transaction.update(aDocRef, { 'isRunning': true });
await transaction.update(bDocRef, { 'isRunning': true });
}
});
In this case, I changed isRunning value to true in firestore console after getting value for making transaction error intentionally.
Then, after task 3 failed, task1 and task2 are rolled-back. And runTransaction starts again.
But at Point 1, transaction stopped and can't reach task1~3 again.
I don't know why it works like this.
I'm thinking about simulating this flow in java or other platforms.
I wanna know if it is only cloud_firestore library's problem or not.
Or am I using this incorrectly?
@butyear
In your context, the potential issue you are seeing is because of incorrect use of Cloud Firestore API ...
You need something like this :

@MsXam
Thanks for your kind explanation.
As you said, of course I set docRef outside of transaction like below. (about No.4)
DocumentReference aDocRef = userRef.document(aDocId);
DocumentReference bDocRef = userRef.document(bDocId);
Firestore.instance.runTransaction(~~~ ...)
But I have a question about No.5
If I don't use "transaction.get", then doesn't transaction work wrong?
If I use just 'aDocRef.get' instead of using 'transaction.get', then when aDoc data is changed from other user, transaction doesn't occur failure.
Even other user changed aDoc's data, this transaction code over write aDoc's data without any failure.
I think transaction.get have to be used for using transaction.
If you are interested in talking about this more, let me make a real working sample code and share it.
@butyear
If you want to always get the LATEST document in your transaction code and if this document can change OUTSIDE of your TX by edits from OTHER USERS - then yes , you will need to make use of the :-
tx.get(....)
construct ...
and used as in :
try {
var baseEntityReference = Firestore.instance
.collection('users/$userFolder/data')
.document('$entityDocumentId');
await Firestore.instance.runTransaction((Transaction tx) async {
// We want to establish if we still have this reference available
var thisEntityReference =
await tx.get(baseEntityReference); // must use a TX for this
if (thisEntityReference != null && thisEntityReference.exists) {
// potentially not been deleted
await tx.update(thisEntityReference.reference, {
'entity.entityName': entityDetails.entityName,
'entity.entityDescription': entityDetails.entityDescription,
'entity.entityIcon': entityDetails.entityIcon,
});
tupleReturned = Tuple2('your success message etc.', true);
} else {
// Removed !
tupleReturned = Tuple2('Update failed. ----- your msg -----', false);
}
}, timeout: getTimeout());
} on PlatformException catch (pe) {
debug('*** Transaction error ***');
debug('*** ${pe.message} ***');
var formatted = formattedPlatformException(pe);
String mess = 'Are you offline ?';
if (formatted != null) {
mess = formatted;
}
tupleReturned = Tuple2('Update failed.\n$mess', false);
} on Exception {
tupleReturned = Tuple2('Update failed.\n Are you offline ?}', false);
}
return tupleReturned;
}
This means that your code needs to be consistent, pure & repeatable - meaning that it should not maintain any STATE from your first call to tx.get() ... since if this fails then your entire tx will run approx 5 times etc ....
Hi everyone. So, I rolled back to 0.12.10+2 and although my app doesn't crash anymore if I fire 2 transactions one on iOS and one on Android physical devices at exactly the same time the Android device fails to get its transaction persisted in Firebase. Which I think it's even worse because I don't get any exceptions and me and the user are none the wiser. I rather let the app crash so the user knows something went wrong. I'm safeguarding his order so he'll see it's still there and will be able to resubmit. I haven't looked at all the code snippets posted yet. Maybe I'll find some wisdom there.
@ErnestoCuesy
From your description - this is what I would expect to happen apart from no exception being propagated back to the android client when its TX fails - but that could be down to your implementation .
If two users update the same document at exactly the same time - Are you expecting Cloud Firestore to deny one user access , fail the tx and allow the other user 100% write access to the document ?
What actually happens is that in the case of any potential concurrent edits, Cloud Firestore will attempt to re-run the entire TX again but this time it will (based on how you have structured your tx code) attempt to get the latest documents so its working on the most uptodate data. It will attempt to do this 5 times and if the tx still finds its working with dirty data, the entire tx will fail. This is the correct scenario.
I suspect that your android client is indeed getting its data persisted but then the iOS client now gets the most uptodate document and overwrites the data persisted by the Android client. This can easily be proved amending your logic to update different parts of the document based on the client that is running the TX (Android/iOS) - then checking to see that iOS field has been updated and Android field has been updated after the TX completes etc ...
If you want your TX code to fail if the document its working on is dirty (Has been changed by another user) then you can provide this functionality yourself - this is an implementation issue and nothing to do with issues with Cloud Firestore.
@MsXam thank you for your comments. Interesting scenario you mentioned that the Android transaction is overwritten by the iOS client. I will test that. I'm still very new to Firebase but from my previous SQL knowledge I would expect the database to kind of queue the incoming transactions and process them serially as they are finished, but that's how I picture it in my head and it could well be a wrong assumption. It looks like more than one transaction can be processed concurrently, am I right? Then yes, I totally agree I would need to look at my logic and rewrite accordingly. Thanks again.
@MsXam
I have downgraded to the recommended version and can confirm BOTH Android & iOS work.
Many thanks for spending the time going through the various commits to establish a version that works.
@MsXam . Downgrading works. I no longer have the crash.
@Ehesp Setting a timeout to say - 5 seconds crashes an iOS or Android app when the TX doesnt complete within this time. Has this been identified as a bug also on your refactoring analysis - the docs state that a transaction that fails should ALWAYS return an error code. This currently is not happening on both platforms.
@bpaul7101 added a test case on that on the rework:

Just curious has this issue been addressed as part of the flutterfire roadmap ? https://github.com/FirebaseExtended/flutterfire/pull/2913
Hey everyone 馃憢 , as part of our on-going work for #2582, this has been resolved in our Firebase Firestore rework (#2913), transactions has had a massive rework as unfortunately it was fundamentally broken in a lot of places, points specifically in relation to this issue (but there's many others):
dispatch_semaphore_wait was completely ignored).This has now been merged into master. We'll look at publishing some prereleases in the next few days. Thank you
@Salakar Are you sure this is in Stable ? I have just tried to see if this has been fixed and I still get the same issue as discussed here https://github.com/FirebaseExtended/flutterfire/issues/1969#issuecomment-599065549
i.e I get a crash in Android when device toggles between offline and online and there are transactions already running ....
@Salakar Are you sure this is in Stable ? I have just tried to see if this has been fixed and I still get the same issue as discussed here #1969 (comment)
i.e I get a crash in Android when device toggles between offline and online and there are transactions already running ....
Nope it's not in stable;
This has now been merged into master. We'll look at publishing some prereleases in the next few days. Thank you
@Salakar , sorry I meant to say master ...
Every time I click the button to execute the transaction when I'm offline, the application crashes. Can anyone tell me why? My code is as follows:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:mobx/mobx.dart';
part 'plantao_controller.g.dart';
class Controller = ControllerBase with _$Controller;
abstract class ControllerBase with Store {
Future<bool> pegarPlantao({cpf, hospital, setor, dia, horario}) async {
Map<String, dynamic> mapa = {};
if (horario == '00:00-06:00') {
mapa['00:00-06:00'] = cpf;
} else if (horario == '06:00-12:00') {
mapa['06:00-12:00'] = cpf;
} else if (horario == '12:00-18:00') {
mapa['12:00-18:00'] = cpf;
} else if (horario == '18:00-00:00') {
mapa['18:00-00:00'] = cpf;
}
final ref = Firestore.instance
.collection(hospital)
.document('Escala 2020')
.collection(setor)
.document(dia);
final doc = await Firestore.instance
.runTransaction((t) {
t.update(ref, mapa);
})
.then((value) => true)
.catchError((onError) => false);
return doc;
}
Future<bool> passarPlantao({cpf, hospital, setor, dia, horario}) async {
Map<String, dynamic> mapa = {};
if (horario == '00:00-06:00') {
mapa['00:00-06:00'] = '';
} else if (horario == '06:00-12:00') {
mapa['06:00-12:00'] = '';
} else if (horario == '12:00-18:00') {
mapa['12:00-18:00'] = '';
} else if (horario == '18:00-00:00') {
mapa['18:00-00:00'] = '';
}
final ref1 = Firestore.instance
.collection(hospital)
.document('Escala 2020')
.collection(setor)
.document(dia);
final doc = await Firestore.instance
.runTransaction((t) {
t.update(ref1, mapa);
})
.then((value) => true)
.catchError((onError) => false);
return doc;
}
}
any updates ?
Most helpful comment
Tracked this down to Firebase offline use and Transactions.
This will happen when you are using either BATCHWRITES or runTransaction in ANDROID and you have no INTERNET connection/limited Internet connection/or unstable internet connection at the point of where the transaction runs.
So to reproduce, run your code that uses either a runTransaction (Ensure that internet is not on). By default, TX require ONLINE access and should FAIL immediately where you can catch the exception. For iOS an exception is raised. For Android - no exception is raised and the transaction trys to run regardless.
No wait a while (5-10 secs) , then turn on INTERNET - after about 10 or so seconds when connection re-establishes itself, FLUTTER crashes.
I dont get this happening in iOS. I recall this working some time back so expect this to be a configuration/regression issue with cloud_firestore and potentially other plugins.
This is my setup :
Build gradle (Snapshot)
and my build.gradle in Android/app :
This is CRITICAL. I cannot release to the PLAYSTORE with this issue.