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)