Search code examples
listviewindexingflutterheader

How to make listview with headers and ListTile?


I have ListView and need to insert few headers in current position (index). But if I use approach from billow ,my index not in order,I lose 1st and last place from list...

if I add itemCount: _list.length + 1, and latter index -= 1; I got first section right but that not working for next header..

class CheckList extends StatefulWidget {
  bool isCheck = false; //for checkbox
  final String value; //geting value from other class
  List<String> _list = [];

  final List<ListItem> items;

  CheckList({Key key, this.value, this.items}) : super(key: key);

  @override
  _Question createState() => _Question();
}

class _Question extends State<CheckList> {
  BuildContext context;


  _getFromDB() async {
    widget._list = await DB().get2(widget.value, 'Description');

    widget._list.insert(0, "Part 1:...");
    widget._list.insert(18, "Part 2:...");
    widget._list.insert(49, "Part 3:...");

    final List<ListItem> items = widget._list.map((value) {
      final index = widget._list.indexOf(value);
      if ([0, 18, 49].contains(index)) {
        return HeaderItem(value);
      } else {
        return MessageItem(value);
      }
    }).toList();

    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    this.context = context;

    return Scaffold(
        appBar: AppBar(
          title: Text(
            'TITLE APP',
          ),
        ),
        body: Container(
          child: getListView(), //listview populated from db
        ));
  }

  Widget getListView() {
    _getFromDB();
    var listview = ListView.builder(
      itemCount: widget.items.length,
      itemBuilder: (context, index) {
        final item = widget.items[index];
        if (item is HeaderItem) {
          return Column(
            children: <Widget>[
              Text(
                item.heading,
              ),
              Divider(),
            ],
          );
        } else if (item is MessageItem) {
          return ListTile(
            dense: true,
            leading: Checkbox(
              value: item.checked,
              onChanged: (value) {
                setState(() {
                  item.checked = value;
                });
              },
            ),
            title: Text(
              item.title,
            ),
            // subtitle: Text(_list2[index]),
          );
        }
      },
    );

    return listview;
  }
}

abstract class ListItem {}

class HeaderItem implements ListItem {
  final String heading;

  HeaderItem(this.heading);
}

class MessageItem implements ListItem {
  final String title;
  bool checked = false;

  MessageItem(this.title);
}

So how to correct index nubers and show all items from list with inserted headers..


Solution

  • You could use this approach...

    Create classes that will represent different item types (HeaderItem,MessageItem), then map _list to list of items (you will need to have header values inside your _list! - for example you can do _list.insert(18, "Part 2:...")) and finally use ListView.builder.

    Full example:

    import 'package:flutter/material.dart';
    
    void main() {
      final _list = List<String>.generate(100, (index) => "$index");
    
      _list.insert(0, "Part 1:...");
      _list.insert(18, "Part 2:...");
      _list.insert(49, "Part 3:...");
    
      final List<ListItem> items = _list.map((value) {
        final index = _list.indexOf(value);
        if ([0, 18, 49].contains(index)) {
          return HeaderItem(value);
        } else {
          return MessageItem(value);
        }
      }).toList();
    
      runApp(
        MaterialApp(
          home: App(items: items),
        ),
      );
    }
    
    class App extends StatelessWidget {
      final List<ListItem> items;
    
      const App({Key key, this.items}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: ListView.builder(
            itemCount: items.length,
            itemBuilder: (context, index) {
              final item = items[index];
    
              if (item is HeaderItem) {
                return Column(
                  children: <Widget>[
                    Text(
                      item.heading,
                    ),
                    Divider(),
                  ],
                );
              } else if (item is MessageItem) {
                return ListTile(
                  leading: Text('$index'),
                  title: Text(
                    item.title,
                    style: TextStyle(fontSize: 18),
                  ),
                );
              }
            },
          ),
        );
      }
    }
    
    abstract class ListItem {}
    
    class HeaderItem implements ListItem {
      final String heading;
    
      HeaderItem(this.heading);
    }
    
    class MessageItem implements ListItem {
      final String title;
    
      MessageItem(this.title);
    }
    

    Example with checkboxes:

    App is now StatefulWidget, MessageItem has checked property.

    import 'package:flutter/material.dart';
    
    void main() {
      final _list = List<String>.generate(100, (index) => "$index");
    
      _list.insert(0, "Part 1:...");
      _list.insert(18, "Part 2:...");
      _list.insert(49, "Part 3:...");
    
      final List<ListItem> items = _list.map((value) {
        final index = _list.indexOf(value);
        if ([0, 18, 49].contains(index)) {
          return HeaderItem(value);
        } else {
          return MessageItem(value);
        }
      }).toList();
    
      runApp(
        MaterialApp(
          home: App(items: items),
        ),
      );
    }
    
    class App extends StatefulWidget {
      final List<ListItem> items;
    
      const App({Key key, this.items}) : super(key: key);
    
      @override
      _AppState createState() => _AppState();
    }
    
    class _AppState extends State<App> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: ListView.builder(
            itemCount: widget.items.length,
            itemBuilder: (context, index) {
              final item = widget.items[index];
    
              if (item is HeaderItem) {
                return Column(
                  children: <Widget>[
                    Text(
                      item.heading,
                    ),
                    Divider(),
                  ],
                );
              } else if (item is MessageItem) {
                return ListTile(
                  leading: Text('$index'),
                  title: Text(
                    item.title,
                    style: TextStyle(fontSize: 18),
                  ),
                  trailing: Checkbox(
                    value: item.checked,
                    onChanged: (value) {
                      setState(() {
                        item.checked = value;
                      });
                    },
                  ),
                );
              }
            },
          ),
        );
      }
    }
    
    abstract class ListItem {}
    
    class HeaderItem implements ListItem {
      final String heading;
    
      HeaderItem(this.heading);
    }
    
    class MessageItem implements ListItem {
      final String title;
      bool checked = false;
    
      MessageItem(this.title);
    }