Search code examples
flutterflutter-ui

How to add Date Header( like [January 2024]) to a list builder in flutter?


I want my Transaction screen to be categorized in months and i build the transaction screen using list view separated .

Here is my Transaction Screen code and i used the intl date package to get the date

class ScreenTransaction extends StatelessWidget {
  const ScreenTransaction({super.key});

  @override
  Widget build(BuildContext context) {
    TransactionDB.interface.refreshTransaction();
    CategoryDB.instance.refreshUI();
    return ValueListenableBuilder(
      valueListenable: TransactionDB.interface.transactionListNotifier,
      builder: (BuildContext ctx, List<TransactionModel> newList, _) {
        return ListView.separated(
          padding: EdgeInsets.all(10),
          itemBuilder: (ctx, index) {
            final _list = newList[index];
            return Slidable(
              key: Key(_list.id!),
              startActionPane: ActionPane(
                motion: DrawerMotion(),
                children: [
                  SlidableAction(
                    onPressed: (ctx) {
                      TransactionDB.interface.deleteTransaction(_list.id!);
                    },
                    icon: Icons.delete,
                    backgroundColor: Colors.red,
                    label: 'Delete',
                  ),
                ],
              ),
              child: Card(
                child: ListTile(
                  leading: CircleAvatar(
                    backgroundColor: _list.type == CategoryType.income
                        ? Colors.green
                        : Colors.red,
                    child: Text(
                      parsedDate(_list.date),
                      textAlign: TextAlign.center,
                    ),
                    radius: 30,
                  ),
                  title: Text('Rs ${_list.amount}'),
                  subtitle: Text(_list.categoryModel.name),
                ),
              ),
            );
          },
          separatorBuilder: (ctx, index) {
            return SizedBox(height: 10);
          },
          itemCount: newList.length,
        );
      },
    );
  }

  String parsedDate(DateTime date) {
    final _date = DateFormat.MMMd().format(date);
    final _splittedDate = _date.split(' ');
    return '${_splittedDate.last}\n ${_splittedDate.first}';
  }
}

This is how my transaction screen looks like now

I want a header like march 2024 and february 2024 and the the list items to be under that header.


Solution

  • Here's a step by step guide on how I would do it.

    1. Extract all the dates from your transaction list newList to a new list datesList

       List<String> datesList = newList
            .map((transaction) => parsedDateHeaders(transaction.date))
            .toList(); 
    

    I am using the parsedDateHeaders function to exclude the day from the date and create the header's format.

        String parsedDateHeaders(DateTime date) {
          return DateFormat.yMMMM().format(date);
        }
    

    2. Now that we have the list of dates by month, remove all the repeated/duplicated dates.

       datesList = datesList.toSet().toList(); 
    

    3. In your ListView widget, use the datesList (headers) as the itemCount.

       ListView.builder(
            itemCount: datesList.length,
            padding: const EdgeInsets.all(10),
            itemBuilder: (BuildContext context, int index) {...}..
    

    4. Now we can use a Column widget to add headers and the transactions underneath it. But instead of adding another builder for the transactions, use mapping instead.

    itemBuilder: (BuildContext context, int index) {
              return Column(
                children: [
                  Text(datesList[index],
                      style: TextStyle(
                          color: Colors.red, fontWeight: FontWeight.w900)),
                  Wrap(
                    children: newList.map((e) {...}
    

    5. Before mapping the transactions, use a function to compare the transaction's months with the header's month, then add them to a new list _filteredList

       List<TransactionModel> _filteredList = [];
       for (final transaction in newList) {
          var getMonth = parsedDateHeaders(transaction.date);
          if (getMonth == datesList[index]) { 
             _filteredList.add(transaction);
          }
        }
    

    You can also get the index of each transaction like this:

     int index2 = _filteredList.indexOf(e);
    

    6. Now add the condition that if _filteredList is empty (not the correct month), return SizedBox.shrink(), otherwise, return the transactions widget.

    if (index2 >= 0 && index2 < _filteredList.length) {
       return Container() 
    } else {
       return SizedBox.shrink();
    }
    

    See full demo here: https://dartpad.dev/?id=38334d9b986e39fa64cbdccb99636871