DEV Community

Putra Prima A
Putra Prima A

Posted on • Edited on

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

Lesson 2: Creating Your First Class

Duration: 30 minutes

App Feature: πŸ’° Building the Expense Class

What You'll Build: Create an Expense class to represent a single expense

Prerequisites: Complete Lesson 1


What We'll Learn Today

By the end of this lesson, you'll be able to:

  • βœ… Write your first Dart class
  • βœ… Understand properties (instance variables)
  • βœ… Create objects from your class
  • βœ… Use constructors to initialize objects
  • βœ… Add methods to make objects do things
  • βœ… Work with multiple expense objects

Review: What is a Class?

Remember from Lesson 1:

  • A class is a blueprint/template
  • An object is an actual instance created from that class
  • Example: Expense class β†’ your coffee purchase object

Today we're going to actually write this code ourselves!


Part 1: Your First Class (The Basic Structure)

Let's start with the simplest possible Expense class:

class Expense {
  // Properties (also called instance variables or fields)
  String description;
  double amount;
  String category;
  DateTime date;

  // Constructor is required for null safety
  Expense({
    required this.description,
    required this.amount,
    required this.category,
    required this.date,
  });
}
Enter fullscreen mode Exit fullscreen mode

Breaking It Down:

class Expense {
Enter fullscreen mode Exit fullscreen mode
  • class keyword tells Dart we're creating a new class
  • Expense is the name (always start with capital letter!)
  • { opens the class body
  String description;
  double amount;
  String category;
  DateTime date;
Enter fullscreen mode Exit fullscreen mode
  • These are properties - data that each expense will store
  • String = text (description, category)
  • double = decimal number (amount like 4.50)
  • DateTime = date and time
  • All properties are non-nullable (must have a value)
  Expense({
    required this.description,
    required this.amount,
    required this.category,
    required this.date,
  });
Enter fullscreen mode Exit fullscreen mode
  • Constructor with named parameters
  • required keyword ensures you can't forget to set values
  • This prevents null safety errors!
}
Enter fullscreen mode Exit fullscreen mode
  • Closes the class body

🎯 Try It Yourself:

Create a file called expense.dart and type this class. Pay attention to:

  • Capital E in Expense
  • Semicolons ; after each property
  • Proper indentation (2 or 4 spaces)
  • The required keyword for null safety

Part 2: Creating Objects (Instances)

Now that we have our class, let's create actual expense objects!

void main() {
  // Create an object using the class
  var coffee = Expense(
    description: 'Morning coffee',
    amount: 4.50,
    category: 'Food',
    date: DateTime.now(),
  );

  // Print the details
  print('I spent \$${coffee.amount} on ${coffee.description}');
}
Enter fullscreen mode Exit fullscreen mode

Understanding the Code:

var coffee = Expense(
  description: 'Morning coffee',
  amount: 4.50,
  category: 'Food',
  date: DateTime.now(),
);
Enter fullscreen mode Exit fullscreen mode
  • var = variable (Dart figures out the type)
  • coffee = variable name (our choice)
  • Expense(...) = calling the class to create a new object
  • Named parameters make it clear what each value represents
  • Must provide all required parameters
coffee.description
Enter fullscreen mode Exit fullscreen mode
  • Use dot notation (.) to access properties
  • Properties are already set by the constructor
DateTime.now()
Enter fullscreen mode Exit fullscreen mode
  • Gets the current date and time
  • Perfect for tracking when you made the expense

🎯 Try It Yourself:

Add this code and run it. You should see:

I spent $4.50 on Morning coffee
Enter fullscreen mode Exit fullscreen mode

Part 3: Why Null Safety Matters

With null safety, Dart prevents common bugs:

void main() {
  // ❌ This won't compile - missing required parameters!
  // var expense = Expense();

  // βœ… This is correct - all required values provided
  var expense = Expense(
    description: 'Coffee',
    amount: 4.50,
    category: 'Food',
    date: DateTime.now(),
  );

  // No need to check for null!
  print('Amount: ${expense.amount}'); // Always has a value
}
Enter fullscreen mode Exit fullscreen mode

Benefits of Null Safety:

  • βœ… Can't forget to set required values
  • βœ… No null pointer errors
  • βœ… IDE helps you with required parameters
  • βœ… Code is safer and more reliable

Part 4: Positional vs Named Parameters

You can choose between two constructor styles:

Positional Parameters (Order matters):

class Expense {
  String description;
  double amount;
  String category;
  DateTime date;

  // Positional constructor
  Expense(this.description, this.amount, this.category, this.date);
}

void main() {
  // Must be in exact order
  var coffee = Expense('Coffee', 4.50, 'Food', DateTime.now());
}
Enter fullscreen mode Exit fullscreen mode

Named Parameters (Order doesn't matter):

class Expense {
  String description;
  double amount;
  String category;
  DateTime date;

  // Named constructor
  Expense({
    required this.description,
    required this.amount,
    required this.category,
    required this.date,
  });
}

void main() {
  // Can be in any order, very clear
  var coffee = Expense(
    category: 'Food',
    description: 'Coffee',
    date: DateTime.now(),
    amount: 4.50,
  );
}
Enter fullscreen mode Exit fullscreen mode

Recommendation: Use named parameters for classes with multiple properties. They're more readable and prevent mistakes!

🎯 Try It Yourself:

Update your Expense class to use named parameters and create 3 different expenses:

void main() {
  var coffee = Expense(
    description: 'Coffee',
    amount: 4.50,
    category: 'Food',
    date: DateTime.now(),
  );

  var lunch = Expense(
    description: 'Lunch at cafe',
    amount: 12.75,
    category: 'Food',
    date: DateTime.now(),
  );

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

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

Part 5: Working with Multiple Objects

Now that we can create individual expenses, let's work with multiple objects using lists:

void main() {
  // Create a list to store multiple expenses
  var expenses = [
    Expense(
      description: 'Coffee',
      amount: 4.50,
      category: 'Food',
      date: DateTime.now(),
    ),
    Expense(
      description: 'Uber to work',
      amount: 8.20,
      category: 'Transport',
      date: DateTime.now(),
    ),
    Expense(
      description: 'Movie ticket',
      amount: 12.00,
      category: 'Entertainment',
      date: DateTime.now(),
    ),
  ];

  // Loop through and print each expense
  print('My Expenses:');
  for (var expense in expenses) {
    print('${expense.description}: \$${expense.amount}');
  }

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

Understanding Lists of Objects:

var expenses = [
  Expense(...),
  Expense(...),
  Expense(...),
];
Enter fullscreen mode Exit fullscreen mode
  • Square brackets [] create a list
  • Each item is an Expense object
  • Comma after each object
for (var expense in expenses) {
  print('${expense.description}: \$${expense.amount}');
}
Enter fullscreen mode Exit fullscreen mode
  • Loop through each expense in the list
  • expense is each individual object
  • Access properties with dot notation

🎯 Try It Yourself:

Create a list of 5 expenses from your recent spending and:

  1. Print all expenses
  2. Calculate the total amount
  3. Count how many food expenses you have

Hint for counting:

int foodCount = 0;
for (var expense in expenses) {
  if (expense.category == 'Food') {
    foodCount++;
  }
}
print('Food expenses: $foodCount');
Enter fullscreen mode Exit fullscreen mode

Part 6: Adding Methods (Making Objects DO Things)

So far our objects just hold data. Let's make them do something useful!

A method is a function inside a class that can work with the object's data.

class Expense {
  String description;
  double amount;
  String category;
  DateTime date;

  Expense({
    required this.description,
    required this.amount,
    required this.category,
    required this.date,
  });

  // Method to print expense details
  void printDetails() {
    print('━━━━━━━━━━━━━━━━━━━━');
    print('πŸ“ Description: $description');
    print('πŸ’΅ Amount: \$${amount.toStringAsFixed(2)}');
    print('πŸ“ Category: $category');
    print('πŸ“… Date: ${date.toString().split(' ')[0]}');
    print('━━━━━━━━━━━━━━━━━━━━');
  }
}

void main() {
  var coffee = Expense(
    description: 'Morning coffee',
    amount: 4.50,
    category: 'Food',
    date: DateTime.now(),
  );

  // Call the method
  coffee.printDetails();
}
Enter fullscreen mode Exit fullscreen mode

Understanding Methods:

void printDetails() {
Enter fullscreen mode Exit fullscreen mode
  • void = doesn't return anything
  • printDetails = method name (use camelCase)
  • () = no parameters needed
print('πŸ’΅ Amount: \$${amount.toStringAsFixed(2)}');
Enter fullscreen mode Exit fullscreen mode
  • Inside methods, we can directly access properties
  • No need for this.amount, just amount
  • \$ escapes the dollar sign so it prints literally
  • toStringAsFixed(2) formats to 2 decimal places
coffee.printDetails();
Enter fullscreen mode Exit fullscreen mode
  • Call methods using dot notation
  • Don't forget the ()!

🎯 Try It Yourself:

Add the printDetails() method and create 3 expenses. Call printDetails() on each:

void main() {
  var coffee = Expense(
    description: 'Coffee',
    amount: 4.50,
    category: 'Food',
    date: DateTime.now(),
  );

  var uber = Expense(
    description: 'Uber to work',
    amount: 8.20,
    category: 'Transport',
    date: DateTime.now(),
  );

  var movie = Expense(
    description: 'Movie ticket',
    amount: 12.00,
    category: 'Entertainment',
    date: DateTime.now(),
  );

  coffee.printDetails();
  uber.printDetails();
  movie.printDetails();
}
Enter fullscreen mode Exit fullscreen mode

Part 7: Methods That Return Values

Methods can also calculate and return values:

class Expense {
  String description;
  double amount;
  String category;
  DateTime date;

  Expense({
    required this.description,
    required this.amount,
    required this.category,
    required this.date,
  });

  void printDetails() {
    print('━━━━━━━━━━━━━━━━━━━━');
    print('πŸ“ $description');
    print('πŸ’΅ \$${amount.toStringAsFixed(2)}');
    print('πŸ“ $category');
    print('πŸ“… ${date.toString().split(' ')[0]}');
    print('━━━━━━━━━━━━━━━━━━━━');
  }

  // Method that returns a boolean
  bool isMajorExpense() {
    return amount > 100;
  }

  // Method that returns a String
  String getSummary() {
    return '$description: \$${amount.toStringAsFixed(2)} [$category]';
  }

  // Method that returns a formatted amount
  String getFormattedAmount() {
    return '\$${amount.toStringAsFixed(2)}';
  }
}

void main() {
  var laptop = Expense(
    description: 'New laptop',
    amount: 899.99,
    category: 'Electronics',
    date: DateTime.now(),
  );

  var coffee = Expense(
    description: 'Coffee',
    amount: 4.50,
    category: 'Food',
    date: DateTime.now(),
  );

  // Using methods that return values
  print(laptop.getSummary());
  print('Is major expense? ${laptop.isMajorExpense()}');
  print('Formatted: ${laptop.getFormattedAmount()}');

  print('');

  print(coffee.getSummary());
  print('Is major expense? ${coffee.isMajorExpense()}');
}
Enter fullscreen mode Exit fullscreen mode

Output:

New laptop: $899.99 [Electronics]
Is major expense? true
Formatted: $899.99

Coffee: $4.50 [Food]
Is major expense? false
Enter fullscreen mode Exit fullscreen mode

Understanding Return Values:

bool isMajorExpense() {
  return amount > 100;
}
Enter fullscreen mode Exit fullscreen mode
  • bool = this method returns true or false
  • return sends the value back
  • Can use the returned value in conditions or print it
String getSummary() {
  return '$description: \$${amount.toStringAsFixed(2)} [$category]';
}
Enter fullscreen mode Exit fullscreen mode
  • String = returns text
  • Builds a nice formatted string
  • Return value can be stored in a variable or printed directly

Part 8: Complete Example

Here's our complete Expense class with everything we've learned:

class Expense {
  // Properties
  String description;
  double amount;
  String category;
  DateTime date;

  // Constructor with named parameters
  Expense({
    required this.description,
    required this.amount,
    required this.category,
    required this.date,
  });

  // Method: Print detailed information
  void printDetails() {
    print('━━━━━━━━━━━━━━━━━━━━');
    print('πŸ“ Description: $description');
    print('πŸ’΅ Amount: ${getFormattedAmount()}');
    print('πŸ“ Category: $category');
    print('πŸ“… Date: ${getFormattedDate()}');
    if (isMajorExpense()) {
      print('⚠️  This is a major expense!');
    }
    print('━━━━━━━━━━━━━━━━━━━━');
  }

  // Method: Check if major expense
  bool isMajorExpense() {
    return amount > 100;
  }

  // Method: Get one-line summary
  String getSummary() {
    String emoji = isMajorExpense() ? 'πŸ”΄' : '🟒';
    return '$emoji $description: ${getFormattedAmount()} [$category]';
  }

  // Method: Get formatted amount with 2 decimals
  String getFormattedAmount() {
    return '\$${amount.toStringAsFixed(2)}';
  }

  // Method: Get formatted date (without time)
  String getFormattedDate() {
    return date.toString().split(' ')[0];
  }
}

void main() {
  print('🏦 MY EXPENSES\n');

  // Create multiple expenses
  var expenses = [
    Expense(
      description: 'Monthly rent',
      amount: 1200.00,
      category: 'Bills',
      date: DateTime.now(),
    ),
    Expense(
      description: 'Groceries',
      amount: 67.50,
      category: 'Food',
      date: DateTime.now(),
    ),
    Expense(
      description: 'Coffee',
      amount: 4.50,
      category: 'Food',
      date: DateTime.now(),
    ),
    Expense(
      description: 'New phone',
      amount: 799.99,
      category: 'Electronics',
      date: DateTime.now(),
    ),
    Expense(
      description: 'Gas',
      amount: 45.00,
      category: 'Transport',
      date: DateTime.now(),
    ),
  ];

  // Print summary for each
  print('SUMMARY:');
  for (var expense in expenses) {
    print(expense.getSummary());
  }

  print('\n');

  // Print details for major expenses
  print('MAJOR EXPENSES (>\$100):');
  for (var expense in expenses) {
    if (expense.isMajorExpense()) {
      expense.printDetails();
      print('');
    }
  }

  // Calculate total
  double total = 0;
  for (var expense in expenses) {
    total += expense.amount;
  }
  print('πŸ’° TOTAL SPENT: \$${total.toStringAsFixed(2)}');
}
Enter fullscreen mode Exit fullscreen mode

🎯 Practice Exercises

Now it's your turn to practice! Complete these exercises to solidify your understanding:

Exercise 1: Basic Class Creation (Easy)

Create a Category class with:

  • Properties: name (String), icon (String), color (String)
  • Constructor with named parameters (use required)
  • Method: getLabel() that returns "icon name" (e.g., "πŸ” Food")

Solution:

class Category {
  String name;
  String icon;
  String color;

  Category({
    required this.name,
    required this.icon,
    required this.color,
  });

  String getLabel() {
    return '$icon $name';
  }
}

void main() {
  var food = Category(name: 'Food', icon: 'πŸ”', color: 'green');
  var transport = Category(name: 'Transport', icon: 'πŸš—', color: 'blue');

  print(food.getLabel());
  print(transport.getLabel());
}
Enter fullscreen mode Exit fullscreen mode

Exercise 2: Adding Methods (Medium)

Add these methods to your Expense class:

  1. isThisMonth() - returns true if expense is from current month
  2. isFood() - returns true if category is "Food"
  3. getDaysAgo() - returns how many days ago this expense was made

Hints:

  • Use DateTime.now() to get current date
  • Use date.difference(otherDate).inDays to calculate days
  • Use date.month and date.year to check if same month

Solution:

bool isThisMonth() {
  DateTime now = DateTime.now();
  return date.year == now.year && date.month == now.month;
}

bool isFood() {
  return category == 'Food';
}

int getDaysAgo() {
  DateTime now = DateTime.now();
  return now.difference(date).inDays;
}
Enter fullscreen mode Exit fullscreen mode

Exercise 3: Real-World Scenario (Hard)

Create expenses for your last week and:

  1. Print all expenses from this month
  2. Calculate total food expenses
  3. Find the largest expense
  4. Count how many major expenses (>$100)

Challenge yourself before looking at the solution!

Solution:

void main() {
  var expenses = [
    Expense(
      description: 'Rent',
      amount: 1200.00,
      category: 'Bills',
      date: DateTime(2025, 10, 1),
    ),
    Expense(
      description: 'Lunch',
      amount: 15.50,
      category: 'Food',
      date: DateTime(2025, 10, 5),
    ),
    Expense(
      description: 'Coffee',
      amount: 4.50,
      category: 'Food',
      date: DateTime(2025, 10, 6),
    ),
    Expense(
      description: 'Groceries',
      amount: 89.30,
      category: 'Food',
      date: DateTime(2025, 10, 7),
    ),
    Expense(
      description: 'Uber',
      amount: 12.00,
      category: 'Transport',
      date: DateTime(2025, 10, 8),
    ),
  ];

  // 1. Print this month's expenses
  print('THIS MONTH\'S EXPENSES:');
  for (var expense in expenses) {
    if (expense.isThisMonth()) {
      print(expense.getSummary());
    }
  }

  // 2. Total food expenses
  double foodTotal = 0;
  for (var expense in expenses) {
    if (expense.category == 'Food') {
      foodTotal += expense.amount;
    }
  }
  print('\nTotal food: \$${foodTotal.toStringAsFixed(2)}');

  // 3. Find largest expense
  Expense? largest;
  for (var expense in expenses) {
    if (largest == null || expense.amount > largest.amount) {
      largest = expense;
    }
  }
  print('Largest: ${largest?.getSummary()}');

  // 4. Count major expenses
  int majorCount = 0;
  for (var expense in expenses) {
    if (expense.isMajorExpense()) {
      majorCount++;
    }
  }
  print('Major expenses: $majorCount');
}
Enter fullscreen mode Exit fullscreen mode

Common Mistakes & How to Fix Them

Mistake 1: Forgetting required for Named Parameters

// ❌ Wrong - parameters are optional without 'required'
Expense({
  this.description,
  this.amount,
  this.category,
  this.date,
});

// βœ… Correct - use 'required' for non-nullable parameters
Expense({
  required this.description,
  required this.amount,
  required this.category,
  required this.date,
});
Enter fullscreen mode Exit fullscreen mode

Mistake 2: Wrong Method Call

// ❌ Wrong - no parentheses
coffee.printDetails;

// βœ… Correct
coffee.printDetails();
Enter fullscreen mode Exit fullscreen mode

Mistake 3: Using Class Name Instead of Object

// ❌ Wrong - Expense is the class, not an object
print(Expense.amount);

// βœ… Correct - coffee is the object
var coffee = Expense(
  description: 'Coffee',
  amount: 4.50,
  category: 'Food',
  date: DateTime.now(),
);
print(coffee.amount);
Enter fullscreen mode Exit fullscreen mode

Mistake 4: Forgetting Return Statement

// ❌ Wrong - no return
String getSummary() {
  '$description: \$$amount';
}

// βœ… Correct
String getSummary() {
  return '$description: \$$amount';
}
Enter fullscreen mode Exit fullscreen mode

Mistake 5: Not Handling Nullable Types

// If you have optional properties, use nullable types
class Expense {
  String description;
  double amount;
  String category;
  DateTime date;
  String? notes;  // βœ… Correct - nullable with ?

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

Key Concepts Review

Before moving to Lesson 3, make sure you understand:

βœ… Classes are blueprints - define structure with properties and methods

βœ… Objects are instances - created using the class

βœ… Constructors initialize objects - use named parameters with required

βœ… Properties hold data - accessed with dot notation

βœ… Methods define behavior - can be void or return values

βœ… Null safety - prevents bugs by ensuring values are always set

βœ… Use proper naming - classes PascalCase, methods camelCase


Self-Check Questions

Test your understanding:

1. What's the difference between a property and a method?

Answer:

  • Property = data (like description, amount)
  • Method = behavior/action (like printDetails(), getSummary())

2. Why do we use constructors?

Answer:
To initialize objects with data when we create them, ensuring all required values are set. This prevents null safety errors.

3. What does required mean in a constructor?

Answer:
required means you must provide a value for that parameter when creating an object. It ensures you don't forget important values and prevents null safety errors.

4. What's the difference between nullable and non-nullable types?

Answer:

  • Non-nullable (String): Must always have a value, cannot be null
  • Nullable (String?): Can be null or have a value, use when something is optional

What's Next?

In Lesson 3: Constructors Deep Dive, we'll learn:

  • Named constructors for easier object creation
  • Optional parameters with default values
  • Factory constructors
  • Making our Expense class even more flexible!

Example preview:

// Instead of this:
var expense = Expense(
  description: 'Coffee',
  amount: 4.50,
  category: 'Food',
  date: DateTime.now(),
);

// We'll be able to do this:
var expense = Expense.quick(
  description: 'Coffee',
  amount: 4.50,
  category: 'Food',
);

var bill = Expense.monthly(
  description: 'Rent',
  amount: 1200.00,
);
Enter fullscreen mode Exit fullscreen mode

Much cleaner, right? See you in Lesson 3! πŸš€


Additional Resources

Remember: Don't rush! Make sure you can complete the exercises before moving forward. Practice is key! οΏ½

Top comments (0)