Lesson 20 · 20 of 20
Project: shopping cart · Dart
A Dart project that brings the course together: a shopping cart with maps, functions, exceptions, null safety, fold and a switch expression for the discount.

This program puts the whole course together. The shop's prices live in a `const` map outside main, because they never change. The cart is a list of product,units lines, just as they might come from a form or a file, and three of them contain mistakes on purpose.
The lineCost function works out the cost of one line. split(',') separates the product from the units; prices[parts[0]] gives a double?, because the product may not exist, and int.tryParse gives an int?, because the text may not be a number. If either is missing, or the quantity is not positive, `throw` raises an ArgumentError with a clear message; otherwise it returns price times units. After those if checks, Dart knows neither is null.
In main, the loop adds each line's cost into the totals map with (totals[product] ?? 0) + ..., so the milk, which appears twice, builds up. The `try` with `on ArgumentError catch (e)` handles the bad lines: they are printed with Skipped: and the cart carries on.
For the receipt, forEach prints each product with the arrow function row, which lines up the columns with padRight and padLeft. `fold` adds up the map's values and a `switch` expression with >= 20 and >= 10 patterns picks the discount: 17.60 euros earns 5%, and the total comes to 16.72.
A good next step is to give it a screen: in DMarlon Studio you can design a Flutter app on the phone canvas with the product list and a pay button, and reuse these same Dart functions.
Code · main.dart
const prices = {'bread': 1.20, 'milk': 0.95, 'cheese': 4.50, 'apples': 2.30};// Gives the cost of one "product,units" line, or throws an error.double lineCost(String line) { var parts = line.split(','); double? price = prices[parts[0]]; int? units = int.tryParse(parts[1]); if (price == null) throw ArgumentError('${parts[0]} is not sold here'); if (units == null || units <= 0) throw ArgumentError('bad quantity in "$line"'); return price * units;}String row(String label, double euros) => label.padRight(14) + euros.toStringAsFixed(2).padLeft(7);void main() { var cart = ['bread,2', 'cheese,1', 'milk,x', 'apples,3', 'ham,1', 'milk,4', 'bread,0']; var totals = <String, double>{}; for (var line in cart) { var product = line.split(',')[0]; try { totals[product] = (totals[product] ?? 0) + lineCost(line); } on ArgumentError catch (e) { print('Skipped: ${e.message}'); } } totals.forEach((product, cost) => print(row(product, cost))); double subtotal = totals.values.fold(0, (a, b) => a + b); double rate = switch (subtotal) { >= 20 => 0.10, >= 10 => 0.05, _ => 0.0 }; print('-' * 21); print(row('Subtotal', subtotal)); print(row('Discount (${(rate * 100).round()}%)', -subtotal * rate)); print(row('TOTAL', subtotal * (1 - rate)));}Output · dart run main.dart
Skipped: bad quantity in "milk,x" Skipped: ham is not sold here Skipped: bad quantity in "bread,0" bread 2.40 cheese 4.50 apples 6.90 milk 3.80 --------------------- Subtotal 17.60 Discount (5%) -0.88 TOTAL 16.72
Key points
- Fixed data goes in a
constoutsidemain. double?andint?make you think about what may be missing.throwin the function andtryin the loop keep the error apart from its handling.foldadds up the values of a map.- A
switchexpression turns the subtotal into a discount.
Exercise
Add a map with each product's VAT rate (4 for bread and milk, 10 for the rest) and print a VAT line on the receipt with the tax included in the total.