After getting a list of image urls from firebase using StreamBuilder
i used a ListView.builder with a TransitionToImage from this package:
https://pub.dartlang.org/packages/flutter_advanced_networkimage
and made a list of images.
here is my Streambuilder / builder code :
builder: (_, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData) return const Text('loading...');
var doc = snapshot.data;
List<String> pages = List.from(doc['pages']);
return ListView.builder(
itemCount: pages.length,
itemBuilder: (context, index) {
return TransitionToImage(
AdvancedNetworkImage("a url", timeoutDuration: Duration(minutes: 1)),
placeholder: CircularProgressIndicator(),
duration: Duration(milliseconds: 300),
);
});
},
the ListView.builder loads all the images in random order at once, which doesn't look that natural for me or the users.
is there a way to make ListView.builder load a image, after the image in the previous index has been loaded?
I would use precacheImage to load the images in pages one-by-one and when arrived replace the URL in pages so that the real image is shown instead of a placeholder.
for(final page in pages) {
await precacheImage(page.downloadUrl, context);
page.displayUrl = page.downloadUrl;
setState(() => pages = pages.toList();
}
Related
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],
);
}
});
},
);
}
Trying to use StreamBuilder to build out a ListView, but it is showing one item in each row of the ListView. I am using StreamBuilder broadcast. I use add(data).
child: StreamBuilder<GoogleAddress>(
stream: pickupStreamController.stream,
builder: (context, snapshot) => ListView.builder(
itemCount: 8,
itemBuilder: (context, index) {
print('Render $index');
if (snapshot.data != null) {
print("${snapshot.data.address}");
return _createListItem(
icon: Icons.location_on,
googleplace: snapshot.data,
onTap: () {
print("${snapshot.data.address}");
});
} else {}
},
),
),
Here is the StreamController code where I add to the stream. It is adding different GoogleAddress data on each iteration.
GoogleAddress addy = GoogleAddress.fromJsonMap(map);
if (sc != null) {
sc.add(addy);
}
StreamBuilder will use the latest value from the stream. To pass a list to StreamBuilder you would need to pass a list to add.
Which means to grow a list over time, you have to keep a regular list and pass it to add after modifying it.
final addresses = [];
GoogleAddress addy = GoogleAddress.fromJsonMap(map);
addresses.add(addy);
controller.add(addresses);
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);
}
);
}
When I launch the app for the first time it loads in all the items into the animated list. But when If I add an item to firestore, the streambuilder realizes something has happend. BUT it doesn't execute the itembuilder so no new items will show up. However if I relaunches the app, the new items will be loaded. If I would just change a name on an existing item on the firestore database, it will rebuild with no problem.
When I update an item. I have to get the map of tasks and replace it with a new copy that has the item and post/update that to firestore.
I have tried everything I can think off, doesn't get why this happens.
I have printed in the itembuilder and can see that it doesn't execute on an update.
Widget _buildTasksList() {
return new Expanded(
child: new StreamBuilder(
stream: Firestore.instance
.collection("lists")
.document(tasklist.postID)
.snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return new Text("Loading");
}
var listdata = snapshot.data;
tasklist.tasks = listdata['tasks'];
tasklist.owners = listdata['owners'];
tasklist.sorter = listdata['sorter'];
return new AnimatedList(
initialItemCount: convertToTaskList(listdata['tasks']).length,
key: _listKey,
itemBuilder: (context, index, animation) {
return new TaskRow(
task: this.listModel[index],
animation: animation,
listModel: this.listModel,
tasklist: tasklist,
onChange: () => _onChange(this.listModel[index]),
);
},
);
},
)
);
}
Got it working by resetting my _listKey, added this before animated list.
_listKey = null;
_listKey = new GlobalKey();
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.