skip to Main Content

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


  1. Chosen as BEST ANSWER

    I fixed this by setting PageStorageKey.

    ListView.separated(
      controller: _scrollController,
      itemCount: notifs.length + 1, // Add 1 for loading indicator
      separatorBuilder: (_, __) => Divider(height: 1),
      key: PageStorageKey('notificationListKey'),
      itemBuilder: (context, index) {
    

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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search