Search code examples
flutterdartprovider

Why create a new Item using _items.update instead of modifying the content of the Item object directly


I am new to Flutter and am following a course on Udemy. I have a model called Item which has final properties as shown:

class Item {
  final String id;
  final String title;
  final int quantity;
  final double price;

  Item({
    required this.id,
    required this.title,
    required this.quantity,
    required this.price,
  });
}

The Item is used by another class Cart which stores a list of Items in a map and it has a removeSingleItem method which reduces the quantity like this:

class Cart with ChangeNotifier {
  Map<String, Item> _items = {};

void removeSingleItem(String productId) {
    if (!_items.containsKey(productId)) {
      return;
    }
    if (_items[productId]!.quantity > 1) {
      _items.update(
          productId,
          (existingCartItem) => Item(
                id: existingCartItem.id,
                price: existingCartItem.price,
                quantity: existingCartItem.quantity - 1,
                title: existingCartItem.title,
              ));
    } else {
      _items.remove(productId);
    }
    notifyListeners();
  }

As we can see that the cart is a provider.

Why do we create a new Item using _items.update? instead of modifying the content of the Item object directly like this after making the property non-final:

_items[productId]!.quantity -= 1;

Will this cause any problems in state management? Because in the demo application the orders screen (new Scaffold) seems to get updated properly even if we don't create an entire new Item object.


Solution

  • The most dangerous thing with a mutable data class is that it's possible to mutate it from every part of the code, not only the model provider.

    Those are some of the aspects of making the data immutable:

    • An immutable data class has the constructor as const and Flutter further optimizes const objects;
    • The framework can detect state changes efficiently as the only comparison needed is the check if the object reference has changed;
    • Handling the data becomes safer because there are no side effects or mutations in unpredictable places;

    That's why a bunch of classes have the copyWith method to mutate just one property but it returns a new object instance.

    So, make Item immutable and create a copyWith method like the following snippet:

    class Item {
      final String id;
      final double price;
      final double quantity;
      final String title;
      const Item({
        required this.id,
        required this.price,
        required this.quantity,
        required this.title,
      });
    
      Item copyWith({
        String? id,
        double? price,
        double? quantity,
        String? title,
      }) {
        return Item(
          id: id ?? this.id,
          price: price ?? this.price,
          quantity: quantity ?? this.quantity,
          title: title ?? this.title,
        );
      }
    }
    

    This way creating a new instance from an existing one is much easier:

      _items.update(
          productId,
          (existingCartItem) => existingCartItem.copyWith(
                quantity: existingCartItem.quantity - 1,
              ));