Lesson 6: Managing Multiple Expenses
Duration: 40 minutes
App Feature: π Creating an Expense Manager
What You'll Build: A class to manage all your expenses together
Prerequisites: Complete Lessons 1-5
What We'll Learn Today
By the end of this lesson, you'll be able to:
- β Create a manager class to handle collections of objects
- β Work with Lists of expenses
- β Add, remove, and update expenses
- β Filter expenses by various criteria
- β Calculate totals and statistics
- β Generate reports from multiple expenses
- β Sort expenses in different ways
Part 1: Introduction to Collections
Before we build the ExpenseManager, let's understand Lists:
void main() {
var manager = ExpenseManager();
// Add expenses
manager.addExpense(Expense.quick('Groceries', 127.50, 'Food'));
manager.addExpense(Expense.quick('Coffee', 4.50, 'Food'));
manager.addExpense(Expense.quick('Dinner', 45.00, 'Food'));
manager.addExpense(Expense.quick('Gas', 60.00, 'Transport'));
// Set budgets
manager.setBudget('Food', 150.0);
manager.setBudget('Transport', 100.0);
// Check budget status
manager.printBudgetReport();
}
Exercise 3: Advanced Analytics (Hard)
Add these advanced methods:
-
getTopExpenses(int n)
- return top N most expensive expenses -
getCategoryTrend(String category, int days)
- return daily average for category over last N days -
predictMonthlyTotal()
- predict month total based on current spending rate
Solution:
class ExpenseManager {
List<Expense> _expenses = [];
List<Expense> getTopExpenses(int n) {
List<Expense> sorted = sortByAmountDesc();
if (n >= sorted.length) return sorted;
return sorted.sublist(0, n);
}
double getCategoryTrend(String category, int days) {
DateTime cutoff = DateTime.now().subtract(Duration(days: days));
var recentExpenses = _expenses.where((e) {
return e.category == category && e.date.isAfter(cutoff);
}).toList();
if (recentExpenses.isEmpty) return 0;
double total = recentExpenses.fold(0.0, (sum, e) => sum + e.amount);
return total / days;
}
double predictMonthlyTotal() {
if (_expenses.isEmpty) return 0;
DateTime now = DateTime.now();
var thisMonthExpenses = _expenses.where((e) {
return e.date.year == now.year && e.date.month == now.month;
}).toList();
if (thisMonthExpenses.isEmpty) return 0;
double totalSoFar = thisMonthExpenses.fold(0.0, (sum, e) => sum + e.amount);
int daysPassed = now.day;
int daysInMonth = DateTime(now.year, now.month + 1, 0).day;
double dailyAverage = totalSoFar / daysPassed;
return dailyAverage * daysInMonth;
}
Map<String, double> getCategoryTrends(int days) {
Map<String, double> trends = {};
var categories = getAllCategories();
for (var category in categories) {
trends[category] = getCategoryTrend(category, days);
}
return trends;
}
void printTrendReport(int days) {
print('\nπ SPENDING TRENDS (Last $days days)\n');
var trends = getCategoryTrends(days);
trends.forEach((category, dailyAvg) {
double monthlyProjection = dailyAvg * 30;
print('$category:');
print(' Daily average: \${dailyAvg.toStringAsFixed(2)}');
print(' Monthly projection: \${monthlyProjection.toStringAsFixed(2)}');
print('');
});
print('π‘ Predicted month total: \${predictMonthlyTotal().toStringAsFixed(2)}');
}
}
void main() {
var manager = ExpenseManager();
// Add expenses spread over 15 days
for (int i = 0; i < 15; i++) {
var date = DateTime.now().subtract(Duration(days: i));
manager.addExpense(Expense(
description: 'Daily expense $i',
amount: 10.0 + (i * 2),
category: i % 2 == 0 ? 'Food' : 'Transport',
date: date,
));
}
print('π TOP 5 EXPENSES:');
var top5 = manager.getTopExpenses(5);
for (int i = 0; i < top5.length; i++) {
print('${i + 1}. ${top5[i].description} - \${top5[i].amount}');
}
manager.printTrendReport(15);
}
Common Mistakes & How to Fix Them
Mistake 1: Modifying Original List
// β Wrong - returns reference to internal list
List<Expense> getAllExpenses() {
return _expenses; // Can be modified by caller!
}
// β
Correct - return a copy
List<Expense> getAllExpenses() {
return List.from(_expenses);
}
Mistake 2: Not Checking for Empty Lists
// β Wrong - will crash if empty
Expense getLargestExpense() {
return _expenses[0]; // Error if empty!
}
// β
Correct - check first
Expense? getLargestExpense() {
if (_expenses.isEmpty) return null;
return _expenses.reduce((a, b) => a.amount > b.amount ? a : b);
}
Mistake 3: Inefficient Filtering
// β Wrong - creates multiple loops
List<Expense> getThisMonthFood() {
var thisMonth = getThisMonth();
List<Expense> food = [];
for (var e in thisMonth) {
if (e.category == 'Food') food.add(e);
}
return food;
}
// β
Correct - one pass
List<Expense> getThisMonthFood() {
return _expenses.where((e) =>
e.isThisMonth() && e.category == 'Food'
).toList();
}
Mistake 4: Not Validating Indices
// β Wrong - no validation
void removeAt(int index) {
_expenses.removeAt(index); // Crash if invalid!
}
// β
Correct - check bounds
bool removeAt(int index) {
if (index < 0 || index >= _expenses.length) {
return false;
}
_expenses.removeAt(index);
return true;
}
Key Concepts Review
β
Manager classes handle collections of objects
β
List operations add, remove, filter, sort data
β
Filter methods return subsets based on criteria
β
Statistics methods calculate totals, averages, etc.
β
Sort methods order data in different ways
β
Report methods display information clearly
β
Always return copies of internal lists to protect data
β
Check for empty lists before operations
Self-Check Questions
1. Why should getAllExpenses() return a copy of the list instead of the original?
Answer:
Returning a copy protects the internal list from being modified by external code. If you return the original list, callers can add/remove items directly, bypassing your manager's control and validation.
2. What's the difference between filter methods and sort methods?
Answer:
- Filter methods: Return a subset of expenses that meet certain criteria (e.g., only Food expenses)
- Sort methods: Return all expenses but in a different order (e.g., sorted by amount)
3. When should you use where()
vs a for loop for filtering?
Answer:
Use where()
when possible - it's more concise and functional. Use a for loop when you need more complex logic or need to modify items during iteration.
What's Next?
In Lesson 7: Inheritance - Different Types of Expenses, we'll learn:
- Creating specialized expense types
- RecurringExpense class (monthly bills, subscriptions)
- OneTimeExpense class (special occasions)
- Using inheritance to reuse code
- The "IS-A" relationship
Example preview:
class RecurringExpense extends Expense {
String frequency; // 'weekly', 'monthly', 'yearly'
double yearlyTotal() {
if (frequency == 'monthly') return amount * 12;
if (frequency == 'weekly') return amount * 52;
return amount;
}
}
See you in Lesson 7! π
Additional Resources
- Practice adding more filter methods for different scenarios
- Try implementing a search feature that searches across all fields
- Experiment with different sorting combinations
- Think about what reports would be useful for your expense app
Remember: The ExpenseManager is the brain of your app - it organizes, analyzes, and presents your data. Make it smart and efficient! π‘
Part 1: Introduction to Collections
Before we build the ExpenseManager, let's understand Lists:
void main() {
// Creating a list of expenses
List<Expense> expenses = [];
// Adding expenses
expenses.add(Expense.quick('Coffee', 4.50, 'Food'));
expenses.add(Expense.quick('Lunch', 12.75, 'Food'));
expenses.add(Expense.quick('Gas', 45.00, 'Transport'));
// Accessing expenses
print('First expense: ${expenses[0].description}');
print('Total count: ${expenses.length}');
// Looping through expenses
print('\nAll expenses:');
for (var expense in expenses) {
print(expense.getSummary());
}
// Removing an expense
expenses.removeAt(1); // Remove lunch
print('\nAfter removing lunch: ${expenses.length} expenses');
}
Key List Operations:
-
add(item)
- Add to end -
insert(index, item)
- Add at specific position -
removeAt(index)
- Remove by position -
remove(item)
- Remove by value -
clear()
- Remove all -
length
- Get count -
[]
- Access by index
Part 2: Basic ExpenseManager Class
Let's create our first version:
class ExpenseManager {
// Private list to store all expenses
List<Expense> _expenses = [];
// Add an expense
void addExpense(Expense expense) {
_expenses.add(expense);
print('β
Added: ${expense.description}');
}
// Get all expenses (return a copy to protect internal list)
List<Expense> getAllExpenses() {
return List.from(_expenses);
}
// Get total number of expenses
int getCount() {
return _expenses.length;
}
// Calculate total spending
double getTotalSpending() {
double total = 0;
for (var expense in _expenses) {
total += expense.amount;
}
return total;
}
// Print a simple summary
void printSummary() {
print('\nπ° EXPENSE SUMMARY');
print('Total expenses: ${getCount()}');
print('Total spent: \$${getTotalSpending().toStringAsFixed(2)}');
}
}
void main() {
var manager = ExpenseManager();
manager.addExpense(Expense.quick('Coffee', 4.50, 'Food'));
manager.addExpense(Expense.quick('Uber', 12.00, 'Transport'));
manager.addExpense(Expense.quick('Lunch', 15.75, 'Food'));
manager.printSummary();
print('\nAll expenses:');
for (var expense in manager.getAllExpenses()) {
print(expense.getSummary());
}
}
Output:
β
Added: Coffee
β
Added: Uber
β
Added: Lunch
π° EXPENSE SUMMARY
Total expenses: 3
Total spent: $32.25
All expenses:
π’ Coffee: $4.50 [Food]
π’ Uber: $12.00 [Transport]
π’ Lunch: $15.75 [Food]
Part 3: Filtering Expenses
Add methods to filter expenses by different criteria:
class ExpenseManager {
List<Expense> _expenses = [];
void addExpense(Expense expense) {
_expenses.add(expense);
}
List<Expense> getAllExpenses() => List.from(_expenses);
// Filter by category
List<Expense> getByCategory(String category) {
List<Expense> filtered = [];
for (var expense in _expenses) {
if (expense.category == category) {
filtered.add(expense);
}
}
return filtered;
}
// Filter by amount range
List<Expense> getByAmountRange(double min, double max) {
List<Expense> filtered = [];
for (var expense in _expenses) {
if (expense.amount >= min && expense.amount <= max) {
filtered.add(expense);
}
}
return filtered;
}
// Get major expenses only
List<Expense> getMajorExpenses() {
List<Expense> filtered = [];
for (var expense in _expenses) {
if (expense.isMajorExpense()) {
filtered.add(expense);
}
}
return filtered;
}
// Get this month's expenses
List<Expense> getThisMonth() {
List<Expense> filtered = [];
for (var expense in _expenses) {
if (expense.isThisMonth()) {
filtered.add(expense);
}
}
return filtered;
}
// Get paid/unpaid expenses
List<Expense> getPaidExpenses() {
List<Expense> filtered = [];
for (var expense in _expenses) {
if (expense.isPaid) {
filtered.add(expense);
}
}
return filtered;
}
List<Expense> getUnpaidExpenses() {
List<Expense> filtered = [];
for (var expense in _expenses) {
if (!expense.isPaid) {
filtered.add(expense);
}
}
return filtered;
}
}
void main() {
var manager = ExpenseManager();
manager.addExpense(Expense(description: 'Coffee', amount: 4.50, category: 'Food'));
manager.addExpense(Expense(description: 'Rent', amount: 1200.0, category: 'Bills', isPaid: true));
manager.addExpense(Expense(description: 'Laptop', amount: 899.99, category: 'Electronics'));
manager.addExpense(Expense(description: 'Lunch', amount: 15.75, category: 'Food'));
print('FOOD EXPENSES:');
for (var expense in manager.getByCategory('Food')) {
print(expense.getSummary());
}
print('\nMAJOR EXPENSES (>$100):');
for (var expense in manager.getMajorExpenses()) {
print(expense.getSummary());
}
print('\nUNPAID EXPENSES:');
for (var expense in manager.getUnpaidExpenses()) {
print(expense.getSummary());
}
}
Part 4: Advanced Statistics
Add methods to calculate useful statistics:
class ExpenseManager {
List<Expense> _expenses = [];
void addExpense(Expense expense) => _expenses.add(expense);
// Total spending
double getTotalSpending() {
double total = 0;
for (var expense in _expenses) {
total += expense.amount;
}
return total;
}
// Total by category
double getTotalByCategory(String category) {
double total = 0;
for (var expense in _expenses) {
if (expense.category == category) {
total += expense.amount;
}
}
return total;
}
// Average expense amount
double getAverageExpense() {
if (_expenses.isEmpty) return 0;
return getTotalSpending() / _expenses.length;
}
// Largest expense
Expense? getLargestExpense() {
if (_expenses.isEmpty) return null;
Expense largest = _expenses[0];
for (var expense in _expenses) {
if (expense.amount > largest.amount) {
largest = expense;
}
}
return largest;
}
// Smallest expense
Expense? getSmallestExpense() {
if (_expenses.isEmpty) return null;
Expense smallest = _expenses[0];
for (var expense in _expenses) {
if (expense.amount < smallest.amount) {
smallest = expense;
}
}
return smallest;
}
// Count by category
int countByCategory(String category) {
int count = 0;
for (var expense in _expenses) {
if (expense.category == category) {
count++;
}
}
return count;
}
// Get all unique categories
List<String> getAllCategories() {
List<String> categories = [];
for (var expense in _expenses) {
if (!categories.contains(expense.category)) {
categories.add(expense.category);
}
}
return categories;
}
// Total unpaid amount
double getTotalUnpaid() {
double total = 0;
for (var expense in _expenses) {
if (!expense.isPaid) {
total += expense.amount;
}
}
return total;
}
// Get category breakdown (map of category -> total)
Map<String, double> getCategoryBreakdown() {
Map<String, double> breakdown = {};
for (var expense in _expenses) {
if (breakdown.containsKey(expense.category)) {
breakdown[expense.category] = breakdown[expense.category]! + expense.amount;
} else {
breakdown[expense.category] = expense.amount;
}
}
return breakdown;
}
}
void main() {
var manager = ExpenseManager();
manager.addExpense(Expense(description: 'Coffee', amount: 4.50, category: 'Food'));
manager.addExpense(Expense(description: 'Rent', amount: 1200.0, category: 'Bills', isPaid: true));
manager.addExpense(Expense(description: 'Laptop', amount: 899.99, category: 'Electronics'));
manager.addExpense(Expense(description: 'Lunch', amount: 15.75, category: 'Food'));
manager.addExpense(Expense(description: 'Gas', amount: 45.00, category: 'Transport'));
print('π STATISTICS:\n');
print('Total spending: \$${manager.getTotalSpending().toStringAsFixed(2)}');
print('Average expense: \$${manager.getAverageExpense().toStringAsFixed(2)}');
print('Total unpaid: \$${manager.getTotalUnpaid().toStringAsFixed(2)}');
var largest = manager.getLargestExpense();
if (largest != null) {
print('Largest: ${largest.description} - \$${largest.amount}');
}
var smallest = manager.getSmallestExpense();
if (smallest != null) {
print('Smallest: ${smallest.description} - \$${smallest.amount}');
}
print('\nπ CATEGORY BREAKDOWN:');
var breakdown = manager.getCategoryBreakdown();
breakdown.forEach((category, total) {
print('$category: \$${total.toStringAsFixed(2)}');
});
}
Part 5: Sorting Expenses
Add methods to sort expenses in different ways:
class ExpenseManager {
List<Expense> _expenses = [];
void addExpense(Expense expense) => _expenses.add(expense);
List<Expense> getAllExpenses() => List.from(_expenses);
// Sort by amount (ascending)
List<Expense> sortByAmountAsc() {
List<Expense> sorted = List.from(_expenses);
sorted.sort((a, b) => a.amount.compareTo(b.amount));
return sorted;
}
// Sort by amount (descending)
List<Expense> sortByAmountDesc() {
List<Expense> sorted = List.from(_expenses);
sorted.sort((a, b) => b.amount.compareTo(a.amount));
return sorted;
}
// Sort by date (newest first)
List<Expense> sortByDateDesc() {
List<Expense> sorted = List.from(_expenses);
sorted.sort((a, b) => b.date.compareTo(a.date));
return sorted;
}
// Sort by date (oldest first)
List<Expense> sortByDateAsc() {
List<Expense> sorted = List.from(_expenses);
sorted.sort((a, b) => a.date.compareTo(b.date));
return sorted;
}
// Sort by category
List<Expense> sortByCategory() {
List<Expense> sorted = List.from(_expenses);
sorted.sort((a, b) => a.category.compareTo(b.category));
return sorted;
}
// Sort by description
List<Expense> sortByDescription() {
List<Expense> sorted = List.from(_expenses);
sorted.sort((a, b) => a.description.compareTo(b.description));
return sorted;
}
}
void main() {
var manager = ExpenseManager();
manager.addExpense(Expense(description: 'Laptop', amount: 899.99, category: 'Electronics', date: DateTime(2025, 10, 5)));
manager.addExpense(Expense(description: 'Coffee', amount: 4.50, category: 'Food', date: DateTime(2025, 10, 9)));
manager.addExpense(Expense(description: 'Rent', amount: 1200.0, category: 'Bills', date: DateTime(2025, 10, 1)));
manager.addExpense(Expense(description: 'Lunch', amount: 15.75, category: 'Food', date: DateTime(2025, 10, 8)));
print('SORTED BY AMOUNT (highest first):');
for (var expense in manager.sortByAmountDesc()) {
print('${expense.description}: \$${expense.amount}');
}
print('\nSORTED BY DATE (newest first):');
for (var expense in manager.sortByDateDesc()) {
print('${expense.description}: ${expense.getFormattedDate()}');
}
print('\nSORTED BY CATEGORY:');
for (var expense in manager.sortByCategory()) {
print('${expense.category}: ${expense.description}');
}
}
Part 6: Removing and Updating Expenses
Add methods to modify the expense list:
class ExpenseManager {
List<Expense> _expenses = [];
void addExpense(Expense expense) {
_expenses.add(expense);
print('β
Added: ${expense.description}');
}
// Remove expense by index
bool removeExpenseAt(int index) {
if (index < 0 || index >= _expenses.length) {
print('β Invalid index');
return false;
}
var removed = _expenses.removeAt(index);
print('ποΈ Removed: ${removed.description}');
return true;
}
// Remove expense by description
bool removeExpenseByDescription(String description) {
for (int i = 0; i < _expenses.length; i++) {
if (_expenses[i].description == description) {
var removed = _expenses.removeAt(i);
print('ποΈ Removed: ${removed.description}');
return true;
}
}
print('β Expense not found: $description');
return false;
}
// Remove all expenses in a category
int removeByCategory(String category) {
int count = 0;
_expenses.removeWhere((expense) {
if (expense.category == category) {
count++;
return true;
}
return false;
});
print('ποΈ Removed $count expenses from category: $category');
return count;
}
// Clear all expenses
void clearAll() {
int count = _expenses.length;
_expenses.clear();
print('ποΈ Cleared all $count expenses');
}
// Update expense at index
bool updateExpense(int index, Expense newExpense) {
if (index < 0 || index >= _expenses.length) {
print('β Invalid index');
return false;
}
_expenses[index] = newExpense;
print('βοΈ Updated expense at index $index');
return true;
}
// Get expense by index
Expense? getExpenseAt(int index) {
if (index < 0 || index >= _expenses.length) {
return null;
}
return _expenses[index];
}
// Find index of expense by description
int findIndexByDescription(String description) {
for (int i = 0; i < _expenses.length; i++) {
if (_expenses[i].description == description) {
return i;
}
}
return -1;
}
}
void main() {
var manager = ExpenseManager();
manager.addExpense(Expense.quick('Coffee', 4.50, 'Food'));
manager.addExpense(Expense.quick('Lunch', 15.75, 'Food'));
manager.addExpense(Expense.quick('Gas', 45.00, 'Transport'));
print('\n--- Removing ---');
manager.removeExpenseByDescription('Coffee');
print('\n--- Updating ---');
int index = manager.findIndexByDescription('Lunch');
if (index != -1) {
manager.updateExpense(index, Expense.quick('Dinner', 25.00, 'Food'));
}
print('\n--- Final list ---');
for (var expense in manager.getAllExpenses()) {
print(expense.getSummary());
}
}
Part 7: Complete ExpenseManager Class
Here's the full-featured ExpenseManager:
class ExpenseManager {
List<Expense> _expenses = [];
// === ADD METHODS ===
void addExpense(Expense expense) {
_expenses.add(expense);
}
void addMultipleExpenses(List<Expense> expenses) {
_expenses.addAll(expenses);
print('β
Added ${expenses.length} expenses');
}
// === GET METHODS ===
List<Expense> getAllExpenses() => List.from(_expenses);
int getCount() => _expenses.length;
bool isEmpty() => _expenses.isEmpty;
Expense? getExpenseAt(int index) {
if (index < 0 || index >= _expenses.length) return null;
return _expenses[index];
}
// === FILTER METHODS ===
List<Expense> getByCategory(String category) {
return _expenses.where((e) => e.category == category).toList();
}
List<Expense> getByAmountRange(double min, double max) {
return _expenses.where((e) => e.amount >= min && e.amount <= max).toList();
}
List<Expense> getMajorExpenses() {
return _expenses.where((e) => e.isMajorExpense()).toList();
}
List<Expense> getThisMonth() {
return _expenses.where((e) => e.isThisMonth()).toList();
}
List<Expense> getPaidExpenses() {
return _expenses.where((e) => e.isPaid).toList();
}
List<Expense> getUnpaidExpenses() {
return _expenses.where((e) => !e.isPaid).toList();
}
// === STATISTICS METHODS ===
double getTotalSpending() {
return _expenses.fold(0.0, (sum, e) => sum + e.amount);
}
double getTotalByCategory(String category) {
return _expenses
.where((e) => e.category == category)
.fold(0.0, (sum, e) => sum + e.amount);
}
double getAverageExpense() {
if (_expenses.isEmpty) return 0;
return getTotalSpending() / _expenses.length;
}
double getTotalUnpaid() {
return _expenses
.where((e) => !e.isPaid)
.fold(0.0, (sum, e) => sum + e.amount);
}
Expense? getLargestExpense() {
if (_expenses.isEmpty) return null;
return _expenses.reduce((a, b) => a.amount > b.amount ? a : b);
}
Expense? getSmallestExpense() {
if (_expenses.isEmpty) return null;
return _expenses.reduce((a, b) => a.amount < b.amount ? a : b);
}
int countByCategory(String category) {
return _expenses.where((e) => e.category == category).length;
}
List<String> getAllCategories() {
return _expenses.map((e) => e.category).toSet().toList();
}
Map<String, double> getCategoryBreakdown() {
Map<String, double> breakdown = {};
for (var expense in _expenses) {
breakdown[expense.category] =
(breakdown[expense.category] ?? 0) + expense.amount;
}
return breakdown;
}
Map<String, int> getCategoryCounts() {
Map<String, int> counts = {};
for (var expense in _expenses) {
counts[expense.category] = (counts[expense.category] ?? 0) + 1;
}
return counts;
}
// === SORT METHODS ===
List<Expense> sortByAmountDesc() {
List<Expense> sorted = List.from(_expenses);
sorted.sort((a, b) => b.amount.compareTo(a.amount));
return sorted;
}
List<Expense> sortByAmountAsc() {
List<Expense> sorted = List.from(_expenses);
sorted.sort((a, b) => a.amount.compareTo(b.amount));
return sorted;
}
List<Expense> sortByDateDesc() {
List<Expense> sorted = List.from(_expenses);
sorted.sort((a, b) => b.date.compareTo(a.date));
return sorted;
}
List<Expense> sortByCategory() {
List<Expense> sorted = List.from(_expenses);
sorted.sort((a, b) => a.category.compareTo(b.category));
return sorted;
}
// === REMOVE METHODS ===
bool removeExpenseAt(int index) {
if (index < 0 || index >= _expenses.length) return false;
_expenses.removeAt(index);
return true;
}
bool removeExpenseByDescription(String description) {
int initialLength = _expenses.length;
_expenses.removeWhere((e) => e.description == description);
return _expenses.length < initialLength;
}
int removeByCategory(String category) {
int initialLength = _expenses.length;
_expenses.removeWhere((e) => e.category == category);
return initialLength - _expenses.length;
}
void clearAll() {
_expenses.clear();
}
// === SEARCH METHODS ===
List<Expense> searchByDescription(String query) {
String lowerQuery = query.toLowerCase();
return _expenses
.where((e) => e.description.toLowerCase().contains(lowerQuery))
.toList();
}
int findIndexByDescription(String description) {
return _expenses.indexWhere((e) => e.description == description);
}
// === REPORT METHODS ===
void printSummary() {
print('\nβββββββββββββββββββββββββββββββββββ');
print('π° EXPENSE SUMMARY');
print('βββββββββββββββββββββββββββββββββββ');
print('Total expenses: ${getCount()}');
print('Total spent: \$${getTotalSpending().toStringAsFixed(2)}');
print('Average expense: \$${getAverageExpense().toStringAsFixed(2)}');
print('Total unpaid: \$${getTotalUnpaid().toStringAsFixed(2)}');
var largest = getLargestExpense();
if (largest != null) {
print('Largest: ${largest.description} (\$${largest.amount})');
}
print('βββββββββββββββββββββββββββββββββββ\n');
}
void printCategoryReport() {
print('\nπ CATEGORY BREAKDOWN\n');
var breakdown = getCategoryBreakdown();
var counts = getCategoryCounts();
double total = getTotalSpending();
breakdown.forEach((category, amount) {
double percentage = (amount / total) * 100;
int count = counts[category] ?? 0;
print('$category:');
print(' Amount: \$${amount.toStringAsFixed(2)} (${percentage.toStringAsFixed(1)}%)');
print(' Count: $count expenses');
print('');
});
}
void printAllExpenses() {
print('\nπ ALL EXPENSES\n');
if (_expenses.isEmpty) {
print('No expenses to display');
return;
}
for (int i = 0; i < _expenses.length; i++) {
print('${i + 1}. ${_expenses[i].getFullDisplay()}');
}
print('');
}
}
void main() {
var manager = ExpenseManager();
// Add expenses
manager.addExpense(Expense(description: 'Coffee', amount: 4.50, category: 'Food'));
manager.addExpense(Expense(description: 'Rent', amount: 1200.0, category: 'Bills', isPaid: true));
manager.addExpense(Expense(description: 'Laptop', amount: 899.99, category: 'Electronics'));
manager.addExpense(Expense(description: 'Lunch', amount: 15.75, category: 'Food'));
manager.addExpense(Expense(description: 'Gas', amount: 45.00, category: 'Transport'));
manager.addExpense(Expense(description: 'Groceries', amount: 127.50, category: 'Food', isPaid: true));
// Print reports
manager.printSummary();
manager.printCategoryReport();
manager.printAllExpenses();
// Filter and display
print('π FOOD EXPENSES:');
for (var expense in manager.getByCategory('Food')) {
print(' ${expense.getSummary()}');
}
print('\nπ΄ MAJOR EXPENSES:');
for (var expense in manager.getMajorExpenses()) {
print(' ${expense.getSummary()}');
}
}
π― Practice Exercises
Exercise 1: Monthly Report (Easy)
Add a method to ExpenseManager:
-
getMonthlyReport(int year, int month)
- returns all expenses for that month -
getMonthlyTotal(int year, int month)
- returns total for that month
Solution:
List<Expense> getMonthlyReport(int year, int month) {
return _expenses.where((e) {
return e.date.year == year && e.date.month == month;
}).toList();
}
double getMonthlyTotal(int year, int month) {
return getMonthlyReport(year, month)
.fold(0.0, (sum, e) => sum + e.amount);
}
void main() {
var manager = ExpenseManager();
// Add expenses...
var octoberExpenses = manager.getMonthlyReport(2025, 10);
print('October expenses: ${octoberExpenses.length}');
print('October total: \$${manager.getMonthlyTotal(2025, 10).toStringAsFixed(2)}');
}
Exercise 2: Budget Tracking (Medium)
Add these methods to ExpenseManager:
-
setBudget(String category, double amount)
- set budget for a category -
isOverBudget(String category)
- check if category is over budget -
getBudgetStatus(String category)
- return how much under/over
Solution:
dart
class ExpenseManager {
List<Expense> _expenses = [];
Map<String, double> _budgets = {};
void setBudget(String category, double amount) {
_budgets[category] = amount;
print('π° Set budget for $category: \$${amount.toStringAsFixed(2)}');
}
bool isOverBudget(String category) {
if (!_budgets.containsKey(category)) return false;
double spent = getTotalByCategory(category);
return spent > _budgets[category]!;
}
String getBudgetStatus(String category) {
if (!_budgets.containsKey(category)) {
return 'No budget set for $category';
}
double budget = _budgets[category]!;
double spent = getTotalByCategory(category);
double remaining = budget - spent;
if (remaining >= 0) {
return '\$${remaining.toStringAsFixed(2)} remaining (${((spent/budget)*100).toStringAsFixed(1)}% used)';
} else {
return '\$${remaining.abs().toStringAsFixed(2)} over budget!';
}
}
void printBudgetReport() {
print('\nπ° BUDGET REPORT\n');
_budgets.forEach((category, budget) {
double spent = getTotalByCategory(category);
String status = isOverBudget(category) ? 'β' : 'β
';
print('$status $category:');
print(' Budget: \$${budget.toStringAsFixed(2)}');
print(' Spent: \$${spent.toStringAsFixed(2)}');
print(' ${getBudgetStatus(category)}');
print('');
});
}
}
void main() {
Top comments (0)