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');
}
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!
}
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! π±
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)}');
}
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]
  */
}
π΄ 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!
  */
}
π΄ 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! π°
}
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)}');
}
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()orgetFormattedAmount()
- β 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! π±
}
π΄ 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!
  }
}
π΄ 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!
}
π΄ 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!
  }
}
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');
}
Problems with This Approach:
- Messy and repetitive - We need 4 variables for each expense!
- Hard to manage - What if you have 50 expenses? That's 200 variables!
- 
No relationship - Nothing connects expense1Descriptionwithexpense1Amount
- 
Error-prone - Easy to mix up expense1Amountwithexpense2Amount
- 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)
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}');
}
Output:
Coffee: $4.50
Netflix: $15.99
See the difference?
- We defined the Expensestructure 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
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
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
  });
}
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!
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
Quick Check: Do You Understand?
Before moving to the next lesson, make sure you can answer these:
β Self-Check Questions:
- 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: Expenseis the class, your coffee purchase is an object
- 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
- 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 Expenseclass
- 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)