Get Firebase Document within ListView Builder - flutter

I got a list of questions. When the user doesn't like the question, it can be added to a hidden list. Now I would like list all the questions which have been added to the hidden list.
The Firestore IDs are added to an array within a provider (setting).
When I build the ListView I want to fetch the question documents by document id and pass those document fields to the HiddenList widget.
I've tryied using StreamBuilder, Future,.. unfortunately nothing worked so far..
Any pointers?
Code:
var questions = FirebaseFirestore.instance.collection('questions');
if (setting.hidden.length == 0) {
return Text('Empty');
} else {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: setting.hidden.length,
itemBuilder: (context, index) {
return new StreamBuilder(
stream: questions.doc('${setting.hidden[index]}').snapshots(),
builder: (context, docSnapshot) {
if (!docSnapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
var data = docSnapshot.data!;
return HiddenList(
de_du: data['de_du'],
de_sie: data['de_sie'],
de_ich: data['de_ich'],
en: data['en'],
id: setting.hidden[index],
);
}
});
},
);
}

Related

Sort messages in Realtime DB by timestamp

I am having trouble by getting my messages stored in realtime db in order.
Here's my db structure
messages: {
$chatId: {
$messageId: {
timestamp: 1664884736728,
sender:"36a72WVw4weQEoXfk3T9gCtOL9n2",
message: "Hello world"
}
}
}
This is my chat repository
//get all messages of a chat
Query getMessages(String chatId) {
//get all messages of a chat
final messages = _database.ref().child("messages/$chatId");
//return all messages of a chat
return messages;
}
}
and this is how I am displaying it
StreamBuilder(
stream: _chatRepository.getMessages(chatId).onValue,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.data != null &&
snapshot.data?.snapshot?.value != null &&
snapshot.hasData) {
final messages = Map.from(snapshot.data?.snapshot.value as Map);
return ListView.builder(
itemCount: messages.length,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
final currChat = messages.values.toList()[index];
return BubbleCustom(...),
);
},
);
}
return Container();
},
),
This is my index in realTimeDB
{
"rules": {
"messages": {
"$chatId": {
".indexOn": ["timestamp"]
}
}
}
}
I need to get them in timestamp order. Is there any way I can do this? I hope you can help me. Thanks in advance!
You can use orderByChild to get the messages in the order of a specific child. So there that'd be:
stream: _chatRepository.getMessages(chatId).orderByChild('timestamp').onValue
Don't forget to define an index for timestamp, so that the sorting can be done on the database server.
To learn more on this, see the Firebase documentation on sorting and filtering data.
I solved it by doing it in the frontend like this:
final messages = Map.from(snapshot.data?.snapshot.value as Map);
//sort messages by timestamp ascending order
final messagesList = messages.values.toList();
messagesList.sort((a, b) => a["timestamp"].compareTo(b["timestamp"]));
final newMessagelist = List.from(messagesList.reversed);
return ListView.builder(
physics: const BouncingScrollPhysics(),
reverse: true,
itemCount: newMessagelist.length,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
final currChat = newMessagelist[index];
return BubbleCustom(
text: currChat["message"],
isSender: currChat["sender"] == senderUser.id,
tail: true,
textStyle: TextStyle(
fontSize: 16,
color: currChat["sender"] == senderUser.id ? Colors.white : Colors.black,
),
);
},
);
with reverse: true and inverting the list before I render it.

How to remove items from a list (Dart) | Firebase rtdb

I have a list that stores items that I don't want to display as part of my StreamBuilder, ListView. This list retrieves its information from a firebase rtdb.
I use a StreamBuilder to populate the ListView, and then I use a for-loop to try and iterate through the list that contains items I don't want to display. So far I can get the ListView populated, but the items removed from the StreamBuilder aren't accurate.
Below is how I have approached it (Any help is much appreciated):
I can confirm that the list definitely contains the info I don't want displayed
ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: friends.length,
itemBuilder: (context, index) {
final friend = friends[index];
if (friend.userID == uid){
return null;
} else {
for (FriendSuggestionModel hidden in hiddenSuggestions){
if (hidden.userID == friend.userID){
return null;
} else {
return friendThumbnail(index, friend);
}
}
return null;
}
});
First, I believe you need to return a Widget in itemBuilder, so don't use return null instead you can return an empty container with return Container().
You also could use list.contains(x) method to verify if this id should be hide (as I imagine , as follows:
itemBuilder: (context, index) {
final friend = friends[index];
if (friend.userID == uid){
return const Container();
} else {
return hiddenSuggestions.map((hidden) => hidden.userID).toList().contains(friend.userID)
? const Container()
: friendThumbnail(index, friend);
}
}
Check that method docs here: https://api.dart.dev/stable/2.0.0/dart-core/Iterable/contains.html

Why am I getting this error Bad state: field does not exist within the DocumentSnapshotPlatform while using firestore?

Bad state: field does not exist within the DocumentSnapshotPlatform
The relevant error-causing widget was
StreamBuilder<QuerySnapshot<Object?>>
StreamBuilder:file:///D:/EgoPro/Flutter%20Apps/task_app/lib/screens/task_screen.dart:189:13
this is the error
StreamBuilder<QuerySnapshot>(
// <2> Pass `Stream<QuerySnapshot>` to stream
stream:
FirebaseFirestore.instance.collection('tasks').snapshots(),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData) {
// <3> Retrieve `List<DocumentSnapshot>` from snapshot
final List<DocumentSnapshot> documents = snapshot.data!.docs;
print(documents);
return ListView(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
children: documents
.map(
(doc) => Meetingcard(
id: doc.get("id"),
title: doc.get("title"),
description: doc.get("description"),
time: TimeOfDay.now()),
)
.toList());
} else if (snapshot.hasError) {
return Text("'It's Error!'");
} else {
return CircularProgressIndicator();
}
},
)
Why am i getting this error ?
This is the image of my documents
enter image description here>
doc.get will return this error if the specified field does not exists in the document. So one of your fields: id, title, description (or more of these) can't be found in doc.
You can add a breakpoint or log and check the result of doc.data() inside your .map((doc)... to see what does it contain.
(One of the possible ways to handle optional fields is to define a model class, create converter where you handle missing values and assign empty string or other default value, so when you read data from your stream you can use this model, and you don't have to handle missing values there.)
EDIT:
Based on the error picture in comment the error seems to be somewhere else, where you assign value to documents. snapshot.data!.docs has the type List<QueryDocumentSnapshot<Object?>> and not `List. Try the following code:
StreamBuilder<QuerySnapshot>(
// <2> Pass `Stream<QuerySnapshot>` to stream
stream:
FirebaseFirestore.instance.collection('tasks').snapshots(),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData) {
// <3> Retrieve `List<DocumentSnapshot>` from snapshot
return ListView(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
children: snapshot.data!.docs
.map(
(DocumentSnapshot doc) => Meetingcard(
id: doc.data()!["id"],
title: doc.data()!["title"],
description: data()!["description"],
time: TimeOfDay.now()),
)
.toList());
} else if (snapshot.hasError) {
return Text("'It's Error!'");
} else {
return CircularProgressIndicator();
}
},
)

how to use async/await in Listview builder

I have a table in my sqflite database containing the call history of the respective users. Now on my Call history page in flutter, I am showing the complete history data, fetched from sqflite up till now its working fine. But now I want to check whether the numbers are in my history list exist in contact. If yes, then I want to show their contact name and avatar in the list. Otherwise I just want to show the number. Here's my code:
List<Map<String, dynamic>> ok =
await DatabaseHelper.instance.getAllLogs(argv);
setState(() {
queryRows = ok;
});
var historyRecords = List<HistoryRecord>.from(queryRows.map((row) => HistoryRecord.fromJson(row)));
FutureBuilder<List<HistoryRecord>>(
future: _checkContact(historyRecords),
builder: (context, snapshot) {
return ListView.builder(
itemCount: historyRecords.length,
itemBuilder: (context, index) {
print(historyRecords[index]);
},
);
},
)
Future<List<HistoryRecord>> _checkContact(List<HistoryRecord> rec)async
{
for(int i=0;i<rec.length;i++) {
var conhere=await
ContactsService.getContactsForPhone(rec[i].callHistoryNumber);
//how should i map iterable contact list to Historyrecord
}
}
To call an asynchronous call in UI, you can use FutureBuilder. You can run a check for each and every items in the list like this:
FutureBuilder<bool>(
initialData: false, // You can set initial data or check snapshot.hasData in the builder
future: _checkRecordInContact(queryRow), // Run check for a single queryRow
builder: (context, snapshot) {
if (snapshot.data) { // snapshot.data is what being return from the above async function
// True: Return your UI element with Name and Avatar here for number in Contacts
} else {
// False: Return UI element withouut Name and Avatar
}
},
);
However I don't recommended this method since there would be too many async calls that will slow down the app. What I recommend is to run a check for all items in the queryRows first, then send it to UI.
First of all you should use an Object to represent your history records instead of Map<String, dynamic> to avoid bugs when handling data. Let's say we have a list of HistoryRecord objects, parse from queryRows. Let's call this list historyRecords
var historyRecords = List<HistoryRecord>.from(queryRows.map((row) => HistoryRecord.fromJson(row)));
Each object should have a Boolean property fromContact to check if it's in the Contacts or not. We can then do this:
Widget buildListView(historyRecords) {
return FutureBuilder<List<HistoryRecord>>(
future: _checkContact(historyRecords), // Here you run the check for all queryRows items and assign the fromContact property of each item
builder: (context, snapshot) {
ListView.builder(
itemCount: historyRecords.length,
itemBuilder: (context, index) {
if (historyRecords[index].fromContact) { // Check if the record is in Contacts
// True: Return your UI element with Name and Avatar here
} else {
// False: Return UI element without Name and Avatar
}
},
);
},
);
}
You can then check the contacts with the following property of HistoryRecord and function:
class HistoryRecord {
bool fromContact;
Uint8List avatar;
String name;
//... other properties
HistoryRecord({this.fromContact, this.avatar, this.name});
}
Future<List<HistoryRecord>> _checkContact(List<HistoryRecord> rec) async {
for (int i = 0; i < rec.length; i++) {
Iterable<Contact> conhere =
await ContactsService.getContactsForPhone(rec[i].callHistoryNumber);
if (conhere != null) {
rec[i]
..name = conhere.first.displayName
..avatar = conhere.first.avatar
..fromContact = true;
}
}
return rec;
}
You can use FutureBuilder to check each number like:
ListView.builder(
itemCount: history.length,
itemBuilder: (context, index) {
FutureBuilder(
future: checkContactExists(history[0]),
builder: (context, snap){
if(snap.hasData){
if(snap.data = true){
return PersonContact();
}else{
return JustNumber();
}
}
return Loading();
}
)
},
);

Flutter Streambuilder map to List object

I need to display a listview in Flutter with data from firestore. Then I want the user to be able to filter the listview by typing his query in a textfield in the appbar. This is the code I came up with for the listview:
_buildAllAds() {
return StreamBuilder(
stream: Firestore.instance.collection("Classificados")
.orderBy('title').snapshots().map((snap) async {
allAds.clear();
snap.documents.forEach((d) {
allAds.add(ClassificadoData(d.documentID,
d.data["title"], d.data["description"], d.data["price"], d.data["images"] ));
});
}),
builder: (context, snapshot) {
// if (!snapshot.hasData) {
// return Center(child: CircularProgressIndicator());
// }
//else{
//}
if (snapshot.hasError) {
print("err:${snapshot.error}");
}
return ListView.builder(
itemCount: allAds.length,
itemBuilder: (context, index) {
ClassificadoData ad = allAds[index];
return ClassificadosTile(ad);
});
});
}
The reason I save the stream data in the List allAds of type ClassificadoData (data items are ads) is because I can then copy it to another List filteredAds on which the user can perform filtering. And the reason I need a stream for allAds is because I want users to be able to see additions/updates in real time.
So this code "works" but it feels a bit awkward and I also can't do nothing with the builder since snaphot remains null all the way (can't show loader during initial data fetch, for example).
Was wondering if there's maybe a more solid way for doing what I want and if it's possible to get a reference to the snapshots down to the builder.
You seem to be mixing two different concepts of using Streams and Stream related Widgets. Ideally you would either use a StreamBuilder and use the data you get from the stream directly on the Widget, or listen to the data and update a variable that is then used to populate your ListView. I've build the latter as an example from your code:
#override
initState(){
_listenToData();
super.initState();
}
_listenToData(){
Firestore.instance.collection("Classificados")
.orderBy('title').snapshots().listen((snap){
allAds.clear();
setState(() {
snap.documents.forEach((d) {
allAds.add(ClassificadoData(d.documentID,
d.data["title"], d.data["description"], d.data["price"], d.data["images"] ));
});
});
});
}
_buildAllAds() {
return ListView.builder(
itemCount: allAds.length,
itemBuilder: (context, index) {
ClassificadoData ad = allAds[index];
return ClassificadosTile(ad);
}
);
}