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
Related
// Build the whole list of todo items
Widget _buildTodoList() {
return new ListView.builder(
itemBuilder: (context, index) {
// itemBuilder will be automatically be called as many times as it takes for the
// list to fill up its available space, which is most likely more than the
// number of todo items we have. So, we need to check the index is OK.
if (index < _todoItems.length) {
return _buildTodoItem(_todoItems[index], index);
}
},
);
}
Attempting to run a simple widget inside my program but receive the
"body_might_complete_normally" error.
How can I correct this?
itemBuilder only return on (index < _todoItems.length), you need to make sure return widget on every case.
Widget _buildTodoList() {
return ListView.builder(
itemCount:_todoItems.length, // your list length
itemBuilder: (context, index) {
if (index < _todoItems.length) {
return _buildTodoItem(_todoItems[index], index);
}
return Text("default case");// if above condtions dont meet, return this
},
);
}
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],
);
}
});
},
);
}
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();
}
)
},
);
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);
}
);
}
I'm trying to have a ListView dynamically update depending on the contents of a TextField (a search bar).
The ListView is inside a "ScenariosList" widget, and contains a list of "Scenarios", which is a custom widget containing a title, content and other bits of data (not really relevant but helpful for context). It's content is fetched from a database via a "ScenariosBloc".
The TextField is contained within a "SearchBar" widget.
The goal is to have the contents of the ListView change whenever a change to the TextField is detected.
I'm currently using two individual blocs. ScenariosBloc fetches all the scenarios from the database and FilterScenariosBloc makes the List render a widget to show the scenario if it's title contains the string in the TextView within the SearchBar.
I'm using nested StreamBuilders to do this (see code below).
ScenariosList.dart
// build individual scenario cards
Widget _buildScenarioListItem(Scenario scen, String filter) {
if (!(filter == null || filter == "")) {
print("null filter");
if (!(scen.title.contains(filter))) {
print("match!");
return ScenarioCard(scen);
}
}
return Container();
}
Widget _buildScenarioList(BuildContext context) {
return StreamBuilder(
stream: scenarioBloc.scenarios,
builder: (BuildContext context,
AsyncSnapshot<List<Scenario>> scenariosSnapshot) {
if (!scenariosSnapshot.hasData) {
return CircularProgressIndicator();
}
return StreamBuilder(
stream: filterScenariosBloc.filterQuery,
initialData: "",
builder: (BuildContext context, AsyncSnapshot filterSnapshot) {
if(!filterSnapshot.hasData) return CircularProgressIndicator();
print("Scenarios Snapshot: ${scenariosSnapshot.toString()}");
print("Filter Snapshot: ${filterSnapshot.toString()}");
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.all(0),
shrinkWrap: true,
itemCount: scenariosSnapshot.data.length,
itemBuilder: (BuildContext context, int index) {
Scenario scen = scenariosSnapshot.data[index];
return _buildScenarioListItem(scen, filterSnapshot.data);
},
);
});
});
}
}
SearchBar.dart
the onChanged method of the Textfield contains:
// function to filter the scenarios depending on the users input.
void filterSearchResults(String query) {
_filterScenariosBloc.doFilter(query);
}
FilterScenariosBloc.dart
class FilterScenariosBloc {
// stream - only need one listener
final _searchController = StreamController<String>.broadcast();
// output stream
get filterQuery => _searchController.stream;
FilterScenariosBloc() {
doFilter(" ");
}
doFilter(String query) {
_searchController.sink.add(query);
}
}
The user input is sent to the FilterScenariosBloc all fine, but the status of the filterSnapshot is always connectionState.waiting.
Any ideas on how I can resolve this?
I had the same issue. The problem was that my firestore DB rules did not allow read or write for the collection in question. Please see if that solves your prob
I had the same issue, always having the connectionState.waiting, and thus the snapshot.data
was null. This means that for whatever reason, the data could not be fetched.
I then ran the app into the debug mode and got an error like "Cannot fit requested classes into a single dex file". Then I just followed this answer and it solved the issue for me.