DEV Community

Putra Prima A
Putra Prima A

Posted 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;
}
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
}
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)

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();

  // Set the properties
  coffee.description = 'Morning coffee';
  coffee.amount = 4.50;
  coffee.category = 'Food';
  coffee.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();
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
  • The () is important - it creates a new instance
coffee.description = 'Morning coffee';
Enter fullscreen mode Exit fullscreen mode
  • Use dot notation (.) to access properties
  • Set each property one by one
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 below your class and run it. You should see:

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

Part 3: The Problem with Our Current Code

Our code works, but there's a problem. Try running this:

void main() {
  var expense = Expense();
  print('Amount: ${expense.amount}');
}
Enter fullscreen mode Exit fullscreen mode

What happens? You'll get an error!

Error: Non-nullable instance field 'amount' must be initialized.
Enter fullscreen mode Exit fullscreen mode

This is because we created an expense but didn't set its properties. Dart doesn't know what values to use!

We need a better way to create objects...


Part 4: Introducing Constructors

A constructor is a special method that runs when you create an object. It initializes (sets up) the object with data.

Basic Constructor:

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

  // Constructor - same name as the class
  Expense(String desc, double amt, String cat, DateTime dt) {
    description = desc;
    amount = amt;
    category = cat;
    date = dt;
  }
}

void main() {
  // Now we MUST provide values when creating an object
  var coffee = Expense('Coffee', 4.50, 'Food', DateTime.now());
  print('${coffee.description}: \$${coffee.amount}');
}
Enter fullscreen mode Exit fullscreen mode

Understanding Constructors:

Expense(String desc, double amt, String cat, DateTime dt) {
Enter fullscreen mode Exit fullscreen mode
  • Constructor has the same name as the class
  • Takes parameters (the values we need)
  • No return type (not even void)
  description = desc;
  amount = amt;
Enter fullscreen mode Exit fullscreen mode
  • Inside the constructor, we assign parameter values to properties
var coffee = Expense('Coffee', 4.50, 'Food', DateTime.now());
Enter fullscreen mode Exit fullscreen mode
  • Now we pass values when creating the object
  • Order matters! (description, amount, category, date)

Part 5: Dart's Shortcut Constructor (The Better Way!)

Dart has a shortcut that makes constructors much cleaner:

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

  // Shortcut constructor using 'this'
  Expense(this.description, this.amount, this.category, this.date);
}
Enter fullscreen mode Exit fullscreen mode

This does the same thing as before, but in one line!

  • this.description means "set the description property to the value passed in"
  • Much cleaner and easier to read
  • This is the style most Dart developers use

🎯 Try It Yourself:

Update your Expense class to use the shortcut constructor and create 3 different expenses:

void main() {
  var coffee = Expense('Coffee', 4.50, 'Food', DateTime.now());
  var lunch = Expense('Lunch at cafe', 12.75, 'Food', DateTime.now());
  var netflix = Expense('Netflix subscription', 15.99, 'Entertainment', DateTime.now());

  print('${coffee.description}: \$${coffee.amount}');
  print('${lunch.description}: \$${lunch.amount}');
  print('${netflix.description}: \$${netflix.amount}');
}
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(this.description, this.amount, this.category, this.date);

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

void main() {
  var coffee = Expense('Morning coffee', 4.50, 'Food', 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');
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
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('Coffee', 4.50, 'Food', DateTime.now());
  var uber = Expense('Uber to work', 8.20, 'Transport', DateTime.now());
  var movie = Expense('Movie ticket', 12.00, 'Entertainment', 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(this.description, this.amount, this.category, this.date);

  void printDetails() {
    print('━━━━━━━━━━━━━━━━━━━━');
    print('πŸ“ $description');
    print('πŸ’΅ \$$amount');
    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 [$category]';
  }

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

void main() {
  var laptop = Expense('New laptop', 899.99, 'Electronics', DateTime.now());
  var coffee = Expense('Coffee', 4.50, 'Food', 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 [$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
  Expense(this.description, this.amount, this.category, 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('Monthly rent', 1200.00, 'Bills', DateTime.now()),
    Expense('Groceries', 67.50, 'Food', DateTime.now()),
    Expense('Coffee', 4.50, 'Food', DateTime.now()),
    Expense('New phone', 799.99, 'Electronics', DateTime.now()),
    Expense('Gas', 45.00, 'Transport', 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 that takes all three properties
  • Method: getLabel() that returns "icon name" (e.g., "πŸ” Food")

Solution:

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

  Category(this.name, this.icon, this.color);

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

void main() {
  var food = Category('Food', 'πŸ”', 'green');
  var transport = Category('Transport', 'πŸš—', '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('Rent', 1200.00, 'Bills', DateTime(2025, 10, 1)),
    Expense('Lunch', 15.50, 'Food', DateTime(2025, 10, 5)),
    Expense('Coffee', 4.50, 'Food', DateTime(2025, 10, 6)),
    Expense('Groceries', 89.30, 'Food', DateTime(2025, 10, 7)),
    Expense('Uber', 12.00, 'Transport', 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 this in Constructor

// ❌ Wrong
Expense(description, amount, category, date);

// βœ… Correct
Expense(this.description, this.amount, this.category, 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('Coffee', 4.50, 'Food', 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

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 this.property shortcut

βœ… Properties hold data - accessed with dot notation

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

βœ… 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.

3. What does this mean in a constructor?

Answer:
this refers to the current object being created. this.amount means "the amount property of this specific expense object".


What's Next?

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

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

Example preview:

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

// We'll be able to do this:
var expense = Expense.quick('Coffee', 4.50, 'Food');
var bill = Expense.monthly('Rent', 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)