Irbisa · Cheatsheet
Architecture Patterns
MVC, MVP, MVVM, Clean Architecture, Repository pattern
Compare MVC, MVP, MVVM, and MVI patterns and how they map to Flutter.
Explain Clean Architecture in Flutter with layers and responsibilities.
// domain: pure Dart, no Flutter or package importsclass Product { const Product({required this.id, required this.name, required this.price}); final int id; final String name; final double price;…What does Flutter's official architecture guide recommend, and how does it differ from Clean Architecture?
What does it mean to apply SOLID principles in Flutter?
When is Clean Architecture the wrong choice for a Flutter app, and what do you keep instead?
How do you map between domain entities and data DTOs cleanly?
// data layer: knows the wire formatclass UserDto { const UserDto({required this.id, required this.fullName, this.avatarUrl}); final String id; final String fullName; final String? avatarUrl;…A reviewer rejects your domain entity because it imports
package:flutter/material.dartfor aColorfield. Is that pedantic?// domain: the meaning, no Flutter importenum OrderStatus { pending, paid, shipped, cancelled } // presentation: the look, chosen where Flutter already livesextension OrderStatusStyle on OrderStatus { Color color(ColorScheme scheme) => switch (this) {…Your
UserRepositoryhas fourteen methods that mirror the REST routes one-for-one and returnsResponseobjects. What would you change?// before: an HTTP client wearing a repository's nameabstract class UserRepository { Future<Response> getUserV2(String id); Future<Response> postAvatarUpload(FormData data); // ...twelve more, one per route}…Every rule about when an
Ordercan be cancelled lives in theCubitandOrderis just a bag of fields. What does that cost you?class Order { Order({required this.id, required this.status, required this.placedAt, this.paidAt}) { // invariant: an invalid Order cannot be constructed if (status == OrderStatus.paid && paidAt == null) { throw ArgumentError('A paid order needs paidAt'); }…QA sends a screenshot with a spinner and an error banner on screen together; the screen state is
isLoading,erroranditemsfields. What is wrong with the model?sealed class OrdersState { const OrdersState();}final class OrdersLoading extends OrdersState { const OrdersLoading(); }final class OrdersEmpty extends OrdersState { const OrdersEmpty(); }final class OrdersFailed extends OrdersState {…Which architecture rules does this
Cubitbreak, and how would you restructure it?class ProfileCubit extends Cubit<ProfileState> { ProfileCubit(this.context) : super(ProfileLoading()); final BuildContext context; Future<void> load() async { final res = await Dio().get('https://api.example.com/me'); final user = User.fromJson(res.data as Map<String, dynamic>); emit(ProfileLoaded(user)); if (user.email == null) { ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text('Add your email'))); } }}Coming from Android, you create a
ViewModelas below; it reloads on every rebuild and resets on a tab switch. Where does aViewModellive in Flutter?class ProfileTab extends StatelessWidget { const ProfileTab({super.key}); @override Widget build(BuildContext context) { final vm = ProfileViewModel(context.read<UserRepository>())..load(); return ListenableBuilder( listenable: vm, builder: (context, _) => Text(vm.name ?? 'Loading'), ); }}You edit your name on the settings screen and the header still shows the old one until restart. Which layer do you fix, and how?
// BehaviorSubject comes from package:rxdartclass UserRepository { UserRepository(this._api); final UserApi _api; // replays the last value to every late subscriber final _user = BehaviorSubject<User>();…Find the bug: with no network, the orders screen says "You have no orders yet" instead of an error with a Retry button.
class OrdersRepository { OrdersRepository(this._api); final OrdersApi _api; Future<List<Order>> fetchOrders() async { try { final dtos = await _api.getOrders(); return dtos.map((dto) => dto.toEntity()).toList(); } catch (e, st) { log('fetchOrders failed', error: e, stackTrace: st); return []; } }}How do you structure a large Flutter app (50+ screens, ten developers) so it stays maintainable?
lib/ main.dart app/ # composition root, router, theme app.dart router.dart core/ # shared infrastructure, nothing feature-specific…How do you split a large Flutter app into modular packages or workspaces?
# pubspec.yaml at the repository root (pub workspaces, Dart 3.6+)name: shop_workspacepublish_to: noneenvironment: sdk: ^3.6.0workspace:…How do you set up dependency injection at scale (composition root, scopes, environments)?
// main.dart: the composition root, the only file that names concrete typesvoid main() { const env = String.fromEnvironment('ENV', defaultValue: 'prod'); // app scope: lives as long as the process final api = ApiClient(baseUrl: env == 'prod' ? prodUrl : stagingUrl); final AuthRepository auth = HttpAuthRepository(api);…Where in the layers do you catch, map and render errors, and when is a failure a
Resultinstead of an exception?sealed class AppError implements Exception { const AppError();}final class NetworkError extends AppError { const NetworkError(); }final class Unauthorized extends AppError { const Unauthorized(); }final class ServerError extends AppError {…How do feature flags and A/B tests fit into a Flutter app's architecture?
Signing out must clear the cart, close the chat socket and drop cached images. How do those features find out without importing each other?
abstract interface class SignOutHandler { Future<void> onSignedOut();} // Lives in the composition root: the one file that knows every feature.class SessionCoordinator {…Placing an order reserves stock, charges the card and clears the cart. Which layer owns that sequence, and what happens when the charge fails?
class PlaceOrder { PlaceOrder(this._stock, this._payments, this._cart); final StockRepository _stock; final PaymentRepository _payments; final CartRepository _cart;…You inherit a Flutter app with logic in StatefulWidgets and singletons everywhere. How do you improve it without a rewrite?