skip to Main Content

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

Answers


  1. 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!

    Login or Signup to reply.
  2. 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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search