ScrollController jumpto when ListView.builder complete - flutter

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).

Related

What widget should I use?

This is what I am doing now.
Main Page:
I would like to make it same like this picture.Example:
I have tried couple ways and widget to build it but couldn't figure it out. Also, I want to retrieve the data from the Firebase and show them as the content.
Code 1: https://pastebin.com/A0nK1riQ
Code 2: https://pastebin.com/i1T7gBNy
Widget build(BuildContext context) {
return Container(
child: StreamBuilder(
stream: _products.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
if (streamSnapshot.hasData) {
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length,
itemBuilder: (context, index) {
final DocumentSnapshot documentSnapshot =
streamSnapshot.data!.docs[index];
return Container(
margin: const EdgeInsets.all(10),
child: ListTile(
title: Text(documentSnapshot['name']),
subtitle: Text(documentSnapshot['price'].toString()),
trailing: SizedBox(
width: 100,
),
),
);
});
}
return SizedBox.shrink();
}),
);
}
You may use GridView.builder
https://api.flutter.dev/flutter/widgets/GridView-class.html
and in gridview build use column
According to me, first you have to check which NavigationRail icon clicked then put the condition on it's GestureDetector like
// global variable
String itemClickedValue = "";
then set the value in it according to user click
itemClickedValue = "first";
then check the condition while fetching data like
if(itemClickedValue.compareTo("first") == 0){
// pass that documentId or api and then show in list
}

how to use two future builder in one page in flutter

In my Flutter app, I need to display two lists that are coming from the database, but I am having trouble getting both lists to display on the same screen. I am using two FutureBuilder widgets, but the first list is displaying correctly while the second list is still loading.
Here is the code I am using:
var future1 = FutureBuilder<List<QuranTextModel>>(
future: database.getQuranText(),
builder: (context, snapshot) {
if(snapshot.hasData){
return ScrollablePositionedList.builder(
itemScrollController: scrollToIndex,
itemCount: snapshot.data!.length,
initialScrollIndex: widget.position,
itemBuilder: (context, index) {
// Build the list item widget here
});
}else{
return const Center(child: CircularProgressIndicator(),);
}
}
);
var future2 = FutureBuilder<List<UrduTextModel>>(
future: database.getUrduTranlation(),
builder: (context, snapshot) {
if(snapshot.hasData){
return ScrollablePositionedList.builder(
itemScrollController: scrollToIndex,
itemCount: snapshot.data!.length,
initialScrollIndex: widget.position,
itemBuilder: (context, index) {
// Build the list item widget here
});
}else{
return const Center(child: CircularProgressIndicator(),);
}
}
);
Column(
children: [
SizedBox(
height: 200,
child: future1,
),
SizedBox(
height: 200,
child: future2,
),
],
)
The first FutureBuilder is used to build a list of QuranTextModel objects, and the second FutureBuilder is used to build a list of UrduTextModel objects. I am using a Column widget to display both lists, with each list contained within a SizedBox widget to give it a fixed height.
The issue I am having is that only the first list is displaying correctly, while the second list is still loading. How can I get both lists to display on the same screen?
Thank you for any help you can provide!
SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: 200,
child: future1),
SizedBox(height: 200,child: future2,)
],
),
),
Try this.
also you have to check your future status before populate you can check that by using
if (snap.connectionState == ConnectionState.done) { your code. you can check does snpa has data in it. }
connection state has deferent states that can help you to make your UI more interactive

How should I get a scrollable set of widgets without overflow in case of FutureBuilder

I have a FutureBuilder in my code that generates a List of widgets based on the data fetched from Firestore. I want the screen to be scrollable without any overflow error. Here's my code:
FutureBuilder<QuerySnapshot>(
future:
FirebaseFirestore.instance.collection('Assignments').get(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
return Column(
children: snapshot.data?.docs.map((doc) {
return Column(
children: [
Subject(
dataMap: doc.data() as Map<dynamic, dynamic>),
Divide()
],
);
}).toList() ??
[],
);
} else {
// or your loading widget here
return Text("Loading....");
}
},
),
Here is the output I'm getting on my screen:
I want this screen to be scrollable. I have used SingleChildScrollview but that is making full screen scrollable whereas I want only the list of widgets below the Assignment heading to be scrollable.
You can use a ListView instead of multiple Column. A ListViewis scrollable so you shouldn't get any problems with overflowing widgets. The ListView.Builder constructor takes the itemCount and a builder where you simply return a widget for each ListItem.
Then your FutureBuilder would look like this:
FutureBuilder<QuerySnapshot>(
future:
FirebaseFirestore.instance.collection('Assignments').get(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
return Column(
children: [
Subject(
dataMap: snapshot.data.docs[index].data() as Map<dynamic, dynamic>),
Divide()
],
);
},
);
} else {
// or your loading widget here
return Text("Loading....");
}
},
),

Flutter - ListView.builder not scrollable

I have my ListView.builder inside Expanded widget which render widgets correctly on the screen but I cannot scroll the widgets rendered by it.
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: getPostsForUid(),
builder: (_, snap) {
return Expanded(
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: snap.data.length,
itemBuilder: (_, index) {
if (snap.data[index]['post_type'] == 'real_estate') {
return realEstate(snap, index);
}
else if (snap.data[index]['post_type'] == 'video_sharing') {
return videoSharing(snap, index);
}
else {
return Text('');
}
},
),
);
},
),
);
}
Try using ScrollPhysics() class,
physics: ScrollPhysics(),
here is the link for reference for the same.
You should set your physics to AlwaysScrollableScrollPhysics().
The docs state the following:
Scroll physics that always lets the user scroll.
This overrides the default behavior which is to disable scrolling when there is no content to scroll. It does not override the handling of overscrolling.
On Android, overscrolls will be clamped by default and result in an overscroll glow. On iOS, overscrolls will load a spring that will return the scroll view to its normal range when released.
Here is an image of "overscroll glow" for you to understand what that means.
Consider using shrinkWrap: false to expand your contents in case they are bounded.
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: getPostsForUid(),
builder: (_, snap) {
return Expanded(
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: snap.data.length,
itemBuilder: (_, index) {
if (snap.data[index]['post_type'] == 'real_estate') {
return realEstate(snap, index);
}
else if (snap.data[index]['post_type'] == 'video_sharing') {
return videoSharing(snap, index);
}
else {
return Text('No data available.');
}
},
),
);
},
),
);
}
See the docs:
https://api.flutter.dev/flutter/widgets/AlwaysScrollableScrollPhysics-class.html
Add Physics:AlwaysScrollable() in your Listview.

Nested ListView, StreamBuilder and Subcollection

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.