I am creating a news feed for my app and as I scroll down data is fetched from Firestore. As soon as I scroll up the listview literally snaps to the very top skipping all the other items in between. The listview works fine if I load it with static data but when i pull data from Firestore, the issue reemerges. I am not sure what's causing this behaviour.
Video demonstrating the irregular scrolling behaviour
Here is my code
return StreamBuilder(
stream: Firestore.instance.collection(Constants.NEWS_FEED_NODE)
.document("m9yyOjsPxV06BrxUtHdp").
collection(Constants.POSTS_NODE).orderBy("timestamp",descending: true).snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData)
return Center(child: CircularProgressIndicator(),);
if (snapshot.data.documents.length == 0)
return Container();
//after getting the post ids, we then make a call to FireStore again
//to retrieve the details of the individual posts
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (_, int index) {
return FeedItem2(feed: Feed.fireStore(
snapshot: snapshot.data.documents[index]),);
});
},
);
Reason for the problem:
ListView
, andScrollViews
in general, tend to dispose of the children that are not currently visible on the screen. When we try to scroll back to the child, the child is reinitialized from scratch. But in this case, our child is a FutureBuilder; re-initializing it creates a progress indicator again just for a part of a second, then creates the page once again. This confuses the scrolling mechanism, throwing us around in non-deterministic ways.
Solution:
One way to solve this is to make sure that the progress indicator has the exact same size of the page, but in most cases, that is not too practical. So, we will resort to a method that is less efficient, but that will solve our problems; we will prevent ListView from disposing of the children. In order to do that, we need to wrap each child — that is, each
FutureBuilder
, with anAutomaticKeepAliveClientMixin
. This mixin makes the children ask their parent to keep them alive even when off-screen, which will solve our problem. So:
class KeepAliveFutureBuilder extends StatefulWidget {
final Future future;
final AsyncWidgetBuilder builder;
KeepAliveFutureBuilder({
this.future,
this.builder
});
@override
_KeepAliveFutureBuilderState createState() => _KeepAliveFutureBuilderState();
}
class _KeepAliveFutureBuilderState extends State<KeepAliveFutureBuilder> with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: widget.future,
builder: widget.builder,
);
}
@override
bool get wantKeepAlive => true;
}
Just had the same issue using Flutter 1.17.3. Based on the comment from @Ashton-Thomas here: Flutter ListView Jumps To Top I added:
cacheExtent: estimatedCellHeight * numberOfItems,
to my ListView
constructor and the problem went away. It has drastically increased the responsiveness of the entire list as well.
Note: my list only has about 50 items in it, so the cacheExtent
isn't extremely high. May need to adjust if you have hundreds/thousands of items.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With