How to fix issue with firestore collection snapshot - firebase

I'm trying to order my messages collection from Firebase from newest to oldest. But my messages display randomly once after the last and once before the last.
Despite my final who order my collection by reverse
I've tried to find any subject to orders by realtime but I can't find anything about this on Flutter and Firestore. (Maybe I'm blind or I'm not looking well)
class MessagesStream extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _fireStore.collection('messages').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.lightBlueAccent,
),
);
}
final messages = snapshot.data.documents.reversed;
List<MessageBubble> messageBubbles = [];
for (var message in messages) {
final messageSender = message.data['sender'];
final messageText = message.data['text'];
final currentUser = loggedInUser.email;
final messageBubble = MessageBubble(
sender: messageSender,
text: messageText,
isMe: currentUser == messageSender,
);
messageBubbles.add(messageBubble);
}
return Expanded(
child: ListView(
reverse: true,
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
children: messageBubbles,
),
);
},
);
}
}
I add my data into this widget :
FlatButton(
onPressed: () {
messageTextController.clear();
_fireStore.collection('messages').add(
{'text': messageText, 'sender': loggedInUser.email});
},
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),

while adding data you can add a timestamp /server timestamp to your message doc and then use it to arrange your data example "timestamp": Timestamp.now(), then query your data .orderby('timestamp' ,descending :true)

Related

'Future is not a subtype of type 'Widget' - error only present when using Future.wait

I have a string error which I can't seem to debug I think its related to how I'm trying to retrieve data when using Future.wait I get the above error. If I only build a single future and don't use Future.wait and retrieve data using snapshot.data!.data()!["prediction"] I don't get any errors.
Code below for my future.wait
Any help appreciated!
class CorrectMood extends StatefulWidget {
const CorrectMood({Key? key}) : super(key: key);
#override
_CorrectMoodState createState() => _CorrectMoodState();
}
class _CorrectMoodState extends State<CorrectMood> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.wait([getData(), getLatestMood()]),
builder: (context, AsyncSnapshot<List> snapshot) {
snapshot.data![0]; //getLatestMood
snapshot.data![1]; //getData
if (snapshot.hasData) {
return Scaffold(
appBar: AppBar(
title: const Text('Display the Picture'),
backgroundColor: kPrimaryColor,
),
// The image is stored as a file on the device. Use the `Image.file`
// constructor with the given path to display the image.
body: Center(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(8.0, 20.0, 8.0, 8.0),
child: Column(
children: [
Center(
child:
Text(
"${snapshot.data![0]["firstName"]}"
// "${snapshot.data![0].prediction}"
//"${snapshot.data!.data()!["firstName"]} \n\n "
"We have predicted your mood as:\n\n "
//"${DatabaseService.getMood()}\n\n"
"Please select a reason associated to your mood",
style: const TextStyle(
color: Colors.black, fontSize: 15),
textAlign: TextAlign.center,
),
),
countDocuments(),
],
),
),
],
),
),
);
}else {
return CircularProgressIndicator();
}
},
);
}
}
Future<DocumentSnapshot<Map<String, dynamic>>> getData() async {
var currentUser = FirebaseAuth.instance.currentUser;
return await FirebaseFirestore.instance
.collection('USER_TABLE')
.doc(currentUser!.uid)
.get();
}
countDocuments() async {
var currentUser = FirebaseAuth.instance.currentUser;
QuerySnapshot _myDoc = await FirebaseFirestore.instance
.collection('userMoods')
.doc('useerID')
.collection(currentUser!.uid)
.get();
List<DocumentSnapshot> _myDocCount = _myDoc.docs;
return (_myDocCount.length);
}
Future<DocumentSnapshot<Map<String, dynamic>>> getLatestMood() async {
var currentUser = FirebaseAuth.instance.currentUser;
var latestMoodDoc = countDocuments();
return await FirebaseFirestore.instance
.collection('userMoods')
.doc('useerID') // make this the userID
.collection('UbcNaFtJwXWoId9J5RLuVBVPhpN2') // make this increment every time
.doc('2') //this can be system generated name don't care about it
.get();
}

Flutter Firebase return a Querysnapshot and DocumentSnapshot in the same widget

I've spent weeks trying to sort this issue and can't seem to sort it.
I have a database with two collections userTable and userMoods
I have a future builder which is returning the name, however I am querying the userMood table to return the last created document.
I cannot seem to find a way to get this data back out.
Picture of data I am trying to retrieve:
Code is as follows:
class CorrectMood extends StatefulWidget {
const CorrectMood({Key? key}) : super(key: key);
#override
_CorrectMoodState createState() => _CorrectMoodState();
}
class _CorrectMoodState extends State<CorrectMood> {
Future<DocumentSnapshot<Map<String, dynamic>>>? _fetchedData;
#override
void initState() {
super.initState();
_fetchedData = getData();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _fetchedData,
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.hasData) {
return Scaffold(
appBar: AppBar(
title: const Text('Display the Picture'),
backgroundColor: kPrimaryColor,
),
// The image is stored as a file on the device. Use the `Image.file`
// constructor with the given path to display the image.
body: Center(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(8.0, 20.0, 8.0, 8.0),
child: Column(
children: [
Center(
child:
Text(
"${snapshot.data!.data()!["firstName"]} \n\n "
"We have predicted your mood as:\n\n "
//"${DatabaseService.getMood()}\n\n"
"Please select a reason associated to your mood",
style: const TextStyle(
color: Colors.black, fontSize: 15),
textAlign: TextAlign.center,
),
),
],
),
),
],
),
),
);
}else {
return CircularProgressIndicator();
}
},
);
}
}
Future<DocumentSnapshot<Map<String, dynamic>>> getData() async {
var currentUser = FirebaseAuth.instance.currentUser;
return await FirebaseFirestore.instance
.collection('USER_TABLE')
.doc(currentUser!.uid)
.get();
}
Future<QuerySnapshot<Map<String, dynamic>>> getMood() async {
var currentUser = FirebaseAuth.instance.currentUser;
return await FirebaseFirestore.instance
.collection('userMood')
.where('userId' == currentUser!.uid)
.orderBy('createdAt', descending: true)
.limit(1)
.get();
Any help is greatly appreciated!
you can use getData() directly to your FutureBuilder.
by the way I cannot where you are calling getMood() function.

Futurebuilder snapshot has no data

CollectionReference users = FirebaseFirestore.instance.collection('Users');
FirebaseAuth auth = FirebaseAuth.instance;
String uid = FirebaseAuth.instance.currentUser!.uid.toString();
var userData;
var dbFuture;
#override
void initState() {
dbFuture = getData();
super.initState();
}
Future getData() async {
final String uid = FirebaseAuth.instance.currentUser!.uid.toString();
final DocumentSnapshot doc = await users.doc(uid).get();
users.doc(uid).get().then((DocumentSnapshot doc) {
userData = doc.data();
print(doc.data());
});
}
#override
Widget build(BuildContext context) => Scaffold(
body: FutureBuilder(
future: dbFuture,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Container(
child: Text('waiting'),
);
}
if (!snapshot.hasData) {
return Container(
child: Text('error'),
);
}
final data = snapshot.data;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(userData['displayName']),
ElevatedButton(
onPressed: FirebaseAuth.instance.signOut,
child: Text("Log out"))
],
),
);
}),
);
I'm new in Flutter and trying to make an application for managing an academy.
I successfully saved the data at Firestore Cloud, and I can read them with
print(doc.data());
Now I want to build Profile page with those data, so I used Futurebuilder.
But snapshot always has no data.
I read documents as well, but still have no idea.
To get your data from Firebase and display them in your widgets, you have two ways, but you have to choose only one according to your needs.
With FutureBuilder()
This code will call your database and load the info you request at each build and at each setState() (responsible for updating your interface content). It could be useful for some data types, but in your case your redundant Firebase calls could cost you.
CollectionReference users = FirebaseFirestore.instance.collection('Users');
final auth = FirebaseAuth.instance;
late final uid = auth.currentUser!.uid;
#override
Widget build(BuildContext context) => Scaffold(
body: FutureBuilder(
future: users.doc(uid).get(),
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Container(
child: Text('waiting'),
);
}
if (!snapshot.hasData) {
return Container(
child: Text('error'),
);
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(snapshot.data!['some_data']),
Text(auth.currentUser?.displayName ?? 'user have no name'),
ElevatedButton(
onPressed: auth.signOut,
child: Text("Log out"))
],
),
);
}),
);
In the initState()
When using initState(), the code inside is called only once. To refresh the content, you will have to call getData() manually (in a setState() for example)
CollectionReference users = FirebaseFirestore.instance.collection('Users');
final auth = FirebaseAuth.instance;
late final uid = auth.currentUser!.uid;
String? someData;
#override
void initState() {
getData();
super.initState();
}
Future<void> getData() async {
users.doc(uid).get().then((doc) {
someData = doc['some_data'];
});
}
#override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(someData ?? 'no data'),
Text(auth.currentUser?.displayName ?? 'user have no name'),
ElevatedButton(
onPressed: auth.signOut,
child: Text("Log out"))
],
),
),
);
Finally, if your users authenticate, you can use auth.currentUser?.displayName and auth.currentUser!.updateDisplayName('new name') to simply get and change your users' names.

how to get document id Firestore

I am new to Firestore. I am stuck at getting the document ID from Firestore.
Here I create the user with name and age parameters.
When I click Submit, it submits to Cloud Firestore. Everything is Ok up to now.
class _MyHomePageState extends State<MyHomePage> {
late final TextEditingController name = TextEditingController();
late final TextEditingController age = TextEditingController();
final DBHelper _helper = DBHelper();
late final User _user=User();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: name,
decoration: const InputDecoration(hintText: "Name"),
),
TextField(
controller: age,
decoration: const InputDecoration(hintText: "Age"),
),
ElevatedButton(onPressed:(){
_user.setAge(age.text);
_user.setName(name.text);
print("Name: ${_user.name}");
_helper.addUser(_user);
Navigator.push(context, MaterialPageRoute(builder: (builder)=>MainPage( ,))); //here i need to push the document id
}, child: const Text("Submit"))
],
),
),
);
}
This is my custom User model
class User{
late final String userId;
final String name;
final String age;
User({required this.name, required this.age});
}
This is my service class for Firestore methods
class DBHelper{
// final User user = User();
final CollectionReference _reference= FirebaseFirestore.instance.collection("users"); // database path
//Adding user to Firestore
Future<void> addUser(User user){
return _reference.add({
'name':user.name,
'age':user.age,
}).then((value) => print("User added"))
.catchError((onError)=> print("Failed to add the user: $onError"));
}
}
Here is what I want: When I click on the submit button in the previous page. It will navigate to the following page. In this page, I want to show the "name" and "age" information. The only thing that is missing is the documentId
return FutureBuilder<DocumentSnapshot>(
future: _ref.doc(documentId).get(), //which document doesn't know
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return const Center(
child: Text(
"Bir hata oluştu.",
textScaleFactor: 3,
));
} else if (snapshot.hasData && !snapshot.data!.exists) {
return const Center(
child: Text(
"Döküman yok",
textScaleFactor: 3,
));
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data =
snapshot.data!.data() as Map<String, dynamic>;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Name: ${data['name']}",
textScaleFactor: 3,
),
Text(
"Age:${data['age']}",
textScaleFactor: 3,
),
],
)),
);
} else {
return const Center(child: CircularProgressIndicator());
}
});
you can get the id on the metodh you used to submit infos
instead of return void,return a String(the id) then go to the next screen
like that:
Future<String> addUser(User user)async {
return await _reference.add({
'name':user.name,
'age':user.age,
}).then((value) => var documentId=value.id)
and the button function should be:
ElevatedButton(onPressed:(){
_user.setAge(age.text);
_user.setName(name.text);
print("Name: ${_user.name}");
var id=await _helper.addUser(_user);
Navigator.push(Get.context!, MaterialPageRoute(builder:(builder)=>MainPage(documentI: id)));
}, child: const Text("Submit"))
],
),

I am trying to make a grocery app using flutter and firebase, everything is working but when I press the checkbox it Checks all of them

I made a floatingactionbutton and every time you press it it adds an item, and each item has a checkbox next to it but when I check off one item it checks all of them, I've spent a lot of time trying to figure out how to fix this but I can't. I could really use your help.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(FireApp());
}
class FireApp extends StatefulWidget {
#override
_FireAppState createState() => _FireAppState();
}
bool isChecked = false;
class _FireAppState extends State<FireApp> {
final TextController = TextEditingController();
#override
Widget build(BuildContext context) {
CollectionReference groceries =
FirebaseFirestore.instance.collection('groceries');
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: TextField(
controller: TextController,
),
),
body: Center(
child: StreamBuilder(
stream: groceries.orderBy('name').snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
return ListView(
children: snapshot.data!.docs.map((grocery) {
return Center(
child: Row(
children: [
Container(color: Colors.red,height: 50,child: Text(grocery['name'])),
Checkbox(
materialTapTargetSize: MaterialTapTargetSize.padded,
value: isChecked,
activeColor: Colors.black,
checkColor: Colors.greenAccent,
onChanged: (bool) {
setState(() {
isChecked = !isChecked;
});
}
)],
),
);
}).toList(),
);
},
),
),
floatingActionButton: FloatingActionButton(onPressed: () {
groceries.add({
'name': TextController.text,
});
},),
),
);
}
}
You are using the same variable for all your checkboxes (isChecked) but you ougth to have one per data, you could add that attribute to your firebase document so its synced or you could create it locally but each time your stream updates you will need to compare what grocery correspond to a checkbox value which can be hard.
UPDATE
The easiest way is to have a bool parameter in your Firestore document
Then just push an update any time the user tap
return ListView(
children: snapshot.data!.docs.map((grocery) {
return Center(
child: Row(
children: [
Container(color: Colors.red,height: 50,child: Text(grocery['name'])),
Checkbox(
materialTapTargetSize: MaterialTapTargetSize.padded,
value: grocery['checked'],
activeColor: Colors.black,
checkColor: Colors.greenAccent,
onChanged: (val) async {
final data = grocery.data();
data['checked'] = val;
await grocery.reference.update(data);
}
)],
),
);
}).toList(),
);
For now this is sufficient to answer your question, you will see later that this incurs in more Firestore calls, unnecesary rebuild of all widgets in the list and so on and you will have to think another way to optimize resources, like watching the stream somewhere else to have a local List of bools that keeps in sync all values of the groceries so you only update locally with an setState and once in the cloud at the end (a save button perhaps)

Resources