Flutter Streambuilder map to List object - flutter

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);
}
);
}

Related

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

Flutter/Dart Non-Nullable - Use Snapshot data directly

I am using expansion tiles in my code, and was using snapshot.data.length then accessing data directly from the snapshot when I was using the nullable version of Dart.
However, I changed it down the line to the non-nullable version, and it is throwing the errors below -- how can I access the snapshot data, or convert it into a List/Map to be able to use it?
The attached image shows the errors it's showing me. I tried converting the snapshot.data to another var but that didn't work either.
So I hope this solves your problem. Attached the code and some comments. I tested it in Dartpad and it worked fine like that. If it's still not working please provide more code.
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
// you need to define what the future builder will return
return FutureBuilder<List<BudgetItem>>(
// helper.getAllItems() needs to have a return type
// "Future<List<BudgetItem>>"
future: helper.getAllItems(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
// snapshot.data[index] is now from type "BudgetItem" and you can
// access whatever properties it has
return ListTile(title: Text(snapshot.data![index].title));
},
);
}
// if there is no data you still need to return sth like a loading spinner
return Container();
},
);
}

Flutter Firestore Convert Stream To Future

I have a firestore stream in flutter that I would instead like to be a future so that I can do pagination of requests. Currently I periodically increase the limit variable in the code below and reload the whole original stream plus new data. This is very irritating because every time the limit variable increases the widget (a listview) scrolls to the top. I would like to ask how to convert the stream below into a future and how to place its contents into a list. My purpose of doing so being that the contents of all the future calls will be accumulated in an list and my listview will be generated off of that array, hopefully without scrolling to the top every time.
My other reason for doing so is to save memory on the client device. When a user scrolls down I would like to remove items from the front of the list to save memory and reload them only if the user scrolls back up. My project is a social-media application so I foresee users scrolling down indefinitely and using up all their phone memory. I am new to flutter so I would also like to ask if this memory usage is a valid concern.
Stream<List<Memo>> getFeed(int limit) async* {
yield* Firestore.instance
.collection('memos')
.where('followers', arrayContains: userid)
.orderBy("date")
.limit(limit) // TODO: add pagination of request
// .startAfterDocument(null)
.snapshots()
.map(_snapshotToMemoList);
}
My Streamsubscription and listview builder code is as follows:
Widget build(BuildContext context) {
return StreamBuilder<List<Memo>>(
stream: dbService( user: widget.user ).getFeed( streamLimit ),
builder: (BuildContext context, AsyncSnapshot<List<Memo>> snapshot) {
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading...');
default:
if (snapshot.data.isEmpty) {
return Text('EMPTY');
}
// streamSub.cancel();
// return Text(snapshot.data[1].body);
return ListView.builder(
controller: _scrollController,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 600,
child: Text(snapshot.data[index].body)
);
}
);
}
},
);
Lastly, my limit increasing function is
void initState() {
if (lastScrollPosition != null) _scrollController.jumpTo(lastScrollPosition);
_scrollController.addListener(() {
final maxScroll = _scrollController.position.maxScrollExtent;
// print(maxScroll);
final currentScroll = _scrollController.position.pixels;
// print(currentScroll);
if (maxScroll - currentScroll <= _scrollThreshold) {
setState(() {
lastScrollPosition = currentScroll;
streamLimit += 1;
print('increasing');
});
}
});
}

AsyncSnapshot state is always connectionState.waiting

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.

StreamBuilder not updating ListView.builder after data changed in Flutter

I am new to Flutter and facing an issue with StreamBuilder & ListView.builder.
I am making a network call on click of a button(Apply Button) available in the list of the card, based on that other buttons are displayed.
the issue I am facing is that widgets are not updated after successful network call but, when I refresh the page I am getting updated result.
What I am doing wrong?
I am not Using any State into this. Do I need to use it?
Widget Code
StreamBuilder(
initialData: [],
stream: dataBoc.ListData,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemBuilder: (BuildContext context, index) {
return InkWell(
key: Key(snapshot.data[index]["lid"]),
child: DataCard(
DataModel(snapshot.data[index]),
),
onTap: () {
Navigator.pushNamed(context, "/detailPage",
arguments: snapshot.data[index]["id"]);
},
);
},
itemCount: snapshot.data.length,
);
}
},
),
Bloc Code
//Here is how I am adding data to the stream
if (res.statusCode == 200) {
var data = json.decode(res.body);
if (data['status'] == true) {
// listDataStream.sink.add(data['result']);
listDataStream.add(data['result']);
} else {
listDataStream.sink.addError("Failed to Load");
}
}
Expected result: When I make Network call and if it succeeds then based on network result other buttons must be displayed on the appropriate card.
I have fixed this issue. The issue was my widget tree was not well structured and it was breaking the Widget build process.