Step 4: Create an infinite scrolling ListView

Before proceeding, it would be good to reflect on what we have achieved so far.

So far we have:

  • created a Flutter app from template
  • ran the template app in debug mode using a newly created Android Emulator
  • experimented with hot-reload, changing our app source and seeing changes immediately
  • verified (and modified if necessary) the template app's pubspec.yaml uses-material-design: true in its flutter: section
  • added the english_words package as a dependency using flutter pub add english_words and verifying changes in pubspec.yaml
    • pulled the package dependency into the project using flutter pub get
    • used the dependency with import 'package:english_words/english_words.dart';
  • added to a column of widgets a new stateful widget of our own design that displays a random word pair
  • experimented with hot-reload to see how the build method relates to both rendering and word-pair generation

In this step, you’ll expand _HttpReqWordsState to generate and display a list of words. As the user scrolls the list (displayed in a ListView widget) it may in some cases grow infinitely. Thankfully, ListView’s builder factory constructor allows you to build a list view lazily, on demand.

  1. Add a _words list to the _HttpReqWordsState class for saving retrieved word pairs. Also, add a _biggerFont variable for making the font size larger.
class _HttpReqWordsState extends State<HttpReqWords> { final _words = <String>[]; // also try: var _words = <String>['a', 'b', 'c', 'd']; final _biggerFont = const TextStyle(fontSize: 18); // ... }
  1. Next, you’ll add a ListView widget to the build method of the _HttpReqWordsState class using the ListView.builder constructor. This method creates the ListView that displays the relevant element of the _words array.

    • The ListView class provides a builder property, itemBuilder, that’s a factory builder and callback function specified as an anonymous function.

    • Two parameters are passed to the function -- the BuildContext, and the row iterator, i.

      • The iterator begins at 0 and increments each time the function is called.
      • It increments twice for every suggested word pairing: once for the ListTile, and once for the Divider.
    • This model allows the suggested list to continue growing as the user scrolls.

    • Return a ListView widget from the build method of the _HttpReqWordsState class using the ListView.builder constructor:

You will have to add import 'dart:developer' as developer to the top of the file for developer.log to work.

// note: you will have to add the following at the top of the file for developer.log to work: // import 'dart:developer' as developer; class _HttpReqWordsState extends State<HttpReqWords> { final _words = <String>[]; // also try: var _words = <String>['a', 'b', 'c', 'd']; final _biggerFont = const TextStyle(fontSize: 18); @override Widget build(BuildContext context) { return build_v1(context); } @override Widget build_v1(BuildContext context) { return ListView.builder( // ← 0 padding: const EdgeInsets.all(16.0), itemCount: _words.length, // ← 1 itemBuilder: /*1*/ (context, i) { // ← 2 developer.log('Building with context and $i', name: 'my.app.category'); return Column( // ← 3 children: <Widget>[ ListTile( // ← 4 title: Text( // ← 5 _words[i], // ← 6 style: _biggerFont, ), ), Divider(height: 1.0), // ← 7 ], // end children ); // end Column }, // end itemBuilder ); // end ListView } @override Widget build_v0(BuildContext context) { // this will get more interesting as we progress in the tutorial // for now, simply return a Text widget holding a random word pair final wordPair = WordPair.random(); return Text(wordPair.asPascalCase); } }

/0/ The ListView.builder constructor creates and displays a Text widget once per word pairing. This text widget holding each new pair is returned as a ListTile, which allows you to make the rows more attractive.

/1/ The ListView is initialized to show as many items for itemCount as in _words, which can grow.

/2/ The itemBuilder callback is called once per suggested word pairing, along with the index i, and places each word pair into a Text widget, contained within a ListTile row, contained within a Column.

/7/ Add a one-pixel-high divider widget at the bottom of each row in the ListView. Note that the divider might be difficult to see on smaller devices.

If you run it now, you should get an RenderBox exception Failed Assertion having to do with hasSize. Let's fix that.

RenderBox error

First, we no longer need our counter and floating action button. So, let's make HttpReqWords the only child of the Scaffold in _MyHomePageState Modify the build method of _MyHomePageState to only include a HttpReqWords widget.

Try it on your own before you peek at the code below.

Warning

Certain changes are a bit hard for the emulator to hot-reload.

You may have to occasionally restart the app (the refresh icon in vscode, or Ctrl + Shift + F5)

@override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done // by the _incrementCounter method above. // // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets. return Scaffold( appBar: AppBar( // TRY THIS: Try changing the color here to a specific color (to // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar // change color while the other colors stay the same. backgroundColor: Theme.of(context).colorScheme.inversePrimary, // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. title: Text(widget.title), ), body: const Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. child: HttpReqWords(), ), // This trailing comma makes auto-formatting nicer for build methods. ); }

Next, we will try a hard-coded, short-term fix: use the "also try:" version of the declaration and initialization of _words in _HttpReqWordsState. Verify you see the words you expect before peeking at the code.

App has infinite scrolling list view with four hard coded words

// comment out final _words and replace with the "also try" // final _words = <String>[]; var _words = <String>['a', 'b', 'c', 'd'];

When you see the expected result, proceed to the next section, which fetches words from the web. Consider making another commit to checkpoint your work.