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

78 Views Asked by At

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.

2

There are 2 best solutions below

0
On BEST ANSWER

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

0
On

You can group your transactions based on dates - assuming the transactions are already sorted by date - using one list.

First, create a function that takes a list of Transactions and return a list of Objects. The function iterates over the list of transactions and adds the transactions in order based on their date as follows:

  List<Object> _getGroupedItems(List<Transcation> transactions) {
    final items = <Object>[];

    for (final txn in transactions) {
      final txnDate = DateTime(txn.date.year, txn.date.month);
      if (!items.contains(txnDate)) {
        items.add(txnDate);
      }
      items.add(txn);
    }

    return items;
  }

In your build, call the above function on your transactions list, and then build each item according to its type as follows:

    @override
  Widget build(BuildContext context) {
    final items = _getGroupedItems(_transactions);
    return ListView.separated(
      itemBuilder: (ctx, index) {
        final item = items[index];

        return item is DateTime
            ? Text(item.toString())
            : Card(
                child: ListTile(
                  leading: CircleAvatar(
                    backgroundColor: Colors.red,
                    child: Text(
                      parsedDate((item as Transcation).date),
                      textAlign: TextAlign.center,
                    ),
                    radius: 30,
                  ),
                  title: Text('Rs ${item.amount}'),
                  subtitle: Text(item.description),
                ),
              );
      },
      separatorBuilder: (ctx, index) {
        return SizedBox(height: 10);
      },
      itemCount: items.length,
    );
  }
}

You customize how the date will appear as you wish, but I guess this should achieve what you are looking for. Good luck!