Returning values in async functions - Flutter - firebase

Using Cloud Firestore as my DB.
I need to read data from two separate documents in my code, get data from a document based on value in the previous document.
Getting data from firestore in a Future method which looks like this:
Future<List<Map<String, dynamic>>> getGenAlertsData() async{
QuerySnapshot alertRef = await Firestore.instance.collection('alerts').orderBy('time', descending: true).getDocuments();
List<Map<String, dynamic>> messages = new List();
Map<String, dynamic> fullMessage;
// int counter = 0;
alertRef.documents.forEach((doc) async {
// counter++;
String senderKey, senderNameKey, senderSpecKey, senderPicKey, msgBodyKey, timeKey, sender, senderName, senderSpec, msgBody, senderPic;
senderKey = 'sender';
sender = doc['sender'];
timeKey = 'time';
DateTime time = doc['time'];
print(time);
msgBodyKey = 'msgBody';
msgBody = doc['msgBody'];
DocumentReference ref = Firestore.instance.document('/staff/'+sender);
print(ref.documentID);
await ref.get().then((onValue) { //values not populated here :(
senderNameKey = 'senderName';
senderName = onValue['name'];
print(senderName);
senderPicKey = 'senderPic';
senderPic = onValue['profilePic'];
senderSpecKey = 'specialization';
senderSpec = onValue['specialization'];
});
// await print(senderName);
fullMessage = <String, dynamic> {
senderKey: sender,
senderNameKey: senderName,
senderSpecKey: senderSpec,
senderPicKey: senderPic,
timeKey: time,
msgBodyKey: msgBody,
};
messages.add(fullMessage);
print(messages.toString()); // I get the messages printed in my console here
}); //loop over collection ends here
print(messages.toString()); // No data here :(
return messages;
}
At this point only the values from alertRef.documents.forEach((doc) are avaialble, and the values inside await ref.get().then((onValue) { are not populated (they are null in the fullMessage Map) .
Any help will be greatly appreciated.

Yaay! Figured it out finally!
Apparently, then() doesn't work that well inside async functions, now my code looks like this, and everything's great now! :)
Put it all in a good old for loop and everything''s fine now.
Future<List<Map<String, dynamic>>> getGenAlertsData() async{
QuerySnapshot alertRef = await Firestore.instance.collection('alerts').orderBy('time', descending: true).getDocuments();
List<Map<String, dynamic>> messages = new List();
Map<String, dynamic> fullMessage;
// String xxx;
String senderKey, senderNameKey, senderSpecKey, senderPicKey, msgBodyKey, timeKey, sender, senderName, senderSpec, msgBody, senderPic;
List<DocumentSnapshot> alertSnaps= alertRef.documents;
for (int i = 0; i < alertSnaps.length; i++)
{
senderKey = 'sender';
sender = alertSnaps[i]['sender'];
timeKey = 'time';
DateTime time = alertSnaps[i]['time'];
print(time);
msgBodyKey = 'msgBody';
msgBody = alertSnaps[i]['msgBody'];
DocumentSnapshot snappy = await Firestore.instance.document('/staff/'+sender).get();
senderNameKey = 'senderName';
senderName = snappy['name'];
print('Doc for sender' + senderName);
senderPicKey = 'senderPic';
senderPic = snappy['profilePic'];
senderSpecKey = 'specialization';
senderSpec = snappy['specialization'];
fullMessage = <String, dynamic> {
senderKey: sender,
senderNameKey: senderName,
senderSpecKey: senderSpec,
senderPicKey: senderPic,
timeKey: time,
msgBodyKey: msgBody,};
messages.add(fullMessage);
}
return messages;
}

Related

Append element to a list flutter firebase

I'm currently working with FLutter and firebase. I'm trying to upload some data to the cloud firestore. So far so good. I have a problem when I'd like to append to a field a list.
As mentioned before this field contains a list of values defined as below:
Map<String, Object> toDocument() {
Map customerAddress = Map();
Map orderCheckoutDetails = Map();
final DateTime now = DateTime.now();
final DateFormat formatter = DateFormat('dd-MM-yyyy');
final String formatted = formatter.format(now);
customerAddress['address'] = this.address;
customerAddress['city'] = this.city;
customerAddress['state'] = this.state;
customerAddress['zipCode'] = this.zipCode;
orderCheckoutDetails['checkoutOrderDate'] = formatted;
orderCheckoutDetails['customerAddress'] = customerAddress;
orderCheckoutDetails['customerName'] = '${this.nome!} ${this.cognome!}';
orderCheckoutDetails['customerPhone'] = this.numeroTelefono!;
orderCheckoutDetails['products'] = this.products!.map((product) => product.name).toList();
orderCheckoutDetails['subtotal'] = this.subtotal!;
orderCheckoutDetails['deliveryFee'] = this.deliveryFee!;
orderCheckoutDetails['total'] = this.total!;
List<Map<dynamic, dynamic>> orderList = [orderCheckoutDetails];
return {
'orderCheckoutDetails': orderList,
};
}
This is how it shows me the item on Firebase (which is correct).
enter image description here
This is how I upload the document to Firebase.
#override
Future<void> addCheckout(Checkout checkout) async {
print(checkout.email!);
final docExists = await _checkIfUserCheckoutExists(checkout.email!);
print(docExists);
if (!docExists) {
return _checkoutCollection
.doc(checkout.email!)
.set(checkout.toDocument());
} else {
await _checkoutCollection.doc(checkout.email!).update({
"orderCheckoutDetails":
FieldValue.arrayUnion(checkout.toDocument() as List)
});
}
}
What I'd like to do is to append at the end of the document another element (the checkout element passed by parameter). How can I do that?
Your can use set with merge option in both cases (whether the document exists or not), it will create or update the document as needed. Plus your toDocument method should return orderList itself, not the map you are currently returning.
Try this:
Map<dynamic, dynamic> toDocument() {
Map customerAddress = Map();
Map<dynamic, dynamic> orderCheckoutDetails = Map();
final DateTime now = DateTime.now();
final DateFormat formatter = DateFormat('dd-MM-yyyy');
final String formatted = formatter.format(now);
customerAddress['address'] = this.address;
customerAddress['city'] = this.city;
customerAddress['state'] = this.state;
customerAddress['zipCode'] = this.zipCode;
orderCheckoutDetails['checkoutOrderDate'] = formatted;
orderCheckoutDetails['customerAddress'] = customerAddress;
orderCheckoutDetails['customerName'] = '${this.nome!} ${this.cognome!}';
orderCheckoutDetails['customerPhone'] = this.numeroTelefono!;
orderCheckoutDetails['products'] = this.products!.map((product) => product.name).toList();
orderCheckoutDetails['subtotal'] = this.subtotal!;
orderCheckoutDetails['deliveryFee'] = this.deliveryFee!;
orderCheckoutDetails['total'] = this.total!;
return orderCheckoutDetails;
}
And then create / insert your document like:
return _checkoutCollection
.doc(checkout.email!)
.set({
'orderCheckoutDetails' : FieldValue.arrayUnion([checkout.toDocument])
}, SetOptions(merge: true));

Flutter-Firebase read from firestore multiple fields including array

Im trying to read all the fields inside my uid1 and uid2 document from firestore. My code crashes because fromJson cant convert imageList from array to List.
Future<List<AppClient>> getClientListResult(List<String> listId) async{
final List<AppClient> clientList = <AppClient>[];
print(listId);
final QuerySnapshot<Map<String, dynamic>> snapshot = await _firestore.collection('clients').where('uid', arrayContainsAny: listId).get();
final List<AppClient> result = snapshot.docs.map((QueryDocumentSnapshot<Map<String, dynamic>> doc) => AppClient.fromJson(doc.data())).toList();
clientList.addAll(result);
return clientList;
}
you can use rxdart to combine two collection from firestore use this plugin rxdart
sample code
StreamBuilder(
stream: CombineLatestStream.list([
firestore
.collection('users')
.doc(data['id'])
.snapshots(),
firestore
.collection('Image')
.doc(data['id'])
.collection('Photos')
.orderBy('timestap')
.snapshots(),
]),
builder: (context, snapshotid) {
// here for the 1st stream
final active = snapshotid.data[0].data();
// here is the for the second stream
final active1 =
snapshotid.data[1].docs[0].data();
Your AppClient class should look something similar to this:
class AppClient {
String address;
String email;
List<String> imageList;
String name;
String price;
double rating;
List<String> uid;
AppClient(
{this.address,
this.email,
this.imageList,
this.name,
this.price,
this.rating,
this.uid});
AppClient.fromJson(Map<String, dynamic> json) {
address = json['address'];
email = json['email'];
imageList = json['imageList'].cast<String>();
name = json['name'];
price = json['price'];
rating = json['rating'];
uid = json['uid'].cast<String>();
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['address'] = this.address;
data['email'] = this.email;
data['imageList'] = this.imageList;
data['name'] = this.name;
data['price'] = this.price;
data['rating'] = this.rating;
data['uid'] = this.uid;
return data;
}
}

Can't assign a data value to a string - returns null - flutter

In my code, am trying a assign a string value to an empty string and display on the page but it keeps showing null but when I print it out, it shows the value.
String fName = '';
#override
void initState() {
super.initState();
getData();
}
getData() async {
FirebaseAuth _auth = FirebaseAuth.instance;
User _firebaseUser = _auth.currentUser;
print("============ MyHome ================");
print(_firebaseUser.uid);
_currentUser = await Database().getUserData(_firebaseUser.uid);
if (_currentUser != null) {
fName = _currentUser.firstName;
print(_currentUser.firstName);
}
}
database
Future<UserData> getUserData(String uid) async {
UserData returnValue = UserData();
try {
DocumentSnapshot _docSnapshot =
await _firestore.collection("users").doc(uid).get();
returnValue.uid = uid;
returnValue.firstName = _docSnapshot.data()["firstName"];
returnValue.lastName = _docSnapshot.data()["lastName"];
returnValue.userMail = _docSnapshot.data()["userMail"];
returnValue.userType = _docSnapshot.data()["userType"];
print("====================== on getData =============");
print(returnValue.firstName);
} catch (e) {
print(e);
}
return returnValue;
}
And whenever I try displaying the data it gives me null
Text("Hello, $fName"),
Please how do I do this or am I missing something
use setState to rebuild the widget tree with the value:
setState(() {
fName = _currentUser.firstName;
});
Since the getData function is async, flutter has already built the widget tree before getData finished. You'll now have to update the state using setstate.
setState(() {
fName = _currentUser.firstName;
});
You need to set the new state since we have made changes to the previous state (since your getData function is async.
setState(() {
fName = _currentUser.firstName;
});

Getting output as "Instance of 'Future<dynamic>'" in flutter

I am trying get sum of a column in sqlite table using Bloc pattern.
debt_bloc.dart
getTotalAmount() async {
return await _debtRepository.getTotalAmt();
}
debt_dao.dart
Future<int> getTotalAmount() async {
final db = await dbProvider.database;
var result = await db.rawQuery("SELECT SUM(amount) FROM $debtDetailsTable");
int value = result[0]["SUM(amount)"];
return value;
}
debt_repositary.dart
Future getTotalAmount() => debtDao.getTotalAmount();
When i try to print like below
var total;
#override
void initState () {
super.initState();
_asyncMethod();
}
_asyncMethod() async {
var t = await debtBloc.getTotalAmount();
setState(() {
total = t;
});
}
print(total);
Output not updating when add new data. But if go back to home screen and come to respective screen value is updating.
Please guide me in right way. Thanks in advance
Await on your method first before printing it.
var total = await debtBloc.getTotalAmount(); // await here
print(total); // should now print some int value

How do I write tests for a transformed stream in Flutter?

I have a Provider which has a method which takes data from Firebase as a stream, transforms it to a list and returns a Stream<List<Model>> . I'm trying to write a test where I want to check if the items in the List are the same as I expect them to be. How can I do that?
My Current Code:
test('getContacts returns a empty list when there is no contact',() async{
when(sharedPreferencesMock.get(any)).thenReturn('uid'); //mock the sharedprefs
documentSnapshot = DocumentSnapshotMock(); //mock documentsnapshot
when(documentSnapshot.exists).thenReturn(true); // this is done to pass the getUidByUsername method
documentReference = DocumentReferenceMock(documentSnapshotMock: documentSnapshot);
documentReference.setData({
'uid':'uid',
'contacts':[] // setting the usename in the data already so that duplicate contact exception is thrown
});
userDataProvider.getContacts().asBroadcastStream().listen((data){
expect(data.length,0);
});
});
And the provider method
#override
Stream<List<Contact>> getContacts() {
CollectionReference userRef = fireStoreDb.collection(Paths.usersPath);
DocumentReference ref =
userRef.document(SharedObjects.prefs.get(Constants.sessionUid));
return ref.snapshots().transform(StreamTransformer<DocumentSnapshot, List<Contact>>.fromHandlers(handleData: (documentSnapshot, sink) async{
List<String> contacts;
if (documentSnapshot.data['contacts'] == null) {
ref.updateData({'contacts': []});
contacts = List();
} else {
contacts = List.from(documentSnapshot.data['contacts']);
}
List<Contact> contactList = List();
for (String username in contacts) {
print(username);
String uid = await getUidByUsername(username);
DocumentSnapshot contactSnapshot = await userRef.document(uid).get();
contactList.add(Contact.fromFirestore(contactSnapshot));
}
sink.add(contactList);
}));
}
Update:
StreamController streamController = StreamController<List<Contact>>();
StreamSink<List<Contact>> sink = streamController.sink;
Stream<List<Contact>> stream = streamController.stream;
stream.listen((List<Contact> list){
expect(list.length,1);
});
userDataProvider.mapDocumentToContact(userCollection, userRef, documentSnapshot, sink);
streamController.close();
Make the lambda function that you currently pass to the StreamTansformer a separate function and test that.
If you want to test the full function there is a Firebase mock package on pub.

Resources