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 itsflutter:
section - added the
english_words
package as a dependency usingflutter pub add english_words
and verifying changes inpubspec.yaml
- pulled the package dependency into the project using
flutter pub get
- used the dependency with
import 'package:english_words/english_words.dart';
- pulled the package dependency into the project using
- 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.
- 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);
// ...
}
-
Next, you’ll add a
ListView
widget to thebuild
method of the_HttpReqWordsState
class using theListView.builder
constructor. This method creates theListView
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.
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.
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.
// 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.