0

I'm following a tutorial for a shopping cart using getx and I've encountered a problem where when the cart is empty I receive "bad state no element" error. I know it is because .reduce method when the list is empty it produces such an error but when I try to use .fold I get an error like this: "The return type 'num' isn't a 'int', as required by the closure's context".

there is only one question I found and the problem is identical to mine but the I did not understand the chosen answer. I've been trying to search for answers on how to solve it but so far none of the answers worked for me.

here is the link for the question : How to identify the List is empty, to avoid the Bad state: No element for ListMixin.reduce

Here is my cart controller:

import 'package:get/get.dart';
import 'package:plantel/screens/widgets/product_model.dart';

class CartController extends GetxController {
  final _products = {}.obs;

  void addProduct(Product product) {
    if (_products.containsKey(product)) {
      _products[product] += 1;
    } else {
      _products[product] = 1;
    }
  }

  void removeProduct(Product product) {
    if (_products.containsKey(product) && _products[product] == 1) {
      _products.removeWhere((key, value) => key == product);
    } else {
      _products[product] -= 1;
    }
  }

  get productSubtotal => _products.entries
      .map((product) => product.key.price * product.value)
      .toList();

  get total => _products.entries
      .map((product) => product.key.price * product.value)
      .toList()
      .reduce((value, element) => value + element)
      .toStringAsFixed(2);

  get products => _products;
}

here is the product controller, I'm using Cloud Firestore database to fetch the lists.

import 'package:get/get.dart';
import 'package:plantel/screens/widgets/firestore_db.dart';
import 'package:plantel/screens/widgets/product_model.dart';

class InDoorProductController extends GetxController {
  final products = <Product>[].obs;

  @override
  void onInit() {
    products.bindStream(FirestoreDB().getInDoorProducts());

    super.onInit();
  }
}

class OutDoorProductController extends GetxController {
  final products = <Product>[].obs;

  @override
  void onInit() {
    products.bindStream(FirestoreDB().getOutdoorProducts());

    super.onInit();
  }
}

class PotsAndVasesProductController extends GetxController {
  final products = <Product>[].obs;

  @override
  void onInit() {
    products.bindStream(FirestoreDB().getpotsandvasesProducts());

    super.onInit();
  }
}

class SoilsProductController extends GetxController {
  final products = <Product>[].obs;

  @override
  void onInit() {
    products.bindStream(FirestoreDB().getsoilsProducts());

    super.onInit();
  }
}

class PesticidesProductController extends GetxController {
  final products = <Product>[].obs;

  @override
  void onInit() {
    products.bindStream(FirestoreDB().getpesticidesProducts());

    super.onInit();
  }
}

Here is the FirestoreDB class to fetch the collections.

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:plantel/screens/widgets/product_model.dart';

class FirestoreDB {
  final FirebaseFirestore _firebaseFirestoreindoor = FirebaseFirestore.instance;
  Stream<List<Product>> getInDoorProducts() {
    return _firebaseFirestoreindoor
        .collection('indoorplants')
        .snapshots()
        .map((snapshot) {
      return snapshot.docs.map((doc) => Product.fromSnapshot(doc)).toList();
    });
  }

  final FirebaseFirestore _firebaseFirestoreoutdoor =
      FirebaseFirestore.instance;
  Stream<List<Product>> getOutdoorProducts() {
    return _firebaseFirestoreoutdoor
        .collection('outdoorplants')
        .snapshots()
        .map((snapshot) {
      return snapshot.docs.map((doc) => Product.fromSnapshot(doc)).toList();
    });
  }

  final FirebaseFirestore _firebaseFirestorepotsandvases =
      FirebaseFirestore.instance;
  Stream<List<Product>> getpotsandvasesProducts() {
    return _firebaseFirestorepotsandvases
        .collection('potsandvases')
        .snapshots()
        .map((snapshot) {
      return snapshot.docs.map((doc) => Product.fromSnapshot(doc)).toList();
    });
  }

  final FirebaseFirestore _firebaseFirestoresoils = FirebaseFirestore.instance;
  Stream<List<Product>> getsoilsProducts() {
    return _firebaseFirestorepotsandvases
        .collection('soils')
        .snapshots()
        .map((snapshot) {
      return snapshot.docs.map((doc) => Product.fromSnapshot(doc)).toList();
    });
  }

  final FirebaseFirestore _firebaseFirestorepesticides =
      FirebaseFirestore.instance;
  Stream<List<Product>> getpesticidesProducts() {
    return _firebaseFirestorepotsandvases
        .collection('pesticides')
        .snapshots()
        .map((snapshot) {
      return snapshot.docs.map((doc) => Product.fromSnapshot(doc)).toList();
    });
  }
}

here is the product model

import 'package:cloud_firestore/cloud_firestore.dart';

class Product {
  final String title;
  final int price;
  final String subtitle;
  final String image;

  const Product(
      {required this.title,
      required this.price,
      required this.subtitle,
      required this.image});

  static Product fromSnapshot(DocumentSnapshot snap) {
    Product product = Product(
        title: snap['title'],
        price: snap['price'],
        subtitle: snap['subtitle'],
        image: snap['image']);
    return product;
  }
}

My apologies if I've added unnecessary code blocks but I've added them in case someone wanted to get the full picture on how I got the lists.

EDIT 1: I just solved it thanks to @pskink for his suggestion. I got it working like this.

.fold(0, (value, element) => (value + element).toInt());
ViiicY
  • 15
  • 5

1 Answers1

2

Fold requires an initial element (often called an accumulator) which will also be the "default" return value if the list is empty. So, to turn this reduce:

final sum = someList.reduce((a, b) => a + b);

into a fold, we just need to provide an initial value:

final sum = someList.fold(0, (a, b) => a + b);

And this will now work if someList is empty, returning 0.

You can always rewrite a reduce as a fold, provided you know the default value. You can also use that in reverse, to rewrite a fold as a reduce:

final sum = [0, ...someList].reduce((a, b) => a + b);

So that would be the other other way to write it. :)

(Note however, this requires the initial value to be of the same type as the other elements... fold can also handle when the accumulator is a completely different type, and that makes it very useful to know.)

Randal Schwartz
  • 39,428
  • 4
  • 43
  • 70