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.
Related
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();
}
}
);
);
}
In my application a lot of data received from the api, how to save data on device when app starts, and take this data from device and take data from the device without making an api call every time.
I can show on the example of my futureBuilder, every time when I go to UserPage, my user data is loaded, how can I save it?
late Future<User> userFuture = getUser();
static Future<User> getUser() async {
var url = '${Constants.API_URL_DOMAIN}action=user_profile&token=${Constants.USER_TOKEN}';
print(Constants.USER_TOKEN);
final response = await http.get(Uri.parse(url));
final body = jsonDecode(response.body);
return User.fromJson(body['data']);
}
FutureBuilder<User>(
future: userFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return buildProfileShimmer();
// Center(child: CircularProgressIndicator());
} else if (snapshot.hasData) {
final user = snapshot.data!;
return buildUser(user);
} else {
return Text("No widget to build");
}
},
)
I have created a group screen in my flutter app and when I hit the create button it create a group as described is code:
//Create group
Future<String> createGroup(String groupName1, userUid) async {
String retVal = "error";
List<String> members = [];
try {
members.add(userUid);
DocumentReference docRef;
docRef = await firestore.collection("groups").add({
'name': groupName1,
'leader': userUid,
'members': members,
'groupCreate': Timestamp.now(),
});
retVal = "success";
} catch (e) {
// ignore: avoid_print
print(e);
}
return retVal;
}
I am stuck in getting the snapshot from my FBbackend. I want to get the info and display is on my group screen. How can I achieve that?
I have try to get the snapshot but it says Text("..."). I think the problem is that the value of DocumentReference? docRef; is empty, but I don’t know how to fixed it.
code:
DocumentReference? docRef;
docRef == null
? const Text('Error No Group Name Found!!')
: // 👈 handle null here
StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection('groups')
.doc(docRef!.id)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.data == null) {
return const ErrorScreen();
}
return Center(
child: Text((snapshot.data
as DocumentSnapshot<
Map<String, dynamic>>)['name']),
);
}),
Sorry if it's a stupid question I am beginner in Flutter and MongoDB Here is my code to return collection data btw this is the only time I use Mongo_Dart all other operations done using JS on heroku
class Azkar {
getAzkar() async {
var db = await Db.create(
'mongodb+srv://Adham:<password>#cluster0.nm0lg.mongodb.net/<db>retryWrites=true&w=majority');
await db.open();
print('Connected to database');
DbCollection coll = db.collection('zekrs');
return await coll.find().toList();
}
}
It is working and I am able to print returned data from another class it is List<Map<String, dynamic>> I want to know how should I use it to generate ListTile with all data.
This package is not worth it. I solved this issue by moving out this part of code on the backend side (NodeJS) in the cloud and just getting what I need with an HTTP request.
Instead of returning data in List<Map<String, dynamic>>, create a class for your data. Suppose your data gives us a list of users. Then
class User {
User({
this.id,
this.name,
});
int id;
String name;
}
This would be your Azkar class
class Azkar {
getAzkar() async {
final db = await Db.create(
'mongodb+srv://Adham:<password>#cluster0.nm0lg.mongodb.net/<db>retryWrites=true&w=majority');
await db.open();
print('Connected to database');
final coll = db.collection('zekrs');
final zekrsList = await coll.find().toList();
List<User> users = [];
for (var item in zekrsList) {
final user = User(
id: item['id'],
name: item['name'],
);
users.add(user);
}
return users;
}
}
You should do something like this.
FutureBuilder(
future: getAzkar(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Container(
margin: EdgeInsets.all(8),
child: Column(
children: [
Text("Name = ${snapshot.data[index].name}"),
Text("Id = ${snapshot.data[index].id}"),
],
),
);
});
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
),
if anyone still have this issue,
I solved it by setting this:
final zekrsList = await coll.find().toList();
to
final zekrsList = await coll.find(where.sortBy('_id')).toList();
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()