How do I know if my Firestore transaction failed? - google-cloud-firestore

I am updating my document with this code.
Future<void> save() async {
print('league save');
final DocumentReference ref =
Firestore.instance.collection('leagues').document(_documentName);
Firestore.instance.runTransaction((Transaction tx) async {
DocumentSnapshot postSnapshot = await tx.get(ref);
if (postSnapshot.exists) {
await tx.update(ref, _getDocument());
print('league save complete');
}
});
}
I believe that this may be failing sometimes but I am not sure. I am got getting an error.
The reason I suspect it is failing sometimes is because my listener (elsewhere in the app) isn't always getting fired when the document changes.
How do I log or capture an error in the transaction?

runTransaction is just a normal async operation that you can follow up with a then and catchError:
Firestore.instance.runTransaction((Transaction tx) async {
// do whatever
}).then((val) {
// do something upon success
}).catchError((e) {
// do something upon error
});
and you can skip then .then() if you want

Related

Flutter - an async function returns before really finishing?

I have a function scanAndConnect() that should scan for BLE devices and connect to the device with the specified service ID. This function should be async and should return Future.
The problem is that scanAndConnect() prints 99999 and returns without waiting for flutterReactiveBle.statusStream.listen() to finish although I use await before it.
Future scanAndConnect(Uuid serviceId, Uuid charctId) async {
StreamSubscription<BleStatus>? bleStatusStreamSubscription;
StreamSubscription<DiscoveredDevice>? deviceStreamSubscription;
Stream<DiscoveredDevice> stream;
bleStatusStreamSubscription =
await flutterReactiveBle.statusStream.listen((bleStatus) async {
print("new listen ${bleStatus.toString()}");
if (bleStatus == BleStatus.ready) {
await bleStatusStreamSubscription!.cancel();
connectionStatus = BLEConnectionStatus.Connecting;
stream = await flutterReactiveBle.scanForDevices(
withServices: [serviceId],
scanMode: ScanMode.lowLatency,
);
}
});
print("9999999");
}
....
Future connectToDevice() async {
await ble.scanAndConnect(BLE_SERVICE_UUID, BLE_CHAR_UUID)
print("Statement after await in main");
setState(() {
loading = false;
print("Changing state to ${loading.toString()}");
});
}
This is the output I get in Xcode:
flutter: 9999999
flutter: Statement after await in main
flutter: Changing state to false
flutter: new listen BleStatus.unknown
flutter: new listen BleStatus.ready
How can I make scanAndConnect doesn't return before really finishing?
According to the documentation, FlutterReactiveBle.scanForDevices() returns a Stream, not a Future, so await will not work here. You can use
await for
listen()
await stream.first()
to wait for data from a Stream.

Upload multiple images to Firebase Storage AT ONCE

There are some questions posted about uploading multiple files to Firebase, but all the solutions I came across use a forEach loop or something similar to upload one by one. However, if I'm uploading files that depend on each other (say, my app requires both of them to exist to function correctly), this could be an issue because one of the uploads could succeed and the other fail.
I thought there must be a way to do something similar to a batch write in Firestore but for uploading files in Firebase Storage.
Is there any way to do that or is the loop method the only way?
(I'm using Flutter, so if possible I would appreciate it if any code provided as an answer is written in Dart)
Well, you could use Future.wait() to upload your files simultaneously without waiting for any of them to complete before you begin other.
To explain further, I made the following code snippet, please follow along:
void main() {
Future.wait([
uploadImage1(),
uploadImage2(),
uploadImage3(),
]);
}
Future<void> uploadImage1() async {
await Future.delayed(Duration(seconds: 3), () {
print('1');
});
}
Future<void> uploadImage2() async {
throw Exception();
}
Future<void> uploadImage3() async {
await Future.delayed(Duration(seconds: 3), () {
print('3');
});
}
I made three async operations, and added them all to Future.wait list, so all are executed/triggered at the same time, doesn't matters if any of them fails. Try the code on dart pad and you will see the output appears at the same time, the output would look like:
1
3
: ExceptionError: Exception
On the other hand, if you want to wait for one operation to successfully finish, then you can use await on a method that returns Future, so it will wait until the operation is successfully completed.
void main() async {
await uploadImage1();
await uploadImage2();
await uploadImage3();
}
Future<void> uploadImage1() async {
await Future.delayed(Duration(seconds: 3), () {
print('1');
});
}
Future<void> uploadImage2() async {
throw Exception();
}
Future<void> uploadImage3() async {
await Future.delayed(Duration(seconds: 3), () {
print('3');
});
}
The output would be:
1
: ExceptionError: Exception

Run multiple asyn function flutter one after one flutter

hello I want have to run two functions(Function1() and Function2()) and store value of these returns and run third function. But some time according to condition Function1() or Function2() or both not be run.
if(condition1){
await Function1();
}
if(condition2){
await Function2();
}
await Functon3();
I try as above but Function3() run simultaneously with Function1() or with Function2().
My Function1() code looks like following...
Future Function1() async {
apiService
.apiFileUpload()
.then((value) async {
///codes goes here
}).catchError((error) {
print('EEEE: ' + error.toString());
});
}
If anything not clear please let me know in the comment section.
Please do not use .then() in combination with async/await. It is technically possible, but it takes some skill to get it right, so why make it hard on yourself. Stick with one way of doing it, use either one or the other. You mixed it up and through a slight oversight, your Function1 does not actually wait on it's result. It just returns, with the function still running in the then block. So you await it, but that does not help.
Since you are using await already, stick with that and remove .then() from your repertoire for now:
Future Function1() async {
try {
final value = await apiService.apiFileUpload();
///codes goes here
} catch(error) {
print('EEEE: ' + error.toString());
}
}
You can use await
Future Function1() async {
try{
final value = await apiService
.apiFileUpload();
final value2 = await secondFuntion();
///add more and condition on values
} catch(e){
.....
}
}
from your question you need to tell the compiler to stop on particular task with await and avoid using then function it will never stop your compiler
your future fuction:
Future Function1() async {
apiService
.apiFileUpload()
.then((value) async {
///codes goes here
}).catchError((error) {
print('EEEE: ' + error.toString());
});
}
Modified Future func
Future Function1() async {
var result = await apiService.apiFileUpload();
if(result == success){
// code goes here
}else{
//you can show your error here
}
}

Is there a way to get notified when a dart stream gets its first result?

I currently have an async function that does the following:
Initializes the stream
Call stream.listen() and provide a function to listen to the stream.
await for the stream to get its first result.
The following is some pseudo code of my function:
Future<void> initStream() async {
// initialize stream
var stream = getStream();
// listen
stream.listen((result) {
// do some stuff here
});
// await until first result
await stream.first; // gives warning
}
Unfortunately it seems that calling stream.first counts as listening to the stream, and streams are not allowed to be listened by multiple...listeners?
I tried a different approach by using await Future.doWhile()
Something like the following:
bool gotFirstResult = false;
Future<void> initStream() async {
var stream = getStream();
stream.listen((result) {
// do some stuff here
gotFirstResult = true;
});
await Future.doWhile(() => !gotFirstResult);
}
This didn't work for me, and I still don't know why. Future.doWhile() was successfully called, but then the function provided to stream.listen() was never called in this case.
Is there a way to wait for the first result of a stream?
(I'm sorry if I didn't describe my question well enough. I'll definitely add other details if needed.)
Thanks in advance!
One way is converting your stream to broadcast one:
var stream = getStream().asBroadcastStream();
stream.listen((result) {
// do some stuff here
});
await stream.first;
Another way, without creating new stream, is to use Completer. It allows you to return a Future which you can complete (send value) later. Caller will be able to await this Future as usual.
Simple example:
Future<int> getValueAsync() {
var completer = Completer<int>();
Future.delayed(Duration(seconds: 1))
.then((_) {
completer.complete(42);
});
return completer.future;
}
is equivalent of
Future<int> getValueAsync() async {
await Future.delayed(Duration(seconds: 1));
return 42;
}
In your case:
Future<void> initStream() {
var stream = getStream();
var firstValueReceived = Completer<void>();
stream.listen((val) {
if (!firstValueReceived.isCompleted) {
firstValueReceived.complete();
}
// do some stuff here
});
return firstValueReceived.future;
}

Async function is not returning expected data in Flutter

I am trying to verify phone number using firebase. But getting unexpected result from async function. Here is my code :
bool isVerificationSuccess = false;
Future<bool> verifyUserPhoneNumber(String phoneNumber) async {
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: phoneNumber,
timeout: Duration(seconds: 60),
verificationCompleted: (credential) => verificationComplete(credential),
verificationFailed: (authException) => verificationFailed(authException),
codeAutoRetrievalTimeout: (verificationId) =>
codeAutoRetrievalTimeout(verificationId),
codeSent: (verificationId, [code]) => smsCodeSent(verificationId, [code]),
);
print("Status from service : $isVerificationSuccess");
return isVerificationSuccess;
}
verificationComplete(AuthCredential credential) async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
user.linkWithCredential(credential).then((_) {
print("User Successfully Linked");
isVerificationSuccess = true;
}).catchError((e) {
print("Linking Error : ${e.toString()}");
});
}
Here is the output :
Status from service : false
User Successfully Linked
So here verifyUserPhoneNumber function returns even before the completion of FirebaseAuth.instance.verifyPhoneNumber(), so its not returning expected data (always false) even when the verification is successful. Whats wrong here?
The issue is that the function call in this line:
verificationCompleted: (credential) => verificationComplete(credential),
is not awaited, even though the function itself is asynchronous. So what happens is:
verifyUserPhoneNumber is called from somewhere
FirebaseAuth.instance.verifyPhoneNumber is awaited
Within this call, verificationComplete(credential) is called and, because it itself contains an await statement, it will be queued up by the Dart event loop. So whatever happens after FirebaseUser user = await FirebaseAuth.instance.currentUser(); is delayed!
verifyUserPhoneNumber now continues executing and returns false.
After a value has already been returned, it is now verificationComplete's turn to process. It will change the boolean value, and then exit.
My recommendation here is not to use a global variable, but instead, for example, call verificationComplete after FirebaseAuth.instance.verifyPhoneNumber has already fully finished processing and returned.
Edit: This is how you could theoretically fix the issue, though it is not particularly pretty I do admit:
Future<bool> verifyUserPhoneNumber(String phoneNumber) async {
final completer = Completer<AuthCredential>();
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: phoneNumber,
timeout: Duration(seconds: 60),
verificationCompleted: completer.complete,
verificationFailed: completer.completeError,
codeAutoRetrievalTimeout: (verificationId) =>
codeAutoRetrievalTimeout(verificationId),
codeSent: (verificationId, [code]) => smsCodeSent(verificationId, [code]),
);
try {
final credential = await completer.future;
return await verificationComplete(credential);
} catch (e) {
verificationFailed(e);
return false;
}
}
Future<bool> verificationComplete(AuthCredential credential) async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
try {
await user.linkWithCredential(credential);
print("User Successfully Linked");
return true;
} catch (e) {
print("Linking Error : ${e.toString()}");
return false;
}
}
if u can place in isVerificationSuccess = true; inside function verificationComplete(credential) then u might get true value
So here verifyUserPhoneNumber function returns even before the
completion of FirebaseAuth.instance.verifyPhoneNumber(), so its not
returning expected data (always false) even when the verification is
successful. Whats wrong here?
FirebaseAuth.instance.verifyPhoneNumber uses a method channel plugins.flutter.io/firebase_auth and calls a native method for authentication.
It uses the call back specified in the argument verificationCompleted once verification completes. However, from the function definitions, it seems that FirebaseAuth.instance.verifyPhoneNumber expects a type void Function(AuthCredential). However, you provide Future<void> Function(AuthCredential)
Whats wrong here?
It seems that what is wrong is to expect that FirebaseAuth.instance.verifyPhoneNumber will wait for an async call back Future<void> Function(AuthCredential) to complete when it expects a void Function (AuthCredential).
So it just calls your verificationComplete(AuthCredential credential) async implementation which returns a Future<void>. At this moment, FirebaseAuth.instance.verifyPhoneNumber is complete and the control moves ahead to print and return isVerificationSuccess which is still false as the future returned by verificationComplete(AuthCredential credential) async is not resolved yet.
So,
Return a Future<void> from verifyUserPhoneNumber
Update the App's state using a setState inside verificationComplete after you assign isVerificationSuccess = true; to rebuild your app after a successful verification