Flutter Future Async Function - await stuck - flutter

I am calling a Future function that should add a document to firestore first, then to retrieve the doc id, then update the doc again with some paramaters.
I need to show a dialog for success or faliure.
it is working fine for success case, but incorrect when testing failure .. I disconnect the internet before pressing the button then I press it to call the future function while internet is off to test failure scenario.
the problem is the future function stuck on the trying to add the document to firestore, it never fail and go to the failure scenario.
the below is future function code:
Future CreateAccount() async {
CollectionReference businessprofilescollectionref =
FirebaseFirestore.instance.collection('BusinessProfilesCollection');
DocumentReference docRef = await businessprofilescollectionref.add({
'Profile_city': bpaddSelectedcity,
'Profile_direct_category': directcategory,
});
if (docRef.id.isEmpty) {
showDialog(context: context, builder: (_) => FailureDialog(context));
}
if (docRef.id.isNotEmpty) {
String documentid = docRef.id.toString();
print(documentid);
FirebaseStorage storage = FirebaseStorage.instance;
Reference ref = storage
.ref()
.child("BusinessProfileImages/$documentid".toString());
UploadTask uploadTask = ref.putFile(bpaddimagefile);
final snapshot = await uploadTask.whenComplete(() {});
print("Image Uploaded");
final urldownload = await snapshot.ref.getDownloadURL();
print(urldownload);
businessprofilescollectionref
.doc(documentid)
.update({'Profile_image_link': urldownload});
print("Link Updated");
await showDialog(
context: context, builder: (_) => SuccessDialog(context));
Navigator.of(context, rootNavigator: true).pop();
}
}
I tried to use timeout method like the below, this made the await ends, however the whole Future function is aborted and it doesn't continue executing the commands of showing the failure dialoge.
DocumentReference docRef = await businessprofilescollectionref.add({
'Profile_city': bpaddSelectedcity,
'Profile_direct_category': directcategory,
}).timeout(Duration(seconds: 20));
how can I solve this? I need to try to add the data to database, if that fails, I need to go to failure scenario and show a dialog.

What went wrong:
You're checking if the ID of the DocumentReference is empty and using that as your failure scenario.
That will not work because when you get a DocumentReference back, it will have an ID. Here is a quote from the docs:
Returns a DocumentReference with an auto-generated ID, after
populating it with provided data.
The above reason is why the docRef.id.isEmpty if statement is never executed because the ID is never empty.
How to fix it:
You should use a try-catch to wrap the add operation and implement the error scenario in the catch block.
Then you continue the rest of the operation in the docRef.id.isNotEmpty if statement right under the try-catch.
Update your CreateAccount function to this below:
Future CreateAccount() async {
try {
CollectionReference businessprofilescollectionref =
FirebaseFirestore.instance.collection('BusinessProfilesCollection');
DocumentReference docRef = await businessprofilescollectionref.add({
'Profile_city': bpaddSelectedcity,
'Profile_direct_category': directcategory,
});
} catch (e) {
showDialog(context: context, builder: (_) => FailureDialog(context));
return;
}
String documentid = docRef.id.toString();
print(documentid);
FirebaseStorage storage = FirebaseStorage.instance;
Reference ref = storage.ref().child("BusinessProfileImages/$documentid".toString());
UploadTask uploadTask = ref.putFile(bpaddimagefile);
final snapshot = await uploadTask.whenComplete(() {});
print("Image Uploaded");
final urldownload = await snapshot.ref.getDownloadURL();
print(urldownload);
businessprofilescollectionref.doc(documentid).update({'Profile_image_link': urldownload});
print("Link Updated");
await showDialog(context: context, builder: (_) => SuccessDialog(context));
Navigator.of(context, rootNavigator: true).pop();
}

Related

how to call a method from another dart file?

in my programme i want to call the same path inside a methode called getPhoto()=>upload(statefulwidget) to other file or statefulwidget
Future getPhoto() async{
FirebaseFirestore fearbase = FirebaseFirestore.instance;
Reference ref=FirebaseStorage.instance
.ref()
.child("${widget.user}/ProfileData")
.child("Url_$postId");
await ref.putFile(file!);
downloadUrl=await ref.getDownloadURL();
// upload image to firestore
var list=[];
await fearbase.collection("users").doc(widget.user)
.collection("PostData").doc(ido)
.set({"PostUrl":downloadUrl,"ownerName":loggedInUser.username,"userId":loggedInUser.uid,"timestemp":postId,"PostId":ido,"like":FieldValue
.arrayUnion(list)})
.whenComplete(() => Fluttertoast.showToast(msg: "Image Uploaded successfully .i."));
// .then((DocumentReference ido) => ido.update({"PostId":ido.id}))
}
more specifically i want to get like field path from the other file
There are multiple ways to do this.
But the simple one is you should create a class and define this method within the class.
class Demo {
static void getPhoto() {
print("photo");
}
}
Then your can access it like
Demo.getPhoto()
You can use callback function to solve this.
Future<String> getPhoto() async {
Reference ref = FirebaseStorage.instance
.ref()
.child("${widget.user}/ProfileData")
.child("Url_$postId");
await ref.putFile(file!);
return await ref.getDownloadURL();
// upload image to firestore
// .then((DocumentReference ido) => ido.update({"PostId":ido.id}))
}
Future upload(String downloadUrl) async {
FirebaseFirestore firebase = FirebaseFirestore.instance;
var list = [];
await firebase.collection("users").doc(widget.user)
.collection("PostData").doc(ido)
.set({
"PostUrl": downloadUrl,
"ownerName": loggedInUser.username,
"userId": loggedInUser.uid,
"timestemp": postId,
"PostId": ido,
"like": FieldValue
.arrayUnion(list)
})
.whenComplete(() => Fluttertoast.showToast(msg: "Image Uploaded successfully .i."));
}
In usage, you can pass the function as follows
getPhoto().then(upload);
Or
final downloadUrl = await getPhoto();
await upload(downloadUrl);

Why is (list.length == otherList.length) showing false - Flutter

I have a List<File> imageFiles made up of files that a user selects and when they press submit, each of the files is uploaded to the collection in Firebase Storage. For each one, the downloadUrl is added to another list uploadedImages. I cannot upload the document to Firestore until all the downloadUrls are added to the uploadedImages list so I used an if statement if (imageFiles.length == uploadedImages.length) {<upload document>}. The problem is, in those lists are not the same length until the forEach block completes, and even then it doesn't return true. I confirmed that all the files are uploaded to Firebase Storage and printed the uploadedImages so I know they are eventually the same length but don't know how to make the if statement recognize that. Here is my current code:
List<String> uploadedUrls = [];
/// Uploads each file and adds its URL
imageFiles.forEach((file) async {
String fileName = '${UniqueKey()}_post.png';
TaskSnapshot uploadTask = await FirebaseStorage.instance
.ref()
.child('/${user.uid}/posts/$postId/$fileName')
.putFile(file);
String url = await uploadTask.ref.getDownloadURL();
setState(() {
uploadedUrls.add(url);
});
print(uploadedUrls);
});
/// Uploads post if all files and URLs are added *NOT WORKING*
if (imageFiles.length == uploadedUrls.length) {
Post post =
Post(postId, uploadedUrls, user.uid, profile, location, Timestamp.now());
try {
FirebaseFirestore.instance
.collection('users')
.doc(user.uid)
.collection('posts')
.doc(postId)
.set(post.toJson()).then((value) {
setState(() {
_isLoading = false;
});
});
} on FirebaseAuthException {
setState(() {
_isLoading = false;
});
}
}
Because you are running an asynchronous function in forEach,it won’t wait until the task is completed. So you can either move the if condition inside the forEach’s callback below the setState or use a parent function as wrapper for both them, and it should await the first call
imageFiles.forEach((file) async {
String fileName = '${UniqueKey()}_post.png';
TaskSnapshot uploadTask = await FirebaseStorage.instance
.ref()
.child('/${user.uid}/posts/$postId/$fileName')
.putFile(file);
String url = await uploadTask.ref.getDownloadURL();
setState(() {
uploadedUrls.add(url);
});
if (imageFiles.length == uploadedUrls.length) {
....
});
or extract the forEach loop into a async function
void addDownloafUrls() asyn {
imageFiles.forEach((file) async { ... });
}
// And you can await this function
addDownloafUrls().then(() {
if (imageFiles.length == uploadedUrls.length) {...}
})

shared_preferences values returning null in flutter

I am using shared_preferences to store a bool value locally but I think I am doing something wrong.
So first of all, here is my initState:
#override
initState(){
super.initState();
checkIfUserHasData();
getBoolValuesSF();
}
on checkIfUserHasData, Im calling another function at the end (addBoolToSF)
Future<void> checkIfUserHasData ()async {
var collection = FirebaseFirestore.instance.
collection('users').doc(userID).collection('personalInfo');
var querySnapshots = await collection.get();
for (var snapshot in querySnapshots.docs) {
documentID = snapshot.id;
}
await FirebaseFirestore.instance
.collection('users')
.doc(userID)
.collection('personalInfo').doc(documentID)
.get().then((value) {
if (!mounted) return;
setState(() {
gender = value.get('gender');
profileImageUrl = value.get('url');
print(profileImageUrl);
print(gender);
});
});
if (gender != null){
if (!mounted) return;
setState((){
isUserNew = false;
});
if(gender == "Male"){
setState(() => genderIsMale = true);
addBoolToSF();
}else{
setState(() => genderIsMale = false);
addBoolToSF();
}
}else {
return;
}
}
Then addBoolToSF:
addBoolToSF() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('genderType', genderIsMale);
}
Lastely getBoolValuesSF:
getBoolValuesSF() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
bool _genderType = ((prefs.getBool('genderType') ?? true)) ;
genderType = _genderType;
});
}
When the genderType value is obtained I then decide which image to be the background image on the screen:
CachedNetworkImage(
placeholder: (context, url) =>
CircularProgressIndicator(),
imageUrl: genderType ? // : //
With all of that said, here is what is happening when the gender is changed on the firebase firestore:
The first time I navigate or refresh the screen nothing is changed and I get this error:
type 'Null' is not a subtype of type 'bool'
The second time I refresh or navigate to the screen, I do get the correct image on place but I get the same error message again
type 'Null' is not a subtype of type 'bool'
I have tried several ways to solve this issue but i dont seem to get it right.
Edit: I have noticed that when I removed the last part for CachedNetworkImage, I get no error so I think the problem might be on this part
In case like that when you need to wait for a future to build some UI, the go to way is to use a FutureBuilder
You use it like this
FutureBuilder<bool>(
future: getBoolValuesSF,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
// build your UI here based on snapshot value
},
)
checkIfUserHasData() and getBoolValuesSF() both are future method. you can create another async method and put it inside initState.
#override
initState(){
super.initState();
newMthod();
}
newMthod() async{
await checkIfUserHasData();
await getBoolValuesSF();
}

Why Flutter "await" doesn't wait?

I'm working on a Flutter app which needs uploading image files to Firebase Storage and then saving urls to Firestore.
Below is my code:
void onSubmit() async {
final fireStore = FirebaseFirestore.instance;
//picList has one element.
List<String> picUrlList = [];
picList.forEach((element) async {
//picUrlList.add(await uploadImage(File(element)));
var url = await uploadImage(File(element));
picUrlList.add(url);
print('1');
});
await fireStore.collection('xxx').doc('yyy').update({
"picUrlList": FieldValue.delete(),
});
await fireStore.collection('xxx').doc('yyy').update({
"picUrlList": FieldValue.arrayUnion(picUrlList),
});
print('2');
print(picUrlList.length);
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => EditPost()),
(Route<dynamic> route) => false,
);
}
Future<String> uploadImage(var imageFile) async {
//var uuid = Uuid().v1();
Reference ref = FirebaseStorage.instance.ref(
user.uid + DateTime.now().millisecondsSinceEpoch.toString() + '.jpg');
await ref.putFile(imageFile);
return await ref.getDownloadURL();
}
I was expecting print('1') to be executed before print('2'). But the printout order is 2, 0, 1.
0 is the length of the list, which I hope can be full of urls just added.
I just don't know what's wrong with the code. Somebody help me.
Thanks.
Your code will be executed normally till the end and then the callbacks from forEach will start executing, so solve this you have two solutions:
Replace forEach by for loop
Use await Future.forEach(picList, (element) async { var url = await uploadImage(File(element));... });

How do I know if my Firestore transaction failed?

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