AsyncSnapshot state is always connectionState.waiting - flutter

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.

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 List View through Future Provider Value

I am trying to call the output of a Future Provider of type Future> into a List View builder. I think I am very near as I am able to render the final List View itself, however, prior that, an error appears and is quickly replaced by the List View after completing the Future. I believe there may be something wrong with my implementation there.
Here's what I've got so far (these are derivatives of my actual code, there's too many going on there that aren't necessary, I tried to simplify it):
class TempProvider extends ChangeNotifier(){
List<Widget> _list = <Widget>[];
List<Widget get list => _list;
Future<List<Widget>> getList() async{
List _result = await db....
_result.forEach((_item){
addToList(_item);
});
}
addToList(Widget widget){
_list.add(widget);
notifyListeners();
}
}
class Parent extends StatelessWidget{
#override
Widget build(BuildContext context) {
return FutureProvider(
create: (context) => TempProvider().getList(),
child: Child(),
);
}
}
class Child extends StatelessWidget{
#override
Widget build(BuildContext context) {
var futureProvider = Provider.of<List<Widget>>(context);
return FutureBuilder(
initialData: <Widget>[],
future: TempProvider().getList(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.none &&
snapshot.hasData == true) {
return ListView.builder(
itemCount: futureProvider.length,
itemBuilder: (BuildContext context, int index) {
return futureProvider[index];
},
);
} else {
return Text('ALAWS');
}
},
);
}
}
So basically, the output of my Future will be a list of widgets that will populate a List View that I am trying to build. Though I am able to render the list view in the end, the error below appears in between:
The getter 'length' was called on null.
Receiver: null
Tried calling: length
The relevant error-causing widget was
FutureBuilder<List<Widget>>
Hoping someone can help with this one or at least give a better example.
Thank you so much!
Not sure but, this is happening because your widget might be building twice and at first futureProvider is null and in second time it has some value.
Workaround:
Replace this:
futureProvider.length
With this:
futureProvider?.length ?? 0
What the above code does?
futureProvider?.length: if futureProvider is null don't access it's length.
Now the value returned will be null.
?? 0: if the value returned is null then return 0;
You need to think over following things and edit your code.
At first place futureProvider should not be null.
Why are you not using snaphot.data when you are using FutureBuilder.
So I have been doing my research and found the article below which shows a definite implementation based on what I need:
Flutter Provider Examples - Codetober
Credits to Douglas Tober for the article. Thanks again to Kalpesh for the quick help!

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

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.