How to get firestore data as stream? - flutter

I am making a collection group query, where upon matching a particular field, I am going a level backwards and then read the data.
I am able to do that in Future approach.
Here is my code returning future:
#override
Future<Either<JobPostFailure, List<JobPost>>> readAppliedJobPosts({
required String seamanId,
}) async {
final querySnapshot = await FirebaseFirestore.instance
.collectionGroup(ConstStrings.applications)
.where(
ConstStrings.seamanId,
isEqualTo: seamanId,
)
.get();
final List<JobPost> mList = [];
for (var docSnap in querySnapshot.docs) {
final jobPostDocSnap = await docSnap.reference.parent.parent?.get();
mList.add(JobPostDto.fromFirestore(jobPostDocSnap!).toDomain());
}
return right(mList);
}
Now I am struggling to do this in Stream approach, where my return type would be something like this : Stream<Either<JobPostFailure, List<JobPost>>>. What is the equivalent of my above code in Stream?
My try so far :
#override
Stream<Either<JobPostFailure, List<JobPost>>> watchAppliedJobPosts({
required String seamanId,
}) async* {
yield* _firestore
.collectionGroup(ConstStrings.applications)
.where(
ConstStrings.seamanId,
isEqualTo: seamanId,
)
.snapshots()
.map((event) {
return event.docs.map((e) {
return e.reference.parent.parent!.snapshots().map((event) {
return right(JobPostDto.fromFirestore(event).toDomain());
}).toList();
});
});
}
And its a big mess!

You can use method snapshots instead of get. Is will create a new stream that will fetch data for every change your document or collection has

Related

How to return a Stream using an async* function in Flutter

I am working on a chat app using Flutter and Firebase. I am new to Dart and so got stuck when I wanted to create a function which fetches (using await) a particular document from one collection (forums) and use an array property of the forum document to query and return a Stream from another collection (openMessages). The problem with my current solution is that it always returns an empty array. I am sure I am using the keywords or logic incorrectly. Can you please help me refactor my method.
Stream<List<ChatMessage>> getForumChatStream(String forumId) async* {
List<ChatMessage> messages = [];
var docSnap = await firestore.collection('forums').doc(forumId).get();
Forum forum = Forum.fromMap(docSnap.data()!);
firestore
.collection('openMessages')
.where('messageId', whereIn: forum.messageIds)
.orderBy('timeSent', descending: true)
.snapshots()
.map((event) {
for (var document in event.docs) {
messages.add(ChatMessage.fromMap(document.data()));
}
});
//print('LENGTH:'+messages.length.toString());
yield messages;}
You can use the following method.
Stream<List<ChatMessage>> getForumChatStream(String forumId) async* {
final firestore = FirebaseFirestore.instance;
List<ChatMessage> messages = [];
var docSnap = await firestore.collection('forums').doc(forumId).get();
Forum forum = Forum.fromMap(docSnap.data()!);
final result = firestore
.collection('openMessages')
.where('messageId', whereIn: forum.messageIds)
.orderBy('timeSent', descending: true)
.snapshots();
await for (final r in result) {
final docs = r.docs;
for (final document in docs) {
messages.add(ChatMessage.fromMap(document.data()));
yield messages;
}
}
}
Or
Stream<List<ChatMessage>> getForumChatStream(String forumId) async* {
final firestore = FirebaseFirestore.instance;
List<ChatMessage> messages = [];
var docSnap = await firestore.collection('forums').doc(forumId).get();
Forum forum = Forum.fromMap(docSnap.data()!);
yield* firestore
.collection('openMessages')
.where('messageId', whereIn: forum.messageIds)
.orderBy('timeSend', descending: true)
.snapshots()
.map((event) =>
event.docs.map((e) => ChatMessage.fromMap(e.data())).toList());
}

Listen to document changes on Firestore and create a stream of objects for StreamBuilder

I have the following function which gets a product document from Firestore, to be used with a FutureBuilder:
Future<Product> getProduct(String productId) async {
var ref = _db.collection('products').doc(productId);
var snapshot = await ref.get();
return Product.fromJson(snapshot.data() ?? {});
}
I can I achieve the same functionality, but with a StreamBuilder so that the build method will be called any time there's a change to the document?
Stream<Product> listenToProduct(String productId) {
?
};
Well, this seems to work:
Stream<Product> listenToProduct(String productId) {
return _db.collection('products')
.doc(productId)
.snapshots()
.map((snapshot) => Product.fromJson(snapshot.data()!));
}
Stream<Product> listenToProduct(String productId) {
return _db.collection('products').doc(productId).snapshots()
.listen((event) => Product.fromJson(event.data() ?? {});
}
Something similar to that.

How to get a subcollection from a document firestore flutter

Im trying to fetch the documents from a subcollection which is in another document, and when I try to fetch the docs and fill a local list with the "docs data" it doesn't fill it, can anyone tell me what I'm doing wrong here?
My method of when I try to fetch the subcollection:
static Stream<List<CheckInOutModel>> employeeCheckInOutStream() {
return firebaseFirestore
.collection('employees')
.doc(auth.currentUser!.uid)
.collection('employeeList')
.snapshots()
.asyncMap((QuerySnapshot querySnapshot) {
final List<CheckInOutModel> employeesCheckInOutList = [];
for (final element in querySnapshot.docs) {
firebaseFirestore
.collection('employees')
.doc(auth.currentUser!.uid)
.collection('employeeList')
.doc(element.id)
.collection('checkInOutList')
.snapshots()
.asyncMap((QuerySnapshot query) {
for (final element in query.docs) {
final employeeCheckInOutModel =
CheckInOutModel.fromDocumentSnapshot(
documentSnapshot: element,
);
employeesCheckInOutList.add(employeeCheckInOutModel);
}
});
}
return employeesCheckInOutList;
});
}
My method when I fetch the fields of the documents that the subcollection is in:
static Stream<List<EmployeeModel>> employeeStream() {
return firebaseFirestore
.collection('employees')
.doc(auth.currentUser!.uid)
.collection('employeeList')
.snapshots()
.map((QuerySnapshot query) {
final List<EmployeeModel> employees = [];
for (final employee in query.docs) {
final employeeModel =
EmployeeModel.fromDocumentSnapshot(documentSnapshot: employee);
employees.add(employeeModel);
}
return employees;
});
}
So I figured out what I did wrong here, I tried to call a stream of it when I only needed it when a callBack is called, so I changed the logic accordingly and went with Future instead Stream
My updated code:
static Future<List<CheckInOutModel>> employeeCheckInOutStream({
required String id,
}) async {
final List<CheckInOutModel> employeesCheckInOutList = [];
final query = await firebaseFirestore
.collection('employees')
.doc(auth.currentUser!.uid)
.collection('employeeList')
.doc(id)
.collection('checkInOutList')
.get();
for (final employee in query.docs) {
final employeeCheckInOutModel = CheckInOutModel.fromDocumentSnapshot(
documentSnapshot: employee,
);
employeesCheckInOutList.add(employeeCheckInOutModel);
}
return employeesCheckInOutList;
}

Get value from one future and use it in another future, with Flutter

I have a favourites collection saved under a users collection. Each of the favourite documents has one field which contains a product_Id. I want to retrieve this product_id value and use it to query another collection. This second collection holds the actual products documents.
Retrieving all the documents in the favourite collection. What do I do next to get the value of the product_id fields as strings?
getIdsfromUserFavs(userId) async {
var _favData = await _usersCollectionReference
.doc(userId)
.collection('favourites')
.get();
}
This is the second method that is used to query the products collection. This method needs the String value from above in order to successfully make the query.
Future<QuerySnapshot<Object?>> queryFavsCollection(value) async {
var _favedProducts = await _productsCollectionReference
.where('prod_id', isEqualTo: value)
.get();
print(value);
return _favedProducts;
}
I am using a futureBuilder in the UI.
THis is one way I have tried(The problem with this is that I don't get any data returned):
getIdsfromUserFavs(userId) async {
var _favData = await _usersCollectionReference
.doc(userId)
.collection('favourites')
.get();
var allData = _favData.docs.map((doc) => doc.data()).toList();
allData.forEach((element) async {
String value = element['prod_id'];
print(value);
await queryFavsCollection(value);
});
}
Future<QuerySnapshot<Object?>> queryFavsCollection(value) async {
var _favedProducts = await _productsCollectionReference
.where('prod_id', isEqualTo: value)
.get();
print(value);
return _favedProducts;
}
I can see that the above methods print the ids to the console. But the FutureBuilder doesn't receive any data:
I/flutter ( 4628): 3nHHEWuCDXvbhYfT8ljY
I/flutter ( 4628): MptYFV1oXhflDYkdQyIP
I/flutter ( 4628): Fd2ntXyNVmjn0D6mG3RA
Below function will return all data from favourite collection
Future<QuerySnapshot<Map<String, dynamic>>> getIdsfromUserFavs(userId) async {
QuerySnapshot<Map<String, dynamic>> _favData = await _usersCollectionReference
.doc(userId)
.collection('favourites')
.get();
return _favData; // This will return all data of favourite collection
}
After that you can return List of desire data as shown in below function
Future<List<QueryDocumentSnapshot<Map<String, dynamic>>>> queryFavsCollection(userId) async {
// Will store data in this list so at the end we can return this
List<QueryDocumentSnapshot<Map<String, dynamic>>> favData = [];
QuerySnapshot<Map<String, dynamic>> _favData =
await getIdsfromUserFavs(userId);
for (QueryDocumentSnapshot<Map<String, dynamic>> data in _favData.docs) {
String value = data['prod_id'];
QuerySnapshot<Map<String, dynamic>> _fav = await
_productsCollectionReference
.where('prod_id', isEqualTo: value)
.get();
if (_fav.docs.isNotEmpty) {
_fav.docs.forEach((element) {
favData.add(element);
});
}
}
return favData;
}
Now you can use FutureBuilder as shown below
FutureBuilder<List<QueryDocumentSnapshot<Map<String, dynamic>>>>(
future: queryFavsCollection(userId),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text('Loading...');
}
return Text('you data');
},
);
For better practice kindly refer this. This is from flutter documentation
"The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted."

wait for firestore documents in for loop

trying to fetch results within a for loop , but for loop doesn't wait for firestore results.tried forEach as well before .
Future<bool> checkIfNewMessages() async{
bool hasNewMessage=false;
QuerySnapshot _myDoc = await Firestore.instance.collection('PropMap')
.orderBy('ProJoiningDate')
.where('TenId', isEqualTo: globals.memberAuthId)
.getDocuments();
List<DocumentSnapshot> properties = _myDoc.documents;
if(properties.length>0)
for(final property in properties) {
//properties.forEach((property) { //tried forEach() as well
String propid= property.data['PropertyId'];
if(property.data['LastVisitTime']!=null) {
DateTime tenantsLastPropVisitTime = property.data['LastVisitTime'].toDate();
getLastPropertyChatTime(propid).then((latestPropChatTime) { //This 'then' seems not working
print('LAST chat date is ${latestPropChatTime}');
if (latestPropChatTime.isAfter(tenantsLastPropVisitTime)) //This means he has not seen new messages , lets notify him
{
hasNewMessage= true;
}
});
}
};
return hasNewMessage;
}
And these are the fetch methods,when the breakpoint is at getDocuments() of getTheLastChat() the control just jumps back to for loop again without waiting for results .
Future getTheLastChat(propId) async {
QuerySnapshot _myDoc =await Firestore.instance.collection('Chats').orderBy('ChatDate', descending: true)
.where('PropertyId', isEqualTo: propId)
.limit(1)
.getDocuments();
List<DocumentSnapshot> tenants = _myDoc.documents;
return tenants;
}
Future<DateTime> getLastPropertyChatTime(propId) async {
DateTime lastChatTime= DateTime.now().add(Duration(days:-365));
var lastChatTimeDocs = await getTheLastChat(propId);
lastChatTime=lastChatTimeDocs.length>0?lastChatTimeDocs[0].data["ChatDate"].toDate():DateTime.now().add(Duration(days:-365));
return lastChatTime;
}
You can use the Future.forEach to achieve your requirement
Future<void> buildData(AsyncSnapshot snapshot) async {
await Future.forEach(snapshot.data.documents, (element) {
employees.add(Employee.fromSnapshot(element));
});
}