Almost every non-trivial mobile app today is likely to have lists in its layouts. That's because using a scrollable list is often the most straightforward way to display a large number of similar items on a small screen.
The Flutter framework offers several widgets you can use to efficiently, and with minimal code, create and display such lists. In this tutorial, I'll show you how to use them with both local and remote data sources.
1. Displaying a Non-Scrollable List
If you need to display a small number of similar items, and you are sure that the user's screen will be able to accommodate all of them at the same time, using the Column
widget is the most efficient thing to do.
To create a list in your Flutter app, you can use the List
class that the Dart programming language offers. After creating the list, you can use its add()
method to add any number of items to it. The following code shows you how to create a list containing three RaisedButton
widgets:
// Create list List<RaisedButton> myItems = new List(); // Add three button widgets to it myItems.add(new RaisedButton( child: new Text("Twitter"), onPressed: (){} )); myItems.add(new RaisedButton( child: new Text("Facebook"), onPressed: (){} )); myItems.add(new RaisedButton( child: new Text("Reddit"), onPressed: (){} ));
Note that each item in the list has an empty onPressed
event handler associated with it because, without that, the item would be disabled.
Now that the list is ready, you can directly assign it to the children
property of the Column
widget to be displayed. Usually, however, you'll also want to specify where the list items should be placed on the screen. Because the Column
widget is a flex widget, you can control the positions of the items along its main axis and cross axis using the mainAxisAlignment
and crossAxisAlignment
properties. By default, for the Column
widget, the main axis is the vertical axis, and the cross axis is the horizontal axis.
The following code shows you how to stretch the three buttons across the horizontal axis and place them in the center of the screen vertically:
Column column = new Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: myItems );
Here's what the column will look like now:
It is important to note that you will encounter a runtime error if a Column
widget is unable to accommodate all its children. For example, if you had over a dozen RaisedButton
widgets in your list instead of three, you would see an error message that looks like this:
2. Displaying a Simple Scrollable List
For slightly larger lists, lists whose contents are likely to overflow the screen, you must consider using a ListView
widget because it supports scrolling.
You may have noticed that the code we wrote to create the list in the previous step was both lengthy and repetitive. Creating a larger list using the same approach can be very tedious. An easy alternative approach is to use two lists instead: one containing the data, and one containing the widgets.
Here's how you can use the []
operator to quickly create a data list, which, for now, only contains several strings:
List<String> data = <String>["Twitter", "Reddit", "YouTube", "Facebook", "Vimeo", "GitHub", "GitLab", "BitBucket", "LinkedIn", "Medium", "Tumblr", "Instagram", "Pinterest"];
To convert the above list of strings into a list of RaisedButton
widgets, you can use the map()
and toList()
methods. With the map()
method, you can you use each string to generate a new RaisedButton
widget. And with the toList()
method, you can convert the Iterable
object returned by the map()
method into an actual List
object. The following code shows you how:
List<RaisedButton> myWidgets = data.map((item) { return new RaisedButton( child: new Text(item), onPressed: () async { String url = "https://${item}.com"; if(await canLaunch(url)) await launch(url); } ); }).toList();
For the sake of completeness, the above code also shows you how to create an onPressed
event handler that uses the canLaunch()
and launch()
methods offered by the url_launcher
package to open the website the user selected in the default browser.
Once your list is ready, you can pass it to the children
property of the ListView
widget to display it.
ListView myList = new ListView( children: myWidgets );
At this point, if you run the app, you should be able to scroll through the list and press any button to launch the associated website.
3. Creating a Grid
The ListView
widget allows you to place only one item on its cross axis. The item will, by default, be stretched to occupy all the space available on that axis. If you want more flexibility, you should consider using a GridView
widget instead, which lets you specify how many items you want on the cross axis.
The following code uses the GridView.count()
constructor to create a GridView
widget that displays two items per row:
GridView myGrid = GridView.count( crossAxisCount: 2, children: myWidgets );
Here's what the grid looks like:
4. Displaying Large Lists
For data lists that contain more than a few dozen items, you should avoid generating widget lists manually, the way you did in an earlier step. Why? Because creating a widget is an expensive operation, and large lists of widgets can consume a lot of memory.
Instead, you should use the IndexedWidgetBuilder
function, which lets you generate widgets only when the user needs to see them. With it, you can lazily generate widgets as the user scrolls through your ListView
widget.
It is quite unlikely that you're going to have large amounts of data defined right inside your apps. Usually, you would be fetching such data from a remote server. Therefore, to give you a realistic example, let me now show you how to fetch 100 questions from Stack Overflow using the Stack Exchange API and display them on demand.
Start by creating a subclass of the StatefulWidget
class, which will act as a container for your ListView
widget and override its createState()
method.
class VeryLargeListHolder extends StatefulWidget { @override State<StatefulWidget> createState() { return new MyState(); } }
The MyState
class mentioned in the above code doesn't exist yet, so create it and override its build()
method.
class MyState extends State<VeryLargeListHolder> { @override Widget build(BuildContext context) { // TODO } }
Next, add a List
object as one of the member variables of the class. You'll be using it to store the questions you downloaded from Stack Overflow. Additionally, add the API's endpoint as another variable.
List questions; String endpoint = "https://api.stackexchange.com/2.2/questions?" + "pagesize=100&order=desc&sort=activity&site=stackoverflow";
Unless you want your user to press a button to download the questions, I suggest you download them automatically when the widget is initializing. Accordingly, override the initState()
method and make a call to a new asynchronous method called loadData()
.
@override void initState() { super.initState(); loadData(); } void loadData() async { // More code here }
Inside the loadData()
method, you can use the get()
function of Dart's http
package to download the questions. The API endpoint returns a JSON document, which you can parse by using the json.decode()
function available in Dart's convert
package. The following code shows you how:
String rawData = (await http.get(endpoint)).body; Map jsonData = json.decode(rawData);
Once the JSON document has been converted into a Map
object, you can use the value associated with its items
key to initialize the questions
variable. The variable, however, is a part of the widget's state. Therefore, you must make sure you update it inside the setState()
method only. Here's how:
setState(() { questions = jsonData["items"]; });
At this point you can create a new ListView
widget using the ListView.builder()
constructor, which expects an IndexedWidgetBuilder
function and an item count as its arguments. For now, the item count is nothing but the size of the questions
list. Accordingly, add the following code inside the build()
method of the MyState
class:
ListView myList = ListView.builder( itemCount: questions == null ? 0 : questions.length, itemBuilder: (BuildContext context, int index) { // More code here } );
Inside the builder function, all you need to do is create a small widget tree to display various details about each question you downloaded. Flutter's material
package offers a very handy widget called ListTile
, which allows you to quickly create such a tree while adhering to the Material Design guidelines.
The following code shows you how to display the question's title and author, using the title
and subtitle
properties of the ListTile
widget:
return new ListTile( title: Text(questions[index]["title"]), subtitle: Text("Asked by ${questions[index]["owner"]["display_name"]}") );
Lastly, create a new Scaffold
widget, assign the ListView
widget to its body
property, and return it from the build()
method so it can be used with a MaterialApp
widget. Optionally, you can add an AppBar
widget to the Scaffold
widget.
return new Scaffold( appBar: new AppBar( title: new Text("LargeListDemo") ), body: myList );
Here's what the app will look like after it has downloaded the questions:
Conclusion
You now know how to work with lists in a Flutter application. In this tutorial, you learned not only how to create lists and grids that support large data sources, but also how to make each item inside them interactive. To learn more about lists in Flutter, you can refer to the official documentation.