Nested ListView, StreamBuilder and Subcollection - flutter

I'm trying to create a nested ListView, but don't know, if I can combine 2 streambuilders in a nested view, as it doesn't work. In the second StreamBuilder with the subcollection query, no data seems to be returned and I can't figure out why.
When I hardcode the document id I don't get any error, but still the query doesn't seem to return any data.
Does anybody know, how to construct a nested listview with streambuilders and Firestore?
List<Widget> buildStreamedListView() {
return [ StreamBuilder(
stream: Firestore.instance.collection('course')
.document(widget.data.documentID)
.collection('section')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text("Loading...");
return Expanded(child: ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
//return buildListItem(context, snapshot.data.documents[index]);
return Card(
child: ExpansionTile(
title: Text(snapshot.data.documents[index]['name']),
children: <Widget>[
StreamBuilder(
stream: Firestore.instance.collection('course')
.document(widget.data.documentID)
.collection('section')
.document('4CjAZEQ416NYpu3ra3OE')
.collection('page')
.snapshots(),
builder: (context, snap) {
return ListView.builder(
shrinkWrap: true,
itemCount: snap.data.documents.length,
itemBuilder: (context, index) {
return Text('Hello you');
}
);
}
),
],
),
);
}
));
},
)];
}

The reason that I had strange errors was, that in the second builder function I didn't add the following code:
if (!snapshot.hasData) return const Text("Loading...");
Once I added it, it worked. Seems the data was just not ready yet, hence it couldn't be read and hence the error.

Be careful to also test for snap.hasData() in your nested StreamBuilder.

Related

ScrollController jumpto when ListView.builder complete

I am creating a chat with firebase in flutter and I want that when the listview builder completes it can go to the end of the list (Last message).
This is what my buildmethod looks like:
return Scaffold(
backgroundColor: Color(0xFFf1e4e8),
body: Stack(
children: [
Container(
padding: EdgeInsets.only(bottom: 125),
child: StreamBuilder(
stream: userBloc.chat(widget.chatID),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else if(snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
return ListView.builder(
controller: scrollController,
physics: BouncingScrollPhysics(),
itemCount: snapshot.data.size,
itemBuilder: (context, index) {
return ChatMessage(
isUserMessage: isUserMessage(snapshot,index),
message:
snapshot.data.docs[index].data()['Message'],
timeStamp:snapshot.data.docs[index].data()['Timestamp']);
},
);
}
}),
);
What is the correct way to do it?
In your initState(), you can register a callback with addPostFrameCallback and it will get called once after the first frame completes rendering. You could use this callback to scroll to the bottom of your ListView.
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback( (_) {
_scrollToBottomOfListView();
}
}
Attach addListener to your scrollcontroller and then use jumpTo function accordingly. You can follow this example
ScrollController _scrollController;
double _lastEndOfScroll = 0;
_scrollController.addListener(() {
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.position.pixels;
if (maxScroll == currentScroll) {
_lastEndOfScroll = maxScroll;
}
});
_scrollController.jumpTo(_lastEndOfScroll);
The simplest solution is the following:
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('<MessagesColection>')
.orderBy('<Time field>',descending: true)
.snapshots(),
builder: (context,snapshot) {
return ListView.builder(
//The reversed list will put the list backwards.
//The start of the list will start at the bottom.
reverse: true,
controller: scrollController,
itemCount: snapshot.data.size,
itemBuilder: (context, index) {
return ChatMessage(snapshot);
},
);
}
),
In the previous code, what was done was to invert the list, the most recent messages will be at the bottom, and order the records in descending order, that is, from the most recent to the least recent. In this way the most recent messages will be at the beginning of the list (in this case at the bottom), and the least at the bottom of the list (in this case at the top).

Flutter Firebase only let certain data be shown on list view builder

I am trying to only display certain data from a collection. In my example, I only want to show data in the list where the collection in name on firebase is equal to the name of the widget. I am unable to solve this problem
Expanded(
child: Container(
child: FutureBuilder(
future: getPosts(),
builder: (_,snapshot){
if(snapshot.data["name"]== widget.info.name){
return ListView.builder(
itemCount:
x,
itemBuilder: (_,index){
return ListTile(
title: Text(snapshot.data[index].data["name"]),
);
});
Expanded(
child: Container(
child: FutureBuilder(
future: getPosts(),
builder: (_,snapshot){
return ListView.builder(
itemCount:
snapshot.data,
itemBuilder: (_,index){
if(snapshot.data["name"]== widget.info.name){
return ListTile(
title:Text(snapshot.data[index].data["name"]),
);
}else{
return Container();
}
})}))
);
A better option will be to pass the widget name in the getPosts() function you defined and update the firebase query to do the filtering. This way it be much faster as firebase does the require indexing and will do the filtering in O(len(filtered_elements)) rather than current time complexity of O(len(all_elements)).

Load data from the Database

I followed the chat development app tutorial on the Flutter page, it's a single user app. I have now been able to create a database and store data in the database, but I now have a conundrum that I'm unable to resolve.
I'm loading the chats from the database using FutureBuilder
FutureBuilder(
future: _requestSqlDataAsync(),
builder: (context, snapshot) {
if (snapshot.data == null) {
return Container(
child: Center(
child: Text("Loading...."),
),
);
}
else {
return ListView.builder(
padding: EdgeInsets.all(8.0),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return Text(snapshot.data[index].chat);
}
);
It successfully loads the data from the database but then I'm unable to pass submit new chats from TextController to the display widget in the following manner:
ListView.builder(
padding: EdgeInsets.all(8.0),
reverse: true,
itemBuilder: (_, int index) => _message[index],
itemCount: _message.length,
)
where _message is the widget that combines the data from the TextController along with animation, to display the chat.
Could anyone please guide me as to how I can load the data from the database and then pass the control for the chats to be loaded.

Combine itemBuilder with StickyHeader in Flutter

I'm new to flutter and I want to organise the elements that I get from Firestore in a list with StickyHeaders. I would like to do it with an itemBuilder and the snapshot I get from the database.
My problem is the itemBuilder builds each item separately and has to be returned, but StickyHeader needs to have all items added as children.
How can I achieve this? Just as a reference I paste my code without the StickyHeader. buildItemBubble returns a Card Widget.
StreamBuilder(
stream: buildSnapshots(_filters),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(
child: CircularProgressIndicator(),
);
return snapshot.data.documents.length == 0
? Center(child: Text('It\s empty here...'))
: CupertinoScrollbar(
child: ListView.builder(
key: new PageStorageKey('randomkey'),
shrinkWrap: true,
padding: const EdgeInsets.all(10.0),
itemCount: snapshot.data.documents.length,
itemBuilder: (ctx, i) =>
buildItemBubble(snapshot, i),
),
);
},
)

Flutter Firestore returning null (retrieve data from subcollection)

What do I want it to do
I want to fetch a list of the amount of photo's a user has stored in it's profile updated in realtime.
I want my code to fetch the links from a user profile but I am constantly getting an error.
The value "Text("${document['link']}")" gives null.
What have I tried
Read and applied every problem/solution on stackoverflow on this issue
My code
I already have another streambuilder which is used to fetch the names of the instances so that's where the other DocumentSnapshot is coming from. Now I just want a streambuilder which focusses on the pictures.
Widget imageList(DocumentSnapshot document //I use this to fetch the current document ID) {
print(_uploadedFileURL);
return StreamBuilder(
stream: Firestore.instance
.collection('Test')
.document(document.documentID).collection('foto')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Text("Loading");
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) =>
_imageList(context, snapshot.data.documents[index]));
},
);
}
//Widget to show the Text but this returns null
Widget _imageList(BuildContext context, DocumentSnapshot document) {
return Card(
child: prefix0.Column(
children: <Widget>[
prefix0.Text("${document['link']}")
],
),
);
}
Get data from the doc snapshot with its data method...
prefix0.Text("${document.data()['link']}")