Flutter - How can I get the Firestore items that contain their id in an array in another table as snapshots? - flutter

How can I get Firestore items containing their id in an array in another table as snapshots in Flutter? I am attaching the code that I have that works perfectly for me doing a "get", but I can't find a way to convert this to Stream and print it on the screen with the StreamBuilder instead of with the FutureBuilder and update it with each change
Future<List<DocumentSnapshot<Map<String, dynamic>>>?> getPools() async {
List<DocumentSnapshot<Map<String, dynamic>>> pools = [];
final user = FirebaseAuth.instance.currentUser;
final DbUser? dbUser = await dbUserAPI.getDbUser(user);
if (dbUser != null) {
for (var pool in dbUser.pools) {
final result = await FirebaseFirestore.instance
.collection('pools')
.doc(pool)
.get();
pools.add(result);
}
return pools;
}
if (kDebugMode) {
print('Error al leer el usuario de FireStore');
}
return null;
}
In the dbUsersAPI.getDbUsers function I retrieve the user data from the "Users" table and then I get the value ".pools", which is an array of Strings with the ids of the items I want to retrieve.
I have tried many ways and to play with Streams but I am always getting a Future or Stream when I only want to get a Stream of the items that I am filtering.
I have tried with the where clause but it does not update the values. I think the problem is that I don't know how to manage the Future returned by the dbUsersAPI.getDbUsers function.

Well for displaying data using a StreamBuilder you need to fetch data in streams by generating a requests that ends with .snapshots() method instead of a .get() method.
A pretty simple scenario will be,
DbUser? dbUser;
getDbUser() async {
final user = FirebaseAuth.instance.currentUser;
final DbUser? _dbUser = await dbUserAPI.getDbUser(user);
if(_dbUser != null){
dbUser = _dbUser;
}
}
#override
void initState(){
getDbUser();
super.initState();
}
#override
void build(BuildContext context){
return ListView.builder(
itemCount: dbUser!.pools.length,
itemBuilder: (context, index){
final pool = dbUser!.pools[index];
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('pools')
.doc(pool)
.snapshots(),
builder: (context, snapshots){
return Container();
}
}
);
);
}

Related

StreamBuilder doesn't updates UI when Firestore data changes

My goal:
I want to retrieve a list of documents from the Firebase Firestore using a Stream to update the interface in real time when the Firestore data changes.
The problem:
I am able to download the data from Firestore with the structure I need, but when I make changes in firestore the interface does not update in real time. When I reload the page, it updates, but that is not the behavior I need.
This is the Stream I have created:
Stream<DayModel> getInstances(String selectedDay, String userUid) async* {
DayModel retval = DayModel();
List<InstanceModel> instances = [];
int index = 0;
try {
final QuerySnapshot<Map<String, dynamic>> querySnapshot =
await FirebaseFirestore.instance
.collection('instances')
.doc(selectedDay)
.collection('instancesUid')
.where("instanceUsersUid", arrayContains: userUid)
.get();
instances = querySnapshot.docs
.map((instance) => InstanceModel.fromSnapshot(instance))
.toList();
for (InstanceModel instance in instances) {
final DocumentSnapshot<Map<String, dynamic>> instanceQuery =
await FirebaseFirestore.instance
.collection('instances')
.doc(selectedDay)
.collection('instancesUid')
.doc(instance.uid)
.get();
instance = InstanceModel.fromMap(instanceQuery);
instances[index] = instance;
index++;
}
retval.instances = instances;
yield retval;
} on Exception catch (e) {
print(e);
}
}
StreamBuilder code:
body: StreamBuilder<DayModel>(
stream:
OurDatabase().getInstances(selectedDay, _currentUser!.uid!),
builder:
(BuildContext context, AsyncSnapshot<DayModel> snapshot) {
if (snapshot.hasError) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
return Center(
child: snapshot.data!.instances!.isNotEmpty
? Text(snapshot.data!.instances![0].uid!)
: Text('No tienes instancias!'),
);
})
Maybe it's because I'm not returning the Stream with a QuerySnapshot?
I have read in other similar posts that it could be a problem with the keys, but I have tried several different combinations and it has not worked.
Do you have any idea what could be happening?
Thank you for your time.

how to retrive value from a firestore flutter where query

I started flutter recently, and I try to retrieve the data from a query I made using 'where' , but the only thing I got back is "Instance of '_JsonQueryDocumentSnapshot'".
I tried different thing , but nothing work or i do it badly
this is my code :
CollectionReference users =
FirebaseFirestore.instance.collection('users');
final documents =
await users.where("username", isEqualTo: "username").get();
documents.docs.forEach((element) {
print(element);
});
I have also tried to use Future but without success :
class finduser extends StatelessWidget {
final String username;
finduser(this.username);
#override
Widget build(BuildContext context) {
CollectionReference users = FirebaseFirestore.instance.collection('users');
return FutureBuilder(
future: users.where('username', isEqualTo: '${username}').get(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
print("wrong");
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
print("doesnt exist");
return Text("User does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data! as Map<String, dynamic>;
print(snapshot.data!);
return Text("${data}");
}
return Text("loading");
},
);
}
}
for the moment, all usernames are just "username"
Thank you for the help
When you get your documents like this :
CollectionReference users =
FirebaseFirestore.instance.collection('users');
final documents =
await users.where("username", isEqualTo: "username").get();
documents.docs.forEach((element) {
print(element);
});
You are trying to print an instance of a QueryDocumentSnapshot
This QueryDocumentSnapshot has a method .data() which returns a Map<String,dynamic> aka JSON.
So in order to print the content of your Document, do this :
documents.docs.forEach((element) {
print(MyClass.fromJson(element.data()));
});
This data by itself will not be very useful so I recommend creating a factory method for your class :
class MyClass {
final String username;
const MyClass({required this.username});
factory MyClass.fromJson(Map<String, dynamic> json) =>
MyClass(username: json['username'] as String);
}
Now you can call MyClass.fromJson(element.data()); and get a new instance of your class this way.
I have searched a lot but i see you have written code right.
The only thing that came to my mind is that you didn't initialize your firebase to your flutter project (you should do it in any flutter project to be able to use flutter).
link of the document:
https://firebase.flutter.dev/docs/overview#initializing-flutterfire
In your first code snippet you are printing element, which are instances of the QueryDocumentSnapshot class. Since you're not accessing specific data of the document snapshot, you get its default toString implementation, which apparently just shows the class name.
A bit more meaningful be to print the document id:
documents.docs.forEach((doc) {
print(doc.id);
});
Or a field from the document, like the username:
documents.docs.forEach((doc) {
print(doc.get("username"));
});
Run this code, it will work.
I also faced this similar problem, so I used this work around.
Map<String, dynamic> data = {};
FirebaseFirestore.instance.collection('users').where("username", isEqualTo: "username").get().then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((value){
data = value.data()!;
print('printing uid ${data['uid']}');
print('printing username--${data['username']}');
print('printing all data--$data');
});
});

Flutter SqlBrite not rebuilding streambuilder list

I am trying to build a chat storage system with firebase, sqlite and sqlBrite.
The aim of this is to stream the newmessages without having to rebuild the page. The stream from sqlBrite is only rebuilding on setstate eg.when the keyboard is drawn back.
How can i get the stream to automatically update on save.
The db document
///INSERT INTO DB
Future<int> insertNewMessage(String id, int result, BriteDatabase briteDb,
Map<String, dynamic> row) async {
messageList.add(id);
await ifexists(id, messageId, briteDb)
? print('message already In')
: result = await briteDb.insert(messageTable, row);
return result;
}
////STREAM MESSAGES
Stream<List<Map<String, dynamic>>> getMessageMapListbyId(
{String sendId, String receiveId, database}) async* {
try {
BriteDatabase briteDb = await database;
yield* briteDb.createQuery(messageTable,
distinct: false,
where:
' $senderId=? $receiverId = ? ',
whereArgs: [
sendId,
receiverId,
])});
provider document
///ADD MESSAGES
addMessageTodb(message) async {
await ldbH
.msg_insertMessage(
message.id, modelFuncs.messageMaping(message, msgFuncs))
.then((value) async {
await getMessageYieldBase(message.senderId, message.receiverId);
});}
///STREAM NEW DATA
getMessageYieldBase(senderId, receiverId) async* {
yield* ldbH.msg_getAllMessagesbyId(senderId, receiverId);}
The ui side
StreamBuilder(
stream: messageStream.getMessageYieldBase(
widget._currentUserId, widget.receiver.uid),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
var d = snapshot.data;
var newList = snapshot.hasData ? d.reversed.toList() : [];
return
ListView.builder(
reverse: true,
padding: EdgeInsets.all(10),
controller: widget._listScrollController,
itemCount: newList.length,
itemBuilder: (BuildContext context, int index) {
return DisplayMessage(
currentUserId: widget._currentUserId,
receiver: widget.receiver,
message: newList[index],
);
});
})
So the new texts keep coming only when the page rebuilds in some sort of way.
Any help rendered is appreciated.
If you are facing this problem use the moor library sqlbrite won't work but this is a link to help....
https://resocoder.com/2019/06/26/moor-room-for-flutter-tables-queries-fluent-sqlite-database/
Matt Rešetár explains in detail so it will be easy to implement...

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."

How to order Firestore documents in list form based on int values in flutter

I currently have a list of documents that each contain an int value called plastics. Currently, the list only displays the documents in order by when it was added to the collection, but I want to be able to order the documents based on the int value within each one. I've looked around on the web and I've only found tutorials mostly on ordering timestamps. Is there any documentation or sources on this matter? Here is the code situation I'm working with:
Firstly, in my app users can join groups, and when they do so they bring along their name and int data which is then stored in documents for each user.
Future<String> joinGroup(String groupId, String userUid, String displayName,
String plastics) async {
String retVal = 'error';
List<String> members = List();
try {
members.add(displayName);
await _firestore.collection('Groups').doc(groupId).update({
'members': FieldValue.arrayUnion(members),
});
final uid = FirebaseAuth.instance.currentUser.uid;
await _firestore.collection('UserNames').doc(uid).update({
'groupId': groupId,
});
//Below me is the code for doing so
await _firestore
.collection("Groups")
.doc(groupId)
.collection("Members")
.doc(userUid)
.set({'displayName': displayName, 'plastics': plastics});
retVal = 'success';
} catch (e) {}
return retVal;
}
I then take that code access the documents and put them in a list.
#override
Widget build(BuildContext context) {
final CollectionReference users = firestore.collection('UserNames');
final String uid = auth.currentUser.uid;
return FutureBuilder(
future: users.doc(uid).get(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final result = snapshot.data;
final groupId = result.data()['groupId'];
return FutureBuilder<QuerySnapshot>(
// <2> Pass `Future<QuerySnapshot>` to future
future: FirebaseFirestore.instance
.collection('Groups')
.doc(groupId)
.collection('Members')
.get(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// <3> Retrieve `List<DocumentSnapshot>` from snapshot
final List<DocumentSnapshot> documents = snapshot.data.docs;
return ListView(
children: documents
.map((doc) => Card(
child: ListTile(
title: Text(doc['displayName']),
subtitle: Text(doc['plastics'].toString()),
),
))
.toList());
} else if (snapshot.hasError) {
return Text('Its Error!');
}
});
}
});
}
Is there a specific function needed so that the documents in the Member collection are listed based on the numerical value of the plastics?
You can use orderBy to sort your results.
FirebaseFirestore.instance
.collection('Groups')
.doc(groupId)
.collection('Members')
.orderBy('plastics', descending: true)
.get()