ListView Lazy Loading in Up and Down Direction

Avatar

You want to create a ListView in flutter with lazy loading in up and down direction with boundaries?
Example: You want to display a scoreboard with 100.000 entries and want to start displaying items from 50.000. New entries should load automatically if scrolling up or down until entry 0 or entry 100.000 is reached (boundaries).

If yes, awesome, then that’s the right article for you.

There are multiple tutorials available explaining how to implement lazy loading with flutter. Unfortunately, I couldn’t find a guide so far which explains how to create a ListView which supports lazy loading in the up and down direction with specified boundaries. Therefore, I decided to create this article.

Let’s explain it with an example. The goal is to create the following list view:

ListView with lazy loading in up and down direction
ListView with lazy loading in up and down direction

1. Add the library bidirectional_listview to your project

Luckily, there’s already a library called bidirectional_listview which we’ll use for lazy loading in the up and down direction with boundaries.

Therefore, integrate bidirectional_listview into your project by adding it to pubspec.yaml:

dependencies:
  bidirectional_listview: ^1.0.1+1

2. Create an initial BidirectionalListView Widget

Let’s create an initial widget which contains a BidirectionalListView, first, without lazy loading.

Therefore, let’s create a simple class MyHome.dart. Please note: Executing this code will throw an error, because scroll boundaries have to be set. This will be added in step 3.

class MyHome extends StatefulWidget {
  @override
  _MyHomeState createState() => new _MyHomeState();
}

class _MyHomeState extends State<MyHome> {
  static const double kItemHeight = 30;
  BidirectionalScrollController controller;
  Map<int, String> items = new Map();
  double oldScrollPosition = 0.0;

  @override
  void initState() {
    super.initState();

    for (int i = -10; i <= 10; i++) {
      items[i] = "Item " + i.toString();
    }
    controller = new BidirectionalScrollController()
      ..addListener(_scrollListener);
  }

  @override
  void dispose() {
    controller.removeListener(_scrollListener);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
   // find out item counts
    List<int> keys = items.keys.toList();
    keys.sort();
    int negativeItemCount = keys.first;
    int itemCount = keys.last;

    return new Scaffold(
      body: new Scrollbar(
        child: new BidirectionalListView.builder(
          controller: controller,
          physics: AlwaysScrollableScrollPhysics(),
          itemBuilder: (context, index) {
            return Container(
                child: Text(items[index]),
                height: kItemHeight,
                padding: EdgeInsets.all(0),
                margin: EdgeInsets.all(0));
          },
          itemCount: itemCount,
          negativeItemCount: negativeItemCount.abs(),
        ),
      ),
    );
  }

  void _rebuild() => setState(() {});

  // Reload new items in up and down direction and update scroll boundaries
  void _scrollListener() {
    // Will be filled in next step
  }
}

3. Add lazy loading in up and down direction and scroll boundaries

Eventually, you already noticed that the method _scrollListener wasn’t filled in step 2.

In this step, we’re filling the _scrollListener method in order to load new list view items and to set scroll boundaries.

  // Reload new items in up and down direction and update scroll boundaries
  void _scrollListener() {
    // detect if scrolling up or down
    bool scrollingDown = oldScrollPosition < controller.position.pixels;

    // find out item counts
    List<int> keys = items.keys.toList();
    keys.sort();
    int negativeItemCount = keys.first.abs();
    int itemCount = keys.last;

    // calculate scroll border from where to load new items
    double positiveReloadBorder = (itemCount * kItemHeight - 3 * kItemHeight);
    double negativeReloadBorder =
        (-(negativeItemCount * kItemHeight - 3 * kItemHeight));
    
    // reload items
    bool rebuildNecessary = false;
    if (scrollingDown && controller.position.pixels > positiveReloadBorder) {
      for (int i = itemCount + 1; i <= itemCount + 20; i++) {
        items[i] = "Item " + i.toString();
      }
      rebuildNecessary = true;
    } else if (!scrollingDown &&
        controller.position.pixels < negativeReloadBorder) {
      for (int i = -negativeItemCount - 20; i < -negativeItemCount; i++) {
        items[i] = "Item " + i.toString();
      }
      rebuildNecessary = true;
    }

    // set new scroll boundaries
    try {
      BidirectionalScrollPosition pos = controller.position;
      pos.setMinMaxExtent(
          -negativeItemCount * kItemHeight, itemCount * kItemHeight);
    } catch (error) {
      print(error.toString());
    }
    if (rebuildNecessary) {
      _rebuild();
    }

    oldScrollPosition = controller.position.pixels;
  }

Finally, that’s it :-).

Sources:

4 thoughts on “ListView Lazy Loading in Up and Down Direction”

  1. Hello,

    Thank you for this tutorial. I find an issue when I try to execute this code, the list appear for element 0 to 10, I can see there’re in a scrollview because there’s a scroll limit animation when I go up and down but the other elements are not generated. I’m blocked with the initial list.

    I’ve try to execute the code founded on the Git of the project but I’ve got the same results.

    I execute the last version on a Samsung Galaxy A51.

    Reply
    • Dear Sébastien,

      I’m sorry that the code doesn’t work for you.

      To reproduce this issue, I started the example code in iOS simulator and Android simulator. Unfortunately, I couldn’t reproduce this issue.

      Could you please try the example above without any modification again on your side?

      Thanks,
      Christoph

      Reply

Leave a Comment