DEV Community

Putra Prima A
Putra Prima A

Posted on • Edited on

Dart Object Oriented For Beginner : Expense Manager Case Study Part 1

Lesson 1: What is OOP? (Why We Need It)

Duration: 15 minutes

App Feature: 🎯 Planning our Expense Manager

What You'll Learn: Understand how OOP helps organize our expense tracking app


Introduction

Welcome to your journey of learning Dart and Object-Oriented Programming! Instead of learning abstract concepts, we're going to build a real expense manager app together. By the end of this tutorial, you'll understand not just what OOP is, but why it's essential for building apps.


The Problem: Life Without OOP

Imagine you want to track your expenses. Let's say you want to record:

  • A coffee you bought for $4.50
  • Groceries for $85.50
  • Netflix subscription for $15.99

Approach 1: Separate Variables (The Worst Way)

void main() {
  // First expense - Coffee
  String expense1Description = 'Coffee';
  double expense1Amount = 4.50;
  String expense1Category = 'Food';
  DateTime expense1Date = DateTime.now();

  // Second expense - Groceries
  String expense2Description = 'Groceries';
  double expense2Amount = 85.50;
  String expense2Category = 'Food';
  DateTime expense2Date = DateTime.now();

  // Third expense - Netflix
  String expense3Description = 'Netflix';
  double expense3Amount = 15.99;
  String expense3Category = 'Entertainment';
  DateTime expense3Date = DateTime.now();

  print('Expense 1: $expense1Description - \$expense1Amount');
  print('Expense 2: $expense2Description - \$expense2Amount');
  print('Expense 3: $expense3Description - \$expense3Amount');
}
Enter fullscreen mode Exit fullscreen mode

Problems:

  • ❌ Messy and repetitive - We need 4 variables for each expense!
  • ❌ Hard to manage - 50 expenses = 200 variables!
  • ❌ No relationship - Nothing connects expense1Description with expense1Amount
  • ❌ Error-prone - Easy to mix up expense1Amount with expense2Amount
  • ❌ Can't reuse code - Want to calculate total? You need to add each variable manually

🔴 Real Problem Example:

void main() {
  String expense1Description = 'Coffee';
  double expense1Amount = 4.50;
  String expense1Category = 'Food';

  String expense2Description = 'Lunch';
  double expense2Amount = 12.50;
  String expense2Category = 'Food';

  // You want to calculate total food expenses
  // You have to manually remember and type each variable!
  double foodTotal = expense1Amount + expense2Amount;  // Only works for 2 expenses

  // What if you have 10 food expenses? You have to type all 10!
  // What if you accidentally use the wrong variable?
  double wrongTotal = expense1Amount + expense1Amount;  // BUG! Used expense1 twice

  // Want to find the most expensive? Manual comparison for each one!
  double mostExpensive = expense1Amount;
  if (expense2Amount > mostExpensive) {
    mostExpensive = expense2Amount;
  }
  // Imagine doing this for 50 expenses... nightmare!
}
Enter fullscreen mode Exit fullscreen mode

The nightmare scenario:

// You have 20 expenses, then you want to add a new field "notes"
// Now you need to add 20 new variables!
String expense1Notes = 'Morning coffee';
String expense2Notes = 'Lunch with team';
String expense3Notes = 'Monthly subscription';
// ... 17 more variables!

// This is completely unmaintainable! 😱
Enter fullscreen mode Exit fullscreen mode

Approach 2: Using Arrays/Lists (Better, but Still Problematic)

void main() {
  // Use lists to group data
  List<String> descriptions = ['Coffee', 'Groceries', 'Netflix'];
  List<double> amounts = [4.50, 85.50, 15.99];
  List<String> categories = ['Food', 'Food', 'Entertainment'];
  List<DateTime> dates = [DateTime.now(), DateTime.now(), DateTime.now()];

  // Print expenses
  for (int i = 0; i < descriptions.length; i++) {
    print('${descriptions[i]}: \${amounts[i]} [${categories[i]}]');
  }

  // Calculate total - much easier!
  double total = 0;
  for (var amount in amounts) {
    total += amount;
  }
  print('\nTotal: \${total.toStringAsFixed(2)}');
}
Enter fullscreen mode Exit fullscreen mode

This is better because:

  • ✅ Can handle any number of expenses
  • ✅ Easier to calculate totals
  • ✅ Less variables overall

But still has problems:

  • ❌ Data is disconnected - descriptions[0] relates to amounts[0], but nothing enforces this
  • ❌ Easy to make mistakes - What if you add to descriptions but forget to add to amounts?
  • ❌ Hard to maintain - Adding a new field means adding a new list everywhere
  • ❌ Confusing indices - Was index 2 the Netflix or the groceries?
  • ❌ Can get out of sync - If you delete from one list but not others, everything breaks!

🔴 Real Problem Example - Data Gets Out of Sync:

void main() {
  List<String> descriptions = ['Coffee', 'Groceries', 'Netflix'];
  List<double> amounts = [4.50, 85.50, 15.99];
  List<String> categories = ['Food', 'Food', 'Entertainment'];

  print('Before deletion:');
  for (int i = 0; i < descriptions.length; i++) {
    print('${descriptions[i]}: \${amounts[i]} [${categories[i]}]');
  }

  // User deletes "Groceries" - you remove from descriptions
  descriptions.removeAt(1);  // Remove "Groceries"

  // Oops! Forgot to remove from amounts and categories!
  // Now everything is WRONG!

  print('\nAfter deletion:');
  for (int i = 0; i < descriptions.length; i++) {
    print('${descriptions[i]}: \${amounts[i]} [${categories[i]}]');
  }

  /* OUTPUT - EVERYTHING IS WRONG! 😱
  Before deletion:
  Coffee: $4.50 [Food]
  Groceries: $85.50 [Food]
  Netflix: $15.99 [Entertainment]

  After deletion:
  Coffee: $4.50 [Food]        ✓ Correct
  Netflix: $85.50 [Food]      ✗ WRONG! Should be $15.99 [Entertainment]
  */
}
Enter fullscreen mode Exit fullscreen mode

🔴 Real Problem Example - Adding New Data:

void main() {
  List<String> descriptions = ['Coffee', 'Lunch'];
  List<double> amounts = [4.50, 12.50];
  List<String> categories = ['Food', 'Food'];

  // You want to add a new expense
  descriptions.add('Netflix');
  amounts.add(15.99);
  // Oops! Forgot to add to categories!

  // Later, you try to access all data...
  for (int i = 0; i < descriptions.length; i++) {
    print('${descriptions[i]}: \${amounts[i]} [${categories[i]}]');
  }

  /* CRASH! 💥
  RangeError: Index out of range
  Because categories only has 2 items but we're trying to access index 2!
  */
}
Enter fullscreen mode Exit fullscreen mode

🔴 Real Problem Example - Adding a New Field:

void main() {
  // You have expenses stored in 3 lists
  List<String> descriptions = ['Coffee', 'Lunch', 'Uber', 'Movie'];
  List<double> amounts = [4.50, 12.50, 8.00, 15.00];
  List<String> categories = ['Food', 'Food', 'Transport', 'Entertainment'];

  // Now you want to add "payment method" to track how you paid
  // You need to create a NEW list and initialize ALL existing data
  List<String> paymentMethods = ['Cash', 'Card', 'Cash', 'Card'];

  // Then update ALL your code that works with expenses
  // Every loop needs to be updated!
  for (int i = 0; i < descriptions.length; i++) {
    print('${descriptions[i]}: \${amounts[i]} [${categories[i]}] - ${paymentMethods[i]}');
  }

  // Now you have 4 lists to keep in sync! 
  // What if you want to add "notes" too? That's 5 lists!
  // This gets unmanageable FAST! 😰
}
Enter fullscreen mode Exit fullscreen mode

Approach 3: Using Maps (Even Better, but Still Not Ideal)

void main() {
  // Each expense is a map
  List<Map<String, dynamic>> expenses = [
    {
      'description': 'Coffee',
      'amount': 4.50,
      'category': 'Food',
      'date': DateTime.now(),
    },
    {
      'description': 'Groceries',
      'amount': 85.50,
      'category': 'Food',
      'date': DateTime.now(),
    },
    {
      'description': 'Netflix',
      'amount': 15.99,
      'category': 'Entertainment',
      'date': DateTime.now(),
    },
  ];

  // Print expenses
  for (var expense in expenses) {
    print('${expense['description']}: \${expense['amount']} [${expense['category']}]');
  }

  // Calculate total
  double total = 0;
  for (var expense in expenses) {
    total += expense['amount'];
  }
  print('\nTotal: \${total.toStringAsFixed(2)}');
}
Enter fullscreen mode Exit fullscreen mode

This is much better because:

  • ✅ Data stays together - can't get out of sync
  • ✅ Easy to add expenses
  • ✅ Easy to add new fields

But still has problems:

  • ❌ Typos cause bugs - 'ammount' vs 'amount' won't show an error!
  • ❌ No type safety - Could accidentally put a string where a number should be
  • ❌ No methods - Can't add functions like isExpensive() or getFormattedAmount()
  • ❌ No validation - Nothing stops you from creating invalid expenses
  • ❌ Hard to read - expense['description'] is confusing

🔴 Real Problem Example - Typos Are Silent:

void main() {
  var expense = {
    'description': 'Coffee',
    'amoutn': 4.50,  // TYPO! Should be 'amount'
    'category': 'Food',
  };

  // Try to get the amount
  print('Amount: ${expense['amount']}');  // Prints: Amount: null

  // No error! The typo is silent and gives you null
  // You won't know there's a bug until runtime!

  // Try to calculate total
  double total = 0;
  List<Map<String, dynamic>> expenses = [expense];

  for (var exp in expenses) {
    total += exp['amount'] ?? 0;  // Have to use ?? because might be null
  }

  print('Total: $total');  // Total: 0 (WRONG! Should be 4.50)
  // The bug is hidden! No error message! 😱
}
Enter fullscreen mode Exit fullscreen mode

🔴 Real Problem Example - Wrong Types Accepted:

void main() {
  // These compile fine but are completely wrong!
  var badExpense1 = {
    'description': 'Coffee',
    'amount': 'four dollars fifty',  // String instead of number!
    'category': 'Food',
  };

  var badExpense2 = {
    'description': 12345,  // Number instead of string!
    'amount': 'expensive',  // String instead of number!
    'category': DateTime.now(),  // DateTime instead of string!
  };

  // No errors during compilation!
  // But when you try to use them...

  List<Map<String, dynamic>> expenses = [badExpense1, badExpense2];

  double total = 0;
  for (var expense in expenses) {
    // This will crash! Can't add a string to a number
    total += expense['amount'];  // 💥 Runtime error!
  }
}
Enter fullscreen mode Exit fullscreen mode

🔴 Real Problem Example - No Autocomplete:

void main() {
  var expense = {
    'description': 'Coffee',
    'amount': 4.50,
    'category': 'Food',
    'paymentMethod': 'Cash',
  };

  // When typing, your IDE doesn't help you!
  // You have to remember: was it 'description' or 'desc'?
  // Was it 'category' or 'cat'? 
  // Was it 'paymentMethod' or 'payment_method'?

  print(expense['descryption']);  // Typo! Returns null, no error
  print(expense['kategory']);     // Typo! Returns null, no error
  print(expense['payment']);      // Typo! Returns null, no error

  // With classes, IDE autocomplete helps you avoid typos!
}
Enter fullscreen mode Exit fullscreen mode

🔴 Real Problem Example - No Validation:

void main() {
  // Nothing stops you from creating completely invalid data!
  var invalidExpense = {
    'description': '',  // Empty string - should not be allowed!
    'amount': -100.50,  // Negative amount - impossible!
    'category': 'asdfgh',  // Invalid category
    'date': 'yesterday',  // String instead of DateTime!
  };

  // This compiles and runs!
  // Your app will have bad data and bugs everywhere

  List<Map<String, dynamic>> expenses = [invalidExpense];

  // Later, your code assumes valid data...
  for (var expense in expenses) {
    if (expense['description'].isEmpty) {  // Wait, what is this expense?
      print('Warning: Empty expense!');
    }

    if (expense['amount'] < 0) {  // Negative money?!
      print('Warning: Negative expense!');
    }

    // You need to manually check EVERYTHING!
    // With classes, validation happens automatically!
  }
}
Enter fullscreen mode Exit fullscreen mode

The Journey So Far:

Approach Main Problem Example Issue
Separate Variables Can't scale Need 200 variables for 50 expenses
Arrays Gets out of sync Delete from one list, forget another → data mismatch
Maps No type safety Typos and wrong types = silent bugs

All of these approaches share common problems:

  • 😰 Easy to make mistakes
  • 🐛 Bugs are hidden until runtime
  • 📝 No IDE help (autocomplete)
  • ❌ No validation
  • 🔧 Hard to add features
  • 📚 Can't add related functions

We need a solution that:

  • ✅ Keeps data together
  • ✅ Catches errors at compile time
  • ✅ Provides IDE autocomplete
  • ✅ Allows validation
  • ✅ Lets us add methods
  • ✅ Is easy to read and maintain

That solution is... Object-Oriented Programming with Classes! 🎉


The Problem: Life Without OOP

Imagine you want to track your expenses. Let's say you want to record:

  • A coffee you bought for $4.50
  • Groceries for $85.50
  • Netflix subscription for $15.99

Approach 1: Separate Variables (The Bad Way)

void main() {
  // First expense - Coffee
  String expense1Description = 'Coffee';
  double expense1Amount = 4.50;
  String expense1Category = 'Food';
  DateTime expense1Date = DateTime.now();

  // Second expense - Groceries
  String expense2Description = 'Groceries';
  double expense2Amount = 85.50;
  String expense2Category = 'Food';
  DateTime expense2Date = DateTime.now();

  // Third expense - Netflix
  String expense3Description = 'Netflix';
  double expense3Amount = 15.99;
  String expense3Category = 'Entertainment';
  DateTime expense3Date = DateTime.now();

  print('Expense 1: $expense1Description - \$$expense1Amount');
  print('Expense 2: $expense2Description - \$$expense2Amount');
  print('Expense 3: $expense3Description - \$$expense3Amount');
}
Enter fullscreen mode Exit fullscreen mode

Problems with This Approach:

  1. Messy and repetitive - We need 4 variables for each expense!
  2. Hard to manage - What if you have 50 expenses? That's 200 variables!
  3. No relationship - Nothing connects expense1Description with expense1Amount
  4. Error-prone - Easy to mix up expense1Amount with expense2Amount
  5. Can't reuse code - Want to calculate total? You need to add each variable manually

Try to imagine tracking 10 expenses this way... That's 40 separate variables! 😱


The Solution: Object-Oriented Programming

OOP lets us bundle related data together. Instead of 4 separate variables per expense, we create a blueprint (called a class) and make objects from it.

Think of it Like This:

🏠 Analogy 1: House Blueprint

House Blueprint (Class)

  • A blueprint shows what every house should have: rooms, doors, windows
  • It's not an actual house, just a template
  • You can't live in a blueprint!

Actual Houses (Objects)

  • You can build many houses from one blueprint
  • Each house is unique (different colors, sizes) but follows the blueprint
  • These are real houses people can live in

🍪 Analogy 2: Cookie Cutter

Cookie Cutter (Class)

  • The cookie cutter defines the shape
  • It's just a tool, not something you can eat
  • One cookie cutter can make hundreds of cookies

Actual Cookies (Objects)

  • Each cookie is made from the same cutter
  • But each can have different decorations, flavors
  • These are the real cookies you can eat!

📱 Analogy 3: Smartphone Model

iPhone Design/Model (Class)

  • Apple designs one iPhone 15 model
  • The design specifies: screen size, camera specs, processor
  • You can't make calls on a design document!

Your Actual iPhone (Object)

  • Your specific iPhone 15 with your apps, photos, contacts
  • Your friend's iPhone 15 with their different data
  • Each is a real, working phone created from the same design

🚗 Analogy 4: Car Manufacturing

Tesla Model 3 Blueprint (Class)

  • Tesla's design for Model 3
  • Specifies: 4 wheels, electric motor, touchscreen, autopilot
  • Can't drive a blueprint!

Actual Tesla Cars (Objects)

  • Each Model 3 rolling off the assembly line
  • Same design, but different colors, VIN numbers, owners
  • These are real cars you can drive!

🎂 Analogy 5: Cake Recipe

Chocolate Cake Recipe (Class)

  • Recipe lists ingredients: flour, sugar, eggs, chocolate
  • Lists steps: mix, bake, cool
  • You can't eat a recipe!

Actual Cakes (Objects)

  • Each cake you bake following the recipe
  • Same recipe, but might have different decorations
  • These are real cakes you can eat!

In Programming:

📋 Expense Class (Blueprint)

  • Defines what every expense should have: description, amount, category, date
  • Defines what every expense can do: calculate total, check if expensive
  • Just a template, not actual data
  • You can't track spending with just a class!

💰 Expense Objects (Actual Expenses)

  • Coffee expense: $4.50, Food, Today
  • Netflix expense: $15.99, Entertainment, Today
  • Rent expense: $1200, Bills, This month
  • Each is a real expense created from the template
  • These are your actual spending records!

Key Understanding:

Class (Blueprint)           →    Objects (Real Things)
─────────────────────────────────────────────────────────
Cookie Cutter              →    Cookie #1, Cookie #2, Cookie #3
iPhone Design              →    Your iPhone, Friend's iPhone
Car Blueprint              →    Car with VIN 123, Car with VIN 456
Recipe                     →    Cake you baked today, Cake from yesterday
Expense Class              →    Coffee expense, Rent expense, Gas expense

ONE CLASS                  →    MANY OBJECTS
(Define once)              →    (Create as many as you need)
Enter fullscreen mode Exit fullscreen mode

The Power:

  • ✨ Define the structure once (the class)
  • 🚀 Create unlimited real instances (objects)
  • 🎯 Each object has its own data
  • 🔧 All objects follow the same structure
  • 💪 Change the class, all objects get the update!

Understanding Classes and Objects

What is a Class?

A class is a blueprint or template that defines:

  • Properties (data): what information it stores
  • Methods (actions): what it can do

Think of it as a cookie cutter - it shapes what the cookies will look like, but it's not the cookie itself.

What is an Object?

An object is an actual instance created from a class.

Think of it as the cookies - real things you can eat, made from the cookie cutter template.


Our First Look at OOP Code

Don't worry if you don't understand everything yet - we'll learn step by step. This is just to show you the difference:

// The BLUEPRINT - This is a class
class Expense {
  String description;
  double amount;
  String category;
  DateTime date;

  // Constructor - required to initialize all properties
  Expense({
    required this.description,
    required this.amount,
    required this.category,
    required this.date,
  });
}

void main() {
  // Creating OBJECTS from the blueprint
  var coffee = Expense(
    description: 'Coffee',
    amount: 4.50,
    category: 'Food',
    date: DateTime.now(),
  );

  var netflix = Expense(
    description: 'Netflix',
    amount: 15.99,
    category: 'Entertainment',
    date: DateTime.now(),
  );

  print('${coffee.description}: \${coffee.amount}');
  print('${netflix.description}: \${netflix.amount}');
}
Enter fullscreen mode Exit fullscreen mode

Output:

Coffee: $4.50
Netflix: $15.99
Enter fullscreen mode Exit fullscreen mode

See the difference?

  • We defined the Expense structure once
  • We created multiple expenses easily
  • Each expense is a separate object with its own data
  • The code is cleaner and more organized
  • Dart's null safety ensures we never forget to set required values!

Why OOP Matters for Your Expense Manager

As we build our expense manager app, you'll see how OOP helps us:

1. Organization

Without OOP: 100 expenses = 400+ variables floating around
With OOP: 100 expense objects, each neatly packaged
Enter fullscreen mode Exit fullscreen mode

2. Reusability

// Want to add a new expense? Just create an object!
var lunch = Expense(
  description: 'Lunch',
  amount: 12.50,
  category: 'Food',
  date: DateTime.now(),
);
var dinner = Expense(
  description: 'Dinner',
  amount: 25.00,
  category: 'Food',
  date: DateTime.now(),
);
var gas = Expense(
  description: 'Gas',
  amount: 45.00,
  category: 'Transport',
  date: DateTime.now(),
);
// Same blueprint, different data
Enter fullscreen mode Exit fullscreen mode

3. Maintainability

// Need to add a "notes" field to all expenses?
// Change the class ONCE, all objects get the new feature
class Expense {
  String description;
  double amount;
  String category;
  DateTime date;
  String? notes; // Added here - affects all expenses! (? means optional)

  Expense({
    required this.description,
    required this.amount,
    required this.category,
    required this.date,
    this.notes, // Optional parameter
  });
}
Enter fullscreen mode Exit fullscreen mode

4. Real-World Modeling

Your app will have:
- Expense objects (each purchase)
- Category objects (Food, Transport, etc.)
- Budget objects (monthly limits)
- User object (your profile)

Each is a separate class, working together!
Enter fullscreen mode Exit fullscreen mode

The Four Pillars of OOP

As we build our expense manager, we'll learn these four core concepts:

1. Encapsulation 🔒

Bundle data and methods together, hide internal details.

In our app: Keep expense data safe, validate amounts (can't be negative!)

2. Inheritance 🌳

Create new classes based on existing ones.

In our app: RecurringExpense (like Netflix) inherits from Expense but adds frequency

3. Polymorphism 🎭

One action, multiple forms.

In our app: All expenses can printDetails(), but recurring ones show frequency too

4. Abstraction 🎯

Hide complex details, show only what's necessary.

In our app: You don't need to know HOW expenses are stored, just add/view/delete them

Don't worry if these seem confusing now! We'll learn each one by actually using it in our app.


What We're Building Together

Over the next lessons, we'll build a complete expense tracking system:

📱 Expense Manager App
├── 💰 Expense Class
│   ├── Track amount, date, category
│   ├── Validate data (no negative amounts!)
│   └── Calculate totals
│
├── 🔄 RecurringExpense Class
│   ├── Monthly bills (rent, subscriptions)
│   └── Calculate yearly costs
│
├── 🎯 OneTimeExpense Class
│   ├── Special purchases (gifts, emergencies)
│   └── Tag occasions
│
├── 💳 PaymentMethod Classes
│   ├── Credit card, cash, digital wallet
│   └── Process payments differently
│
└── 📊 ExpenseManager Class
    ├── Store all expenses
    ├── Filter by category
    ├── Calculate totals
    └── Generate reports
Enter fullscreen mode Exit fullscreen mode

Quick Check: Do You Understand?

Before moving to the next lesson, make sure you can answer these:

✅ Self-Check Questions:

  1. What's the difference between a class and an object? Click to see answer
  • Class = Blueprint/template (defines structure)
  • Object = Actual instance (real data)
  • Example: Expense is the class, your coffee purchase is an object
  1. Why is OOP better than using separate variables? Click to see answer
  • Organizes related data together
  • Easier to manage many items
  • Less error-prone
  • Code is reusable
  1. Name one real-world example of a class and its objects Click to see answer

Examples:

  • Class: Car → Objects: Your Toyota, Your friend's Honda
  • Class: Student → Objects: John, Sarah, Mike
  • Class: Song → Objects: "Bohemian Rhapsody", "Hotel California"

Try It Yourself! 🎯

Before moving to Lesson 2, think about what properties an Expense should have:

Your Task:

Write down (on paper or in notes) what information you'd want to track for each expense:

Example answers:

  • ✓ Description (what you bought)
  • ✓ Amount (how much it cost)
  • ✓ Category (Food, Transport, etc.)
  • ✓ Date (when you bought it)
  • ? What else would be useful?

Bonus questions to think about:

  • Should expenses have a unique ID number?
  • Should we track the payment method (cash, card)?
  • Should we allow notes or tags?

We'll use your ideas in the next lesson when we actually create the Expense class!


Key Takeaways 🎓

Before moving forward, remember:

OOP organizes code into objects - like real-world things

Classes are blueprints - they define structure

Objects are instances - actual data created from classes

OOP makes code cleaner - easier to read and maintain

Perfect for apps - Flutter itself is built on OOP principles


What's Next?

In Lesson 2, we'll:

  • Write our first Expense class
  • Create actual expense objects
  • Learn about constructors
  • Add methods to print expense details
  • See our code actually work!

Time to get hands-on and write real code! 🚀


Need Help?

Common questions beginners ask:

Q: Do I need to memorize all the OOP terms?

A: No! Focus on understanding the concepts. The terms will become natural as you use them.

Q: Is OOP hard?

A: It might feel strange at first, but by building a real app, you'll see why it's actually easier than the alternative!

Q: Can I skip OOP and just learn Flutter?

A: Flutter IS built with OOP! Every widget is a class. Understanding OOP will make Flutter much easier.

Q: What if I get stuck?

A: That's normal! Re-read sections, try the examples, and remember - we're building this step by step together.


Ready for Lesson 2? Let's write some code! →

Top comments (0)