Getting to know Flutter: List Lazy loading
Tags: dart, flutter, beginners, tutorial
Published: 2021-03-17 16:04:49

Sometimes you may need to handle of hundreds of data received from an API or fetched from a DB. **ListView lazy load** is absolutely what you need to maintain a responsive layout.

First of all, let’s begin doing a simple setup for our example:

- Declare a Stateful Widget,
- Add a ScrollController,
- Add a ListView, 
- Assign the `_controller` as controller of our ListView.

```dart
final ScrollController _controller = ScrollController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Lazy list'),
      ),
      body: ListView.builder(
        controller: _controller,
        itemCount: 0,
        itemBuilder: (context, index) {
          return;
        },
      ),
    );
  }
```

 

### Listening to the Scroll Event

Let’s add a listener to our `_controller` Inside `initState` method, he will be responsible to handle the pagination of our ListView.

```dart
@override
  void initState() {
    _controller.addListener(_onScroll);
    super.initState();
  }

  _onScroll() {

  }
```

Since we added a listener to our controller we must call `dispose()` method too to remove it from memory once we dismiss the current screen. 

```dart
@override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
```

 

Let’s add two parameters, one for the loading state and one for the data, I’ll call it `_dummy` and it will be List of Strings, but you can use any type of object. 

```dart
class _MyHomePageState extends State<MyHomePage> {
  final ScrollController _controller = ScrollController();

  bool _isLoading = false;
  List<String> _dummy = List.generate(20, (index) => 'Item $index');
```

 

Now let’s set up our ListView.

```dart
@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('lazy list'),
      ),
      body: ListView.builder(
        controller: _controller,
        itemCount: _isLoading ? _dummy.length + 1 : _dummy.length,
        itemBuilder: (context, index) {
          if (_dummy.length == index)
            return Center(
                child: CircularProgressIndicator()
            );
          return ListTile(
            title: Text(
                _dummy[index]
            )
          );
        },
      ),
    );
  }
```

 

When we reach the end of the list we will show a loader as the last element, else we’ll show an element of dummy. Now let’s get back to the scroll thing. The following method shows how to know when the user reaches the bottom of the list, and when he does it we’ll update the `_isLoading` variable to show the spinner.

```dart
_onScroll() {
    if (_controller.offset >=
        _controller.position.maxScrollExtent &&
        !_controller.position.outOfRange) {
      setState(() {
        _isLoading = true;
      });
      _fetchData();
    }
}
```

 

### Data Fetch

Now let’s simulate an API call or a DB fetch using a more concise `Future.delayed`:

```dart
Future _fetchData() async {
  await new Future.delayed(new Duration(seconds: 2));
}
```

 

Just after 2 seconds we will receive the data (whatever it is) and update the state of the widget to reflect the changes.

```dart
Future _fetchData() async {
  await new Future.delayed(new Duration(seconds: 2));
  int lastIndex = _dummy.length;

  setState(() {
     _dummy.addAll(
         List.generate(15, (index) => "New Item ${lastIndex+index}")
     );
    _isLoading = false;
  });
}
```

Using the **ScrollController** is not so hard, but you can go deeper using the **ScrollNotification** and more deeper adding animations with CustomScrollView.

Need to check the full code? Nothing easier => [GitHub](https://github.com/Alessandro-v/lazy_loading_list).