I'm trying to display data from an array in firestore. I displayed it, but only [0] in the array is showing. I'm trying to get all the data in the array to show.
builder: (_, AsyncSnapshot<List<DocumentSnapshot>> snapshot){
if(snapshot.hasData){
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: ((_, index) {
List<Widget> tiles = [];
for (Map post in snapshot.data![index]['posts']) {
tiles.add(
Expanded(
child: Container(
margin: EdgeInsets.all(2),
padding: EdgeInsets.all(1),
decoration: BoxDecoration(border: Border.all(color:Colors.black)),
child: Center(
child: ListTile(
title: Text(post['postText'], style: TextStyle(color: Colors.white),),
subtitle: Text(post['fromUser'], style: TextStyle(color: Colors.white),),
),
),
),
)
);
}
return Expanded(
child: ListView(
children: tiles,
),
);
}),
);
}
else{
return Center(child: CircularProgressIndicator(),);
}
},
enter image description here
Edit
To answer your qn about newest to oldest:
I suggest you put a FieldValue.timestamp field in your group chat documents! Then, you can order them like this:
Future<List<DocumentSnapshot>> getDoc(groupID) async {
var firestore = FirebaseFirestore.instance;
QuerySnapshot qn = await firestore.collection('groups')
.where('groupChatId', isEqualTo: groupID)
.orderBy('timestamp', descending: true) // <- Here!
.get();
return qn.docs;
}
(All of that I copied by hand, since you hadn't provided this code as text, as I asked you to!... 😆)
If you don't have a timestamp field, there is a way to still find out when a document was created... but I don't know how. Plus, in this case, I guess you want the time a certain FIELD was created in the document...! I don't know if that's possible. In fact, for that you'll probably have to do:
List<Map> posts = snapshot.data![index]['posts'];
// Sort list according to the 'date' field in each Map in the list:
posts.sort((mapA, mapB){
return mapA['date'].compareTo(mapB['date']);
});
// Then you'll use posts in your for-loop instead of snapshot.data![index]['posts']:
for (Map post in posts) {
tiles.add( /*etc*/);
}
Btw, if you want it to update when new messages come in, you can do like this:
import 'dart:async';
// Put the below in the State of a StatefullWidget:
StreamSubscription<QuerySnapshot<Map<String, dynamic>>>? qn;
List<DocumentSnapshot>? eventDocs;
void getDocStream(groupID) async {
var firestore = FirebaseFirestore.instance;
qn = firestore.collection('groups')
.where('groupChatId', isEqualTo: groupID)
.orderBy('timestamp', descending: true)
.snapshots().listen((event) {
// Put here everything you want to happen when new things happen in the stream!
// For example:
setState(() {
eventDocs = event.docs;
});
// Now, you can use eventDocs instead of getDoc(groupID), as you did before.
// Just remember that it will be null at first!
});
}
#override
void dispose() {
if (qn != null) qn!.cancel(); // This is to prevent the stream from going on, after you've left the page or even closed the app...
super.dispose();
}
Old answer:
But you're telling it to display only post [0]!...
If there are more posts in each document, and you want to display all of them, you need to make a for-loop or something. For example:
itemBuilder: ((_, index) {
List<Widget> tiles = [];
for (Map post in snapshot.data![index]['posts']) {
tiles.add(
ListTile(
title: Text(post['postText']),
subtitle: Text(post['fromUser']),
));
}
return Expanded(
child: Column(
children: tiles,
),
);
}),
And btw... Next time you ask a qn, plz paste your code as text rather than an image! So that we can copy-paste it into our answer, rather than having to retype it from the image. It's so easy to make a mistake and then you get an error coz we didn't copy it right.
try this
title: Text(snapshot.data![index]['posts']['postText']),
Related
I make a chat using firebase firestore.
So. I tried express not read count in the Chat list.
But, Initially the number appears, but it changes to null data.
I don't know Why data chage null data?
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('chatRooms')
.where('emails', arrayContainsAny: [user?.email]).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
var chatLists = snapshot.data?.docs;
if (snapshot.hasError) {
return Text('Something error...');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text('is Loading...');
}
return ListView.builder(
itemCount: chatLists?.length,
itemBuilder: (context, index) {
if (chatLists?[index]['currentMsg'] != null &&
chatLists?[index]['currentMsg'] != "") {
var list = List.from(chatLists?[index]['members']);
var member = '';
if (loginUser['userName'] != null) {
for (int i = 0; i < list.length; i++) {
if (list[i] != loginUser['userName']) {
member += list[i];
}
}
}
return ListTile(
title: Row(
children: [
Text(member),
const SizedBox(
width: 20.0,
),
ChatLength(docId: chatLists![index].id, uid: user!.uid),
],
),
subtitle: SizedBox(
height: 40.0,
child: Text(
chatLists[index]['currentMsg'],
overflow: TextOverflow.ellipsis,
)),
trailing: Text(
tmFormmater(chatLists?[index]['currentTm']),
style: const TextStyle(
color: Color(0xff999999),
),
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatScreen(
docId: chatLists![index].id,
title: member,
),
)),
);
} else {
return Container();
}
},
);
return Container();
},
),
class ChatLength extends StatelessWidget {
const ChatLength({super.key, required this.docId, required this.uid});
final String docId;
final String uid;
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('message_read')
.doc(docId)
.collection('message')
.where('userId', isNotEqualTo: uid)
.where('read', isEqualTo: false)
.snapshots(),
builder: (context, snapshot) {
print(snapshot.data?.size);
if (snapshot.data?.size != null) {
return Container(
child: Text('${snapshot.data?.size}'),
);
} else {
return Container();
}
},
);
}
}
===== Debug console ====
flutter: 1
flutter: null // Data changes to null immediately
message_read collection structure
message_read -> documentId -> message -> documentId -> field(userId(chat writer), read)
I'm trying get a snapshot data from firestore.
And I put the imported data into the text.
But Data changes to null immediately.
Since you use snapshots() the code is not just reading the data once, but then continues listening for changes to the data. So the two values you see mean that the snapshots() stream was triggered twice, the first time with a single message - and the second time without any messages.
My educated guess is that your code changes the read value of the document after it gets it, since it has now displayed that message to the user. But doing so means the document no longer meets the criteria of your query, so the snapshots stream gets a new event without that message in it.
Consider using a different mechanism for the query, for example I usually use a timestamp to determine what messages to show. Step by step:
Ensure each message document has a timestamp field.
For each user store (either in the database or in local storage of the app) when they last started the app.
Then request from the database the messages since they last started the app.
Make sure to start the query before you update the timestamp value, otherwise you'll never get any results.
I have been scowering the internet and trying to find a way to structure and builda friend system using Flutter and Firebase. I have settled on the following structure, but am certainly open to new suggestions:
I have a collection containing all existing users. Where each document is the users uid. Furthermore each document again has a few collections containging the friend data. I keep track of which users have sent the user in question a friendrequest, and which friend requests have been sent out by the user in question. Last but not least ofcourse a list of users which are actually his friends.
Initial sctructure
All 3 of these sub collection simply hold more uid's, since I dont want to store a copy of the actual user data here. sub collection structure. This is because when a user updates his personal information I have to also update all of the information which belong to all the friends, resulting in a huge number of reads and writes. So with these uid's I want to backtrack to the initial user collection to find his/her information and display it
So my idea was to have a StreamBuilder which has a snapshot of all the friends of the current user. These snapshots can then be used to track back to the user collection and fetch that user's data. However I have many doubts on this structure since it isnt very asynchronous and is giving me errors. Because the index of the Listview is continuing onward while the data isnt loaded yet.
And I want them added to a list so I can manage the state of the isSelected variable and the ProfilePicture. Also if there is a better way of doing this please enlighten me!
I know it is quite the long question, but could somebody please help me out on this one :)
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser?.uid)
.collection('friends')
.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshots) {
if (snapshots.hasError) {
return const Text("Er is iets fout gegaan!");
}
if (snapshots.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshots.data!.docs.isEmpty) {
return Column(children: const [
Icon(
Icons.info,
color: Colors.grey,
size: 50,
),
Text(
"Voeg eerst vrienden toe!",
style: TextStyle(color: Colors.grey, fontSize: 26),
)
]);
}
return Expanded(
child: ListView.builder(
itemCount: snapshots.data!.docs.length,
itemBuilder: (context, index) {
return FutureBuilder<DocumentSnapshot>(
future: FirebaseFirestore.instance
.collection("users")
.doc(snapshots.data!.docs[index].id)
.get(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var data =
snapshot.data!.data() as Map<String, dynamic>;
allReceiptDebtors.add(ReceiptDebtor(
snapshots.data!.docs[index].id,
data['email'],
data['username'],
ShowProfilePicture()
.show(data['image'], data['username']),
false,
false));
print(index);
print(allReceiptDebtors);
return ListTile(
title: Text(
allReceiptDebtors[index].username,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.black54,
fontSize: 16,
fontWeight: FontWeight.bold),
),
subtitle: Text(
allReceiptDebtors[index].email,
overflow: TextOverflow.ellipsis,
),
leading: Container(
width: 40,
height: 40,
alignment: Alignment.center,
child: allReceiptDebtors[index].profilePicture,
),
trailing: Visibility(
maintainSize: true,
maintainAnimation: true,
maintainState: true,
visible: allReceiptDebtors[index].isSelected,
child: const Icon(
Icons.check_rounded,
color: SplitlyColors.gold,
),
),
onTap: () {
setState(() {
if (!allReceiptDebtors[index].isSelected) {
allReceiptDebtors[index].isSelected = true;
} else {
allReceiptDebtors[index].isSelected = false;
}
});
},
);
}
return Container();
},
);
}));
});
class ReceiptDebtor {
final String docId;
final String email, username;
final Widget? profilePicture;
List items = [];
bool isSelected;
bool isExpanded;
ReceiptDebtor(this.docId, this.email, this.username, this.profilePicture,
this.isSelected, this.isExpanded);
}
I have a dropdown menu that is being populated from Firestore with this code.
FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance
.collection('field_management')
.orderBy('cultivar_name')
.get(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox(
height: 15.0,
width: 15.0,
child: Center(
child: CircularProgressIndicator(),
),
);
}
return DropdownButton(
onChanged: (newValue) {
setState(() {
cultivarDropdownValue =
newValue.toString();
});
},
hint: Text(cultivarDropdownValue),
items: snapshot.data!.docs
.map((DocumentSnapshot document) {
return DropdownMenuItem<String>(
value: document['cultivar_name'],
child:
Text(document['cultivar_name']),
);
}).toList(),
);
},
),
How can I create a unique list from the Firestore data to avoid duplicates in the dropdown menu?
I have tried adding items to the list in the initState() method, still received all the values from the collection (code below). Not sure what else I can try to make this work.
addDropDownItems() async {
return await FirebaseFirestore.instance
.collection('field_management')
.get()
.then((snapshot) {
for (dynamic document in snapshot.docs) {
if (!blockList.contains(document.data())) {
blockList.add(document.data());
}
}
print('block list: ${blockList}');
});
}
I stumbled across this post (How can I distinct a complex object list in DART), which solved the problem.
I made use of the darq package and just had to add this line above my print statement:
var uniqueList = blockList.distinct();
I am using Futurebuilder to show List issue is its running function 2 time or 3 times i dont know and showing data 2 times.
My function code
getCustomerList() async {
customerS = [];
print('check running');
final storage = new FlutterSecureStorage();
String uUid = await storage.read(key: "uUid");
CollectionReference _collectionRef =
FirebaseFirestore.instance.collection('Transaction');
QuerySnapshot querySnapshot =
await _collectionRef.get();
// Get data from docs and convert map to List
List allData = querySnapshot.docs.where((element) => element['CustomerID'] == widget.data['customerID'])
.map((doc) => doc.data())
.toList();
print(allData);
print('allData length ${allData.length}' );
for (int i = 0; i < allData.length; i++) {
print(allData[i]);
customerS.add(allData[i]);
}
print(customerS);
print('cus length ${customerS.length}' );
return customerS;
}
My future builder code
FutureBuilder(
future: getCustomerList(),
builder: (context, snapshot) {
print('snapshot length ${snapshot.data.length}');
print(snapshot);
print(snapshot.data);
if (snapshot.hasData)
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
print('List length ${snapshot.data.length}');
return Padding(
padding: const EdgeInsets.only(left: 13, right: 13),
child: Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: Colors.grey, width: .5)),
),
child: Padding(
padding: const EdgeInsets.all(13.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'10th May ',
style: TextStyle(fontFamily: 'PoppinsMedium'),
),
Text(
snapshot.data[index]['give'].toString(),
style: TextStyle(
fontFamily: 'PoppinsMedium',
color: Colors.green),
),
],
),
),
),
);
},
);
else
return Text('Result: ${snapshot.data.body}');
}
),
Issue is i have only 2 arrays in list but its showing for look like its running two time i am also try to clear the array but nothing work.
You can look on this picture
enter image description here
I have only 2 array which are in red circle and its showing double can see in blue circle.
Why you are using customerS I mean its saving data in this by initState and when Future call its double.
Remove it from initState and just simply return allData like this
getCustomerList() async {
customerS.clear();
print('check running');
final storage = new FlutterSecureStorage();
String uUid = await storage.read(key: "uUid");
CollectionReference _collectionRef =
FirebaseFirestore.instance.collection('Transaction');
QuerySnapshot querySnapshot = await _collectionRef.get();
// Get data from docs and convert map to List
List allData = querySnapshot.docs
.where((element) => element['CustomerID'] == widget.data['customerID'])
.map((doc) => doc.data())
.toList();
print(allData);
return allData;
}
From the docs:
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.
This line:
FutureBuilder(
future: getCustomerList(), <- this line
...
)
I am trying to get set of encrypted data documents from firebase and display them on a list view on flutter.
I used a stream builder for obtaining data and started displaying it on the list view. But I cant perform the decryption operation on each data item as it is an async operation. What is the best way to do this?
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection(ScopedModel.of<User>(context).userId)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(
child: new Container(
child: CircularProgressIndicator(),
));
default:
if (snapshot.data.documents.length == 0) {
return Container(
padding: EdgeInsets.all(16.0),
child: Row(
children: <Widget>[
Text('Empty',),
],
),
);
}
final docs = snapshot.data.documents;
return ScrollConfiguration(
behavior: ScrollBehavior(),
child: ListView.builder(
itemCount: len,
scrollDirection: Axis.horizontal,
itemBuilder: (context, position) {
// Where should I decrypt the below data?
// let decrypted = await myDecryptionFunction(docs[position]['myDataKey']) ;
// the above is not working
// this will show the encrypted text
return Text(docs[position]['myDataKey']);
}
....
For your situation you can use a StreamController with a helper class to hold the information.
The following is just an example but adapt it to your own needs.
// Helper classes
// adapt it to your own business case
class Notes {
String title;
String description;
Notes({this.title, this.description});
}
class NotesFromDb {
String connectionState;
bool hasError;
String error;
List<Notes> notes;
NotesFromDb({this.connectionState, this.hasError, this.error, this.notes});
}
// The Streambuilder
StreamBuilder<NotesFromDb>(
stream: sController.stream,
builder: (BuildContext context, AsyncSnapshot<NotesFromDb> snapshot) {
// Here you can check for errors
if (snapshot.data.hasError == true) {
return Container(
color: Colors.red,
child: Text(snapshot.data.error),
);
}
// Here you can check for your connection state
if (snapshot.data.connectionState == 'Loading') {
return Container(
color: Colors.yellow,
child: CircularProgressIndicator(),
);
}
// Here you can show your data
var info = snapshot.data.notes.map((doc) {
return Text(doc.title);
});
return Center(
child: Container(
color: Colors.deepOrange,
child: Column(
children: info.toList(),
),
));
})
// Here is how you can handle the decrypt data
// using a FloatingActionButton for loading the data (for example)
FloatingActionButton(
onPressed: () async { // you would need to add the async
List<Notes> theNotes; //just to hold the information
// Use this to allow to show the CircularProgressIndicator
sController.sink.add(NotesFromDb(connectionState: 'Loading'));
var snapshots = Firestore.instance.collection('notes').snapshots();
snapshots.listen((QuerySnapshot data) {
theNotes = data.documents.map((DocumentSnapshot doc) {
// Build your data
return Notes(
title: doc.data['title'],
description: doc.data['description']);
}).toList();
}, onError: (err, stack) {
// If an error happend then send the error to the stream
sController.sink
.add(NotesFromDb(hasError: true, error: err.error));
});
// Here you can to decrypt the documents with your function
var decryptDocuments = await Future.delayed(Duration(seconds: 2)); //Whatever future function
// Once you have the decrypt documents, you would need to send that to the stream.
// set the connectionState to Done, so the spinner is not showed anymore.
sController.sink.add(NotesFromDb(
hasError: false, connectionState: 'Done', notes: decryptDocuments));
},
child: Icon(Icons.arrow_forward),
)
Take the example just for illustration on how to do it.
Hope this help.