Learn how to implement CRUD operations in a Flutter Expense Tracker App using SQLite for data persistence. Master inserting, retrieving, updating, and deleting expense records with async/await for smooth user experiences.
In this section, we will delve into implementing CRUD (Create, Read, Update, Delete) operations for managing expenses within our Expense Tracker App. These operations are fundamental for any application that requires data persistence, allowing users to interact with their data effectively. We’ll use SQLite, a lightweight database engine, to store and manage our expense records. This approach ensures that our app can function offline and provides a robust solution for data management.
CRUD operations are the backbone of any data-driven application. Here’s a brief overview of what each operation entails:
We’ll implement these operations using Dart’s async/await syntax to handle asynchronous database interactions, ensuring a smooth and responsive user experience.
Before we dive into the CRUD operations, let’s define our Expense
model. This model will represent the structure of our expense records in the database.
class Expense {
final int? id;
final double amount;
final int? categoryId;
final String date;
final String? description;
Expense({this.id, required this.amount, this.categoryId, required this.date, this.description});
Map<String, dynamic> toMap() {
return {
'id': id,
'amount': amount,
'category_id': categoryId,
'date': date,
'description': description,
};
}
factory Expense.fromMap(Map<String, dynamic> map) {
return Expense(
id: map['id'],
amount: map['amount'],
categoryId: map['category_id'],
date: map['date'],
description: map['description'],
);
}
}
To insert a new expense into the database, we’ll create a method that takes an Expense
object, converts it to a map, and inserts it into the expenses
table.
class ExpenseDatabaseHelper {
final DatabaseHelper _dbHelper = DatabaseHelper();
Future<int> insertExpense(Expense expense) async {
final db = await _dbHelper.database;
return await db.insert('expenses', expense.toMap());
}
}
If your app categorizes expenses, ensure that each expense entry references a valid category ID. This can be managed by maintaining a separate categories
table and using foreign keys to establish relationships.
Fetching expenses from the database involves querying the expenses
table and converting the results into a list of Expense
objects.
Future<List<Expense>> getExpenses() async {
final db = await _dbHelper.database;
final List<Map<String, dynamic>> maps = await db.query('expenses', orderBy: 'date DESC');
return List.generate(maps.length, (i) {
return Expense.fromMap(maps[i]);
});
}
You can enhance the retrieval process by adding filters, such as fetching expenses by date or category. This can be achieved by modifying the query parameters.
Updating an expense involves modifying an existing record in the database. We’ll use the update
method, specifying the record to update using its ID.
Future<int> updateExpense(Expense expense) async {
final db = await _dbHelper.database;
return await db.update(
'expenses',
expense.toMap(),
where: 'id = ?',
whereArgs: [expense.id],
);
}
When updating records, ensure that the data remains consistent. For example, if an expense’s category changes, verify that the new category ID is valid.
To delete an expense, we’ll use the delete
method, specifying the record to remove using its ID.
Future<int> deleteExpense(int id) async {
final db = await _dbHelper.database;
return await db.delete(
'expenses',
where: 'id = ?',
whereArgs: [id],
);
}
To prevent accidental data loss, consider implementing a confirmation dialog before performing the deletion.
Handling database operations asynchronously is crucial for maintaining a responsive UI. Dart’s async
and await
keywords simplify this process, allowing you to write asynchronous code that reads like synchronous code.
Future<void> performDatabaseOperation() async {
try {
// Perform some database operation
await someAsyncFunction();
} catch (e) {
// Handle exceptions
}
}
Robust error handling is essential for a smooth user experience. Catch potential exceptions during database operations and provide meaningful feedback to the user.
try {
await insertExpense(expense);
// Notify user of success
} catch (e) {
// Handle error and notify user
}
Here’s a diagram illustrating the flow of CRUD operations in our Expense Tracker App:
graph LR A[User Action] --> B[Insert Expense] A --> C[View Expenses] A --> D[Update Expense] A --> E[Delete Expense] B --> F[Add Record to Database] C --> G[Fetch Records from Database] D --> H[Modify Existing Record] E --> I[Remove Record from Database] G --> J[Display Expenses in UI]
Implementing CRUD operations in your Flutter app is a critical step towards building a fully functional and user-friendly application. By leveraging SQLite for data persistence and Dart’s async/await for handling asynchronous operations, you can ensure that your app remains responsive and efficient. Remember to handle errors gracefully and provide users with clear feedback to enhance their experience.
By mastering these concepts, you’ll be well-equipped to implement data operations in your own projects, paving the way for more complex and feature-rich applications.