How to add more data to Flutter ListView Builder via Provider without resetting the scroll position to top

Viewed 994

I am building a Listview Builder in Flutter and populating the items via a provider which fetches the data from firestore in a paginated manner.

So the overall flow is -

  1. Main Widget gets access to items via Provider that returns a Future.
  2. Main Widget then creates a ListView Builder wrapped inside a FutureBuilder.
  3. The provider uses a service to query Firestore and gets an initial set of items via a getter
  4. The provider then calls getMoreItemsData() for getting the next set of items from the scroll listener when the user scrolls to the bottom of the list.

The issue I am facing is that after calling getMoreItemsData() my widget scroll position gets reset to the first item, whereas I want it to keep it at the last item so that the user can continue scrolling. I think it is happening due to the notifyListeners() that gets called from getMoreItemsData() but I need it to refresh my list of items. Is there a way to do this without resetting the scroll position to the top of the list?

Here is the Main widget with steps 1 and 2:

@override
  Widget build(BuildContext context) {

    final itemsData = Provider.of<itemsProvider>(context);

    List<item> items;
    ScrollController _scrollController = new ScrollController();

    return FutureBuilder(
      future: itemsData.items,
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        if (snapshot.connectionState != ConnectionState.done) {
          return Center(
              child:
                  CircularProgressIndicator(backgroundColor: Colors.red));
        } else {
          if (snapshot.hasError) {
            // return: show error widget
          }
          _scrollController
            ..addListener(
              () {
                double maxScroll = _scrollController.position.maxScrollExtent;
                double currentScroll = _scrollController.position.pixels;
                double delta = MediaQuery.of(context).size.height * 0.30;
                if (maxScroll - currentScroll <= delta) {
                  itemsData.getMoreitemsData();
                }
              },
            );
          items = snapshot.data ?? [];
          return ListView.builder(
        itemCount: items.length,
        controller: _scrollController,
        itemBuilder: (BuildContext context, int index) =>
            Card(
            margin: EdgeInsets.only(top: 10.0),
            elevation: 4,
            child: Text(items[index].caption),
        // ...  Card defined with some items
             ),
          );
        }
      },
    );
  }
}

Here is my provider snippet with steps 3 and 4

class ItemsProvider with ChangeNotifier {
    bool _getMoreData = false;

Future<List<Item>> get items async {
    _items = await Service.getItems();
    return _items;
  }

void getMoreItemsData() {
    _getMoreItems = true;
    notifyListeners();
  }
}

Here is my service snippet:

class Service {
  static final firestoreInstance = Firestore.instance;
  static DocumentSnapshot lastDocument, firstDocument;
  static final _paginationNext = 3;
  static List<Item> _itemData = [];

static Future getItems(bool getMoreData) async {
    var dataFirestore;
    if (!getMoreData) {
      _itemData = [];
    }

    if (getMoreData) {
      dataFirestore = await firestoreInstance
          .collection("items")
          .orderBy("sell", descending: true)
          .startAfterDocument(lastDocument)
          .limit(_paginationNext)
          .getDocuments();
    } else {
      dataFirestore = await firestoreInstance
          .collection("items")
          .orderBy("sell", descending: true)
          .limit(_paginationNext)
          .getDocuments();
    }

    lastDocument =
        dataFirestore.documents[dataFirestore.documents.length - 1];
    dataFirestore.documents.forEach(
      (doc) {
        _itemData.add(Item.fromJson(doc.data));
      },
    );
    return _itemData;
  }

Here is a code snippet for Items model

class Item {
  String item;
  String sell;

  Item({
      @required this.item,
      @required this.sell,
    // More ...
     });

  factory Item.fromJson(Map<dynamic, dynamic> json) {
    return new Item(
      item: json['item'],
      sell: json['sell'],
      // More ...
    );
  }
}
0 Answers
Related