Displaying elements in a list is a very common pattern in mobile applications. The user sees a collection of items and can scroll through them. The collection of items can be a list, a grid or another structured representation of data.

In this tutorial, we are going to learn how to create a simple ListView from a list of items. We’ll also learn to add items, add divider and onTap on the item. The ListView we are going to design contains a list of String.

1. Create Datasource

First, we’ll need a data source to work with ListView. For example, your data source might be a list of items, or products in a store. Most of the time, this data will come from the internet or a database.

class Item {
  Item({this.name, this.isSelected});

  String name;
  bool isSelected;
}

for (var i = 0; i < 15; i++) {
      items.add(new Item(name: "Default Items", isSelected: false));

2.Create Stateful Widget

Stateful widgets are useful when the list of items can change dynamically, e.g. add or remove items from list.

class ListScreen extends StatefulWidget {
  @override
  _ListScreenState createState() => _ListScreenState();
}

class _ListScreenState extends State<ListScreen> {
  List<Item> items = new List();

  _addItems() {
    setState(() {
      items.add(new Item(name: "My Item name", isSelected: false));
    });
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    for (var i = 0; i < 15; i++) {
      items.add(new Item(name: "Default Items", isSelected: false));
    }
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("Dynamic List"),
      ),
      body: new StringList(items: items),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: _addItems,
      ),
    );
  }
}

First, we have created a stateful widget. Then we have taken one floatingActionButton, and when the user presses the button, we will add the new item to the list and display.

3.Create ListView Widget

In order to display our List of Items, we’ll need to render each item as a Widget.

class StringList extends StatelessWidget {
  final List<Item> items;

  StringList({@required this.items});

  Widget build(BuildContext context) {
    // TODO: implement build
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text('${items[index].name}'),
        );
      },
    );
  }
}

ListView.builder creates a scrollable, linear array of widgets that are created on demand. It is appropriate for list views with a large (or infinite) number of children because the builder is called only for those children that are actually visible.

4.Adding ListView Divider

ListView.separated constructor is appropriate for list views with a large number of item and separator children. Separators only appear between list items: separator 0 appears after item 0 and the last separator appears before the last item.

   return ListView.separated(
      itemCount: items.length,
      separatorBuilder: (BuildContext context, int index) => Divider(
            color: Colors.deepOrange,
          ),
      itemBuilder: (context, index) {
        return ListTile(
          title: Text('${items[index].name}'),
        );
      },
    );

5.Handling onTap Events

onTap event Called when the user taps this list tile.

return ListTile(
          title: Text('${items[index].name}'),
          onTap: () {
            print("Item at $index is ${items[index].name}");
          },
        );

Complete example

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class Item {
  Item({this.name, this.isSelected});

  String name;
  bool isSelected;
}

class MyApp extends StatelessWidget {
  const MyApp({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dynamic List',
      home: ListScreen(),
    );
  }
}

class ListScreen extends StatefulWidget {
  @override
  _ListScreenState createState() => _ListScreenState();
}

class _ListScreenState extends State<ListScreen> {
  List<Item> items = new List();

  _addItems() {
    setState(() {
      items.add(new Item(name: "My Item name", isSelected: false));
    });
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    for (var i = 0; i < 15; i++) {
      items.add(new Item(name: "Default Items", isSelected: false));
    }
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("Dynamic List"),
      ),
      body: new StringList(items: items),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: _addItems,
      ),
    );
  }
}

class StringList extends StatelessWidget {
  final List<Item> items;

  StringList({@required this.items});

  Widget build(BuildContext context) {
    // TODO: implement build
    return ListView.separated(
      itemCount: items.length,
      separatorBuilder: (BuildContext context, int index) => Divider(
            color: Colors.deepOrange,
          ),
      itemBuilder: (context, index) {
        return ListTile(
          title: Text('${items[index].name}'),
        );
      },
    );
  }
}