Irbisa · Cheatsheet
SOLID & Design Principles
The SOLID principles in Dart and Flutter, DRY, KISS and YAGNI, coupling and cohesion, the Law of Demeter, dependency injection vs service locator, code smells and refactoring
What do DRY, KISS and YAGNI mean, and when does removing duplication make code worse?
What are coupling and cohesion, and why do you want low coupling and high cohesion?
What is a code smell, and which ones do you meet most often in Flutter code?
What is refactoring, and how do you keep a refactor from changing behaviour?
Your checkout
Stateis 1,000 lines: it validates the form, calls the API, formats prices and builds the UI. How do you apply Single Responsibility?// Formatting: pure, so the cart and the receipt print prices the same wayclass PriceFormatter { const PriceFormatter(this.currency); final String currency; String format(int cents) => '${(cents / 100).toStringAsFixed(2)} $currency';}…What does the Open/Closed Principle look like in Flutter, for a reusable widget and for a service?
// before: a flag for every screen that needed something different// ProductCard(product, showBadge: true, isCompact: false, showFavorite: true) // after: slots, so a screen extends the card without editing itclass ProductCard extends StatelessWidget { const ProductCard(…Which SOLID principle does
LimitedCartbreak, and why doesbuyAgainreturn 5 for a five-item order when aLimitedCart(3)holds only 3?class Cart { final _items = <String>[]; int get count => _items.length; void add(String sku) => _items.add(sku);} class LimitedCart extends Cart { LimitedCart(this.max); final int max; @override void add(String sku) { if (count < max) super.add(sku); // full: quietly does nothing }} // "Buy again" re-adds a past order and reports how many items went inint buyAgain(Cart cart, List<String> skus) { skus.forEach(cart.add); return skus.length;}Your widget tests pass against a fake repository, but the real one behaves differently in production. How do you keep the fake substitutable?
// test/orders_repository_contract.dart: written oncevoid ordersRepositoryContract(Future<OrdersRepository> Function() create) { late OrdersRepository repo; setUp(() async => repo = await create()); test('a placed order is returned by the next read', () async {…Your model has
PremiumUser extends User, and screens checkuser is PremiumUser. What breaks when a user upgrades mid-session, and how would you model it?enum Feature { noAds, offlineDownloads } // The plan is data that changes; the user stays the same usersealed class Plan { const Plan(this.features); final Set<Feature> features;…How does the Interface Segregation Principle apply in Dart, where every class is already an implicit interface?
// Two roles instead of one fat OrdersServiceabstract interface class OrderHistory { Future<List<Order>> recent({int limit = 20});} abstract interface class OrderPlacement {…What is the difference between Dependency Inversion, dependency injection and a service locator?
// Injection without inversion: passed in, but shaped by Dio's APIclass OrdersViewModelA { OrdersViewModelA(this._api); final DioOrdersApi _api; Future<int> count() async => (await _api.getOrders()).length;}…Every Dart class is already an implicit interface. When is a separate
abstract interface classwith oneImplworth writing, and when is it ceremony?// One concrete class: no ClockService plus ClockServiceImplclass Clock { const Clock(); DateTime now() => DateTime.now();}…What do the Law of Demeter and tell-don't-ask say, and what is wrong with
context.read<Session>().user!.cart.items.add(item)?// before: the button knows how the session is built and the cart's rulesvoid onAddPressedBefore(BuildContext context, Item item) { final cart = context.read<Session>().user!.cart; if (cart.items.length < 10) { cart.items.add(item); cart.total += item.priceCents;…Find the bug: the settings screen throws
LateInitializationError, but only when the app is opened from a push notification.class Prefs { late SharedPreferences _prefs; Future<void> init() async => _prefs = await SharedPreferences.getInstance(); bool get darkMode => _prefs.getBool('dark_mode') ?? false;} final prefs = Prefs(); // SettingsPage reads prefs.darkMode in build class _HomePageState extends State<HomePage> { @override void initState() { super.initState(); prefs.init(); } @override Widget build(BuildContext context) => const FeedView();}Which code smells do you see in this
placeOrdercall, and which refactoring fixes each one?Future<String> placeOrder(String userId, String productId, int quantity, String street, String city, String zip, double price, String currency, bool isGift, bool express) async { return 'order-1'; // creates the order and returns its id} Future<String> onBuyPressed(User user, Product product) => placeOrder(user.id, product.id, 1, 'Abay Ave 10', 'Almaty', '050000', 19.99, 'USD', true, false);A
utils.dartfile has grown to 2,000 lines and every feature imports it. What is wrong with it, and where should its functions go?Every repository extends a generic
BaseRepository<T>with five CRUD methods. A year later half the overrides throwUnimplementedError. What went wrong?// before: every repository has to pretend it supports everythingabstract class BaseRepository<T> { Future<List<T>> getAll(); Future<T?> getById(String id); Future<void> delete(String id); // plus create() and update()}…You are extracting a
paymentspackage used by three apps. How do you design its public API so the internals can change without a major release?// lib/payments.dart: the only file apps importexport 'src/charge_result.dart' show ChargeResult, Charged, Declined;export 'src/money.dart' show Money;export 'src/payments.dart' show Payments; // lib/src/payments.dart…