Skip to main content

Command Palette

Search for a command to run...

Creating Scalable and Maintainable Collection Managers in Dart

Updated
3 min read

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:

  1. Add profiles.

  2. Update a user's name by their id.

  3. Filter profiles by age.

  4. 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:
  1. 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.

  2. 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.

  3. 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:
  1. Cleaner Code: The operations are clearly defined, and the usage of methods like updateProfileWithId or removeDuplicates makes the code more readable and easier to understand.

  2. Reusable Methods: The UserProfiles class encapsulates common list operations, promoting reusability. We can call addProfile, filter, or removeDuplicates as needed without rewriting the logic.

  3. 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 UserProfiles class. This approach keeps your logic encapsulated in one place.

  4. 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.