I have the below code which fetches more data when user scrolls to the bottom of the page. The issue is when the next set of items are appended to the model, the page flickers and moves to the top with the new list. How to keep the scroll position the same and just append the additional data below the current item?
Future<void> _loadMoreItems() async {
if (!_isLoading) {
setState(() {
_isLoading = true;
});
_scrollPosition = _scrollController.position.pixels;
String udid = await FlutterUdid.udid;
offset = offset + max;
NotificationsList notifsList = await getNotifications(offset, max, udid);
NotificationsList? existingList = await notificationsListFuture;
if (existingList == null) {
existingList = NotificationsList(
error: notifsList.error,
message: notifsList.message,
unread: notifsList.unread,
notifications: []);
}
if (notifsList.notifications.length > 0) {
existingList.notifications.addAll(notifsList.notifications);
} else {
offset = offset - max;
if (offset < 0) {
offset = 0;
}
}
setState(() {
if (notifsList.notifications.length > 0) {
notificationsListFuture = Future.value(existingList);
}
_isLoading = false;
});
WidgetsBinding.instance!.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.jumpTo(_scrollPosition);
}
});
}
}
I tried checking scroll position but it is not working. Please help me. Thanks.
The code for the widget tree:
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: widget.showAppbar ? TopAppBar() : null,
drawer: widget.showAppbar ? TopDrawer() : null,
body: RefreshIndicator(
onRefresh: _refreshItems,
child: FutureBuilder<NotificationsList?>(
future: notificationsListFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return _buildLoadingIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
final List<NotificationBody> notifs = snapshot.data?.notifications ?? [];
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(top: 16, left: 16, bottom: 8),
child: Text(
'Notifications',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: ScreenSizeService.h1FontSize),
),
),
Expanded(
child: notifs.isEmpty
? Padding(
padding: const EdgeInsets.only(top: 8, left: 8, bottom: 0),
child: Text('No notifications'),
)
: ListView.separated(
controller: _scrollController,
itemCount: notifs.length + 1, // Add 1 for loading indicator
separatorBuilder: (_, __) => Divider(height: 1),
itemBuilder: (context, index) {
if (index < notifs.length) {
return Padding(
padding: EdgeInsets.fromLTRB(0, 8, 0, 8),
child: ListTile(
title: Padding(
padding: EdgeInsets.only(bottom: 8.0),
child: Text(
notifs[index].title,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
),
subtitle: Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(bottom: 8.0),
child: Text(
notifs[index].msg,
style: TextStyle(
fontWeight: FontWeight.normal,
color: Colors.black,
),
),
),
Text(
notifs[index].date,
style: TextStyle(
color: Colors.grey,
),
),
],
),
),
trailing: Icon(Icons.arrow_forward, color: Colors.red),
onTap: () {
print("notification list item tapped");
},
),
);
} else if (_isLoading) {
return _buildLoadingIndicator();
} else {
return Container();
}
},
),
)
]
)
);
}
}
),
),
);
}
2
Answers
I fixed this by setting
PageStorageKey
.The problem is with the
setState
it again and again calls the build method and rebuilds the whole page.that’s why your listview reaches to the top when the new items are fetched.