Home > Net >  Adding a spinner to a ListView builder in Flutter
Adding a spinner to a ListView builder in Flutter

Time:11-22

I have a ListView builder in my Flutter app that does some heavy computation for each item in the list, so it can take some time to populate new elements. The computation is all synchronous code. I'd like to add a spinner while the list is adding new elements, but don't want to change all the computation code. So I think that means making the itemBuilder itself async, but I'm not sure how to do this. Is it possible?

Below is the code I had, trying to use the EasyLoading package, but that didn't work, because the APIs from EasyLoading are async, which wasn't mentioned in the description.

class Matches extends StatefulWidget {
  final Iterator<String> _match;

  Matches(this._matcher);

  @override
  MatchesState createState() => MatchesState(_match);
}

class MatchesState extends State<Matches> {
  final Iterator<String> _matcher;
  final _matches = <String>[];

  MatchesState(this._matcher);

  _next() {
    // This is async so doesn't work
    EasyLoading.show(status: 'Searching...');
    // Get the next ten results
    for (var i = 0; i < 10; i  ) {
      // _matcher is a synchronous iterator that does some
      // computationally intensive work each iteration.
      if (_matcher.moveNext()) {
        _matches.add(_matcher.current);
        EasyLoading.showProgress(0.1 * i, status: 'Searching...');
      } else {
        EasyLoading.showSuccess('Searching...');
        break;
      }
    }
    EasyLoading.dismiss(animation: false);
  }

  Widget _buildMatches() {
    return ListView.builder(
      padding: const EdgeInsets.all(16.0),
      itemBuilder: (context, i) {
        if (i.isOdd) return Divider();
        final index = i ~/ 2;
        if (index >= _matches.length) {
          _next();
        }
        final row = (index < _matches.length) ? _matches[index] : '';
        return ListTile(title: Text(row));
      }
    );
  }

  @override
  Widget build(BuildContext context) {
    return FlutterEasyLoading(
      child: Scaffold(
        appBar: AppBar(
        title: Text(TITLE),
      ),
      body: _buildMatches(),
    ));
  }
}

It seems like I need to use a FutureBuilder. I found a good article here https://blog.devgenius.io/understanding-futurebuilder-in-flutter-491501526373 but it (like most I have seen) assumes that all the results are computed before the list is populated, whereas I want to build my list lazily as the user scrolls through it. So I need something like FutureListItemBuilder, if that only existed.

Update: I found this article which is closer to what I want to do; going to see if I can make it work for me: https://medium.com/theotherdev-s/getting-to-know-flutter-list-lazy-loading-1cb0ed5de91f

CodePudding user response:

While your data is a synchronous, so you can fetch about loading in the ListView.builder so you can add if statement to your code, like this:

ListView.builder(
  itemCount: _matches.length,
  itemBuilder: (context, i) {
    if (sth.isLoading){
      return CircularProgressIndicator();
    }
    etc...
  }
),

And I didn't see any of async, await , and the itemCount in your code!!!

CodePudding user response:

I believe I have figured it out. The trick was to use Future.delayed on each update, and as Muhammad Qazmouz mentioned, just return the spinner if I hadn't retrieved a requested item yet. I might need to refine this a bit, but the code is below:

class MatchesState extends State<MatchingWords> {
  final Iterator<String> _matcher;
  final _matches = <String>[];
  bool _isDone = false; // no more matches to fetch
  var _fetchUntil = 20;

  MatchesState(this._matcher);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(TITLE),
        ),
        body: ListView.builder(
            padding: const EdgeInsets.all(16.0),
            itemBuilder: (context, i) {
              if (i.isOdd) return Divider();
              final index = i ~/ 2;
              if (!_isDone && index >= _matches.length) {
                _fetchUntil = _matches.length   20;
                Future.delayed(const Duration(seconds: 0), () {
                  _fetchNext();
                });
                // Return a spinner for now
                return Center(child: CircularProgressIndicator());
              }
              return _buildRow(
                  (index < _matches.length) ? _matches[index] : '');
            }));
  }

  _fetchNext() {
    if (_matcher.moveNext()) {
      _matches.add(_matcher.current);
      if (_matches.length < _fetchUntil) {
        Future.delayed(const Duration(seconds: 0), () {
          _fetchNext();
        });
        return;
      }
    } else {
      _isDone = true;
    }
    // We fetched another 20 or no more left; update UI.
    setState(() {});
  }

  • Related