Creating Scalable and Maintainable Collection Managers in Dart
Scenario
Suppose we are building a social media application where we need to manage user profiles. The profiles contain attributes like id, name, and age. Below, I demonstrate how using the UserProfiles class makes the code cleaner, reusable, and easier to maintain compared to handling it manually without this utility class.
Without UserProfiles Class
Let's consider a scenario where we need to:
Add profiles.
Update a user's name by their
id.Filter profiles by age.
Remove duplicate profiles based on
id.
Code without UserProfiles:
dartCopy codeimport 'user_profile.dart';
List<UserProfile> profiles = [];
// Add a new profile
profiles.add(UserProfile(id: '1', name: 'Alice', age: 25));
profiles.add(UserProfile(id: '2', name: 'Bob', age: 30));
// Update user profile by id
String idToUpdate = '1';
for (int i = 0; i < profiles.length; i++) {
if (profiles[i].id == idToUpdate) {
profiles[i] = profiles[i].copyWith(name: 'Alice Updated');
}
}
// Filter profiles by age > 26
List<UserProfile> filteredProfiles = profiles.where((profile) => profile.age > 26).toList();
// Remove duplicate profiles by id
final seen = <String>{};
profiles = profiles.where((profile) => seen.add(profile.id)).toList();
Problems with This Approach:
Boilerplate Code: We need to write repetitive code to iterate over the list whenever we want to perform operations like update, filter, or remove duplicates.
Lack of Reusability: Each time we need to add similar functionality (e.g., finding a profile or updating it), we have to manually write the code, which is error-prone.
Complexity: As the application grows, managing user profiles will require more such operations, increasing the complexity of maintaining the code.
With UserProfiles Class
Now, let's use the UserProfiles class to perform the same operations.
Code with UserProfiles:
dartCopy codeimport 'user_profile.dart';
void main() {
// Initialize an empty collection of user profiles
UserProfiles<UserProfile> userProfiles = UserProfiles<UserProfile>.empty();
// Add profiles
userProfiles.addProfile(UserProfile(id: '1', name: 'Alice', age: 25));
userProfiles.addProfile(UserProfile(id: '2', name: 'Bob', age: 30));
// Update a user profile by id
userProfiles.updateProfileWithId('1', (profile) => profile.copyWith(name: 'Alice Updated'));
// Filter profiles by age > 26
UserProfiles<UserProfile> filteredProfiles = userProfiles.filter((profile) => profile.age > 26);
// Remove duplicate profiles by id
userProfiles.removeDuplicates((profile) => profile.id);
}
Advantages of This Approach:
Cleaner Code: The operations are clearly defined, and the usage of methods like
updateProfileWithIdorremoveDuplicatesmakes the code more readable and easier to understand.Reusable Methods: The
UserProfilesclass encapsulates common list operations, promoting reusability. We can calladdProfile,filter, orremoveDuplicatesas needed without rewriting the logic.Extensibility: If you need new methods, like sorting profiles or finding the first element of a specific type, you can add them directly in the
UserProfilesclass. This approach keeps your logic encapsulated in one place.Immutability: The
addedProfile,addedAllProfiles, and similar methods return new instances, which is useful for keeping data immutable—important in state management (e.g., when using Bloc or Redux in Flutter).
Summary
The UserProfiles class provides a clean, reusable, and easy-to-maintain approach for managing collections of user profiles. Instead of writing repetitive and error-prone boilerplate code, you can rely on a well-defined API that allows for common operations like adding, updating, filtering, and removing duplicates. This is especially beneficial for larger projects or when dealing with complex data models.