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:
Expenseclass β 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,
});
}
Breaking It Down:
class Expense {
-
classkeyword tells Dart we're creating a new class -
Expenseis the name (always start with capital letter!) -
{opens the class body
String description;
double amount;
String category;
DateTime date;
- 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,
});
- Constructor with named parameters
-
requiredkeyword ensures you can't forget to set values - This prevents null safety errors!
}
- 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
requiredkeyword 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}');
}
Understanding the Code:
var coffee = Expense(
description: 'Morning coffee',
amount: 4.50,
category: 'Food',
date: DateTime.now(),
);
-
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
requiredparameters
coffee.description
- Use dot notation (
.) to access properties - Properties are already set by the constructor
DateTime.now()
- 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
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
}
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());
}
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,
);
}
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}');
}
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)}');
}
Understanding Lists of Objects:
var expenses = [
Expense(...),
Expense(...),
Expense(...),
];
- Square brackets
[]create a list - Each item is an
Expenseobject - Comma after each object
for (var expense in expenses) {
print('${expense.description}: \$${expense.amount}');
}
- Loop through each expense in the list
-
expenseis each individual object - Access properties with dot notation
π― Try It Yourself:
Create a list of 5 expenses from your recent spending and:
- Print all expenses
- Calculate the total amount
- 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');
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();
}
Understanding Methods:
void printDetails() {
-
void= doesn't return anything -
printDetails= method name (use camelCase) -
()= no parameters needed
print('π΅ Amount: \$${amount.toStringAsFixed(2)}');
- Inside methods, we can directly access properties
- No need for
this.amount, justamount -
\$escapes the dollar sign so it prints literally -
toStringAsFixed(2)formats to 2 decimal places
coffee.printDetails();
- 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();
}
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()}');
}
Output:
New laptop: $899.99 [Electronics]
Is major expense? true
Formatted: $899.99
Coffee: $4.50 [Food]
Is major expense? false
Understanding Return Values:
bool isMajorExpense() {
return amount > 100;
}
-
bool= this method returns true or false -
returnsends the value back - Can use the returned value in conditions or print it
String getSummary() {
return '$description: \$${amount.toStringAsFixed(2)} [$category]';
}
-
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)}');
}
π― 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());
}
Exercise 2: Adding Methods (Medium)
Add these methods to your Expense class:
-
isThisMonth()- returns true if expense is from current month -
isFood()- returns true if category is "Food" -
getDaysAgo()- returns how many days ago this expense was made
Hints:
- Use
DateTime.now()to get current date - Use
date.difference(otherDate).inDaysto calculate days - Use
date.monthanddate.yearto 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;
}
Exercise 3: Real-World Scenario (Hard)
Create expenses for your last week and:
- Print all expenses from this month
- Calculate total food expenses
- Find the largest expense
- 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');
}
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,
});
Mistake 2: Wrong Method Call
// β Wrong - no parentheses
coffee.printDetails;
// β
Correct
coffee.printDetails();
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);
Mistake 4: Forgetting Return Statement
// β Wrong - no return
String getSummary() {
'$description: \$$amount';
}
// β
Correct
String getSummary() {
return '$description: \$$amount';
}
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
});
}
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,
);
Much cleaner, right? See you in Lesson 3! π
Additional Resources
- Dart Language Tour: https://dart.dev/guides/language/language-tour#classes
- Dart Null Safety: https://dart.dev/null-safety
-
Practice: Try creating other classes like
Budget,User, orTransaction - Next Step: Move on to Lesson 3 when you're comfortable with this material
Remember: Don't rush! Make sure you can complete the exercises before moving forward. Practice is key! οΏ½
Top comments (0)