Irbisa · Шпаргалка
Архитектурные паттерны
MVC, MVP, MVVM, Clean Architecture, паттерн Repository
Сравните паттерны MVC, MVP, MVVM и MVI и как они ложатся на Flutter.
Объясните Clean Architecture во Flutter: слои и зоны ответственности.
// 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;…Что рекомендует официальный гайд Flutter по архитектуре и чем он отличается от Clean Architecture?
Что значит применять SOLID-принципы во Flutter?
Когда Clean Architecture оказывается неверным выбором для Flutter-приложения и что оставить вместо неё?
Как чисто маппить между доменными entity и data DTO?
// 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;…Ревьюер завернул вашу доменную сущность из-за импорта
package:flutter/material.dartради поляColor. Это придирка?// 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) {…В вашем
UserRepositoryчетырнадцать методов, один в один повторяющих REST-роуты, и все возвращаютResponse. Что вы поменяете?// 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}…Все правила о том, когда
Orderможно отменить, живут вCubit, аOrderостаётся просто набором полей. Чем это обходится?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 присылает скриншот: на экране одновременно спиннер и баннер ошибки; состояние экрана хранится в полях
isLoading,errorиitems. Что не так с моделью?sealed class OrdersState { const OrdersState();}final class OrdersLoading extends OrdersState { const OrdersLoading(); }final class OrdersEmpty extends OrdersState { const OrdersEmpty(); }final class OrdersFailed extends OrdersState {…Какие архитектурные правила нарушает этот
Cubitи как бы вы его переделали?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'))); } }}Придя с Android, вы создаёте
ViewModelкак в коде ниже: она перезагружается при каждом ребилде и сбрасывается при переключении вкладок. Где во Flutter живётViewModel?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'), ); }}Вы меняете имя на экране настроек, а в шапке до перезапуска висит старое. Какой слой чинить и как?
// BehaviorSubject comes from package:rxdartclass UserRepository { UserRepository(this._api); final UserApi _api; // replays the last value to every late subscriber final _user = BehaviorSubject<User>();…Найдите баг: без сети экран заказов пишет «У вас пока нет заказов» вместо ошибки с кнопкой «Повторить».
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 []; } }}Как структурировать большое Flutter-приложение (50+ экранов, десять разработчиков), чтобы его можно было поддерживать?
lib/ main.dart app/ # composition root, router, theme app.dart router.dart core/ # shared infrastructure, nothing feature-specific…Как разбить большое Flutter-приложение на модульные пакеты или workspaces?
# pubspec.yaml at the repository root (pub workspaces, Dart 3.6+)name: shop_workspacepublish_to: noneenvironment: sdk: ^3.6.0workspace:…Как настроить dependency injection в масштабе (composition root, scope-ы, окружения)?
// 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);…Где по слоям вы ловите, преобразуете и показываете ошибки и когда сбой оформляют как
Result, а не как исключение?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 {…Как feature flag-и и A/B-тесты вписываются в архитектуру Flutter-приложения?
При выходе из аккаунта нужно очистить корзину, закрыть сокет чата и сбросить кэш картинок. Как фичи узнают об этом, не импортируя друг друга?
abstract interface class SignOutHandler { Future<void> onSignedOut();} // Lives in the composition root: the one file that knows every feature.class SessionCoordinator {…Оформление заказа резервирует товар, списывает деньги и чистит корзину. Какой слой владеет этой последовательностью и что делать, если списание упало?
class PlaceOrder { PlaceOrder(this._stock, this._payments, this._cart); final StockRepository _stock; final PaymentRepository _payments; final CartRepository _cart;…Вам достался Flutter-проект с логикой в StatefulWidget-ах и синглтонами повсюду. Как улучшить его без переписывания?