1. Архитектурный подход
Feature-first: внутри каждой фичи обычно есть screen, cubit, model, service.
➤ UI отделен от логики.
➤ Логика отделена от сети.
➤ Для состояния используется Cubit, для зависимостей — GetIt.
2. Основная схема
Screen → Cubit → Service → ApiClient → Backend
Backend → ApiClient → Service → Cubit → Screen
➤ Screen показывает UI
➤ Cubit управляет состоянием
➤ Service знает, какой запрос вызвать
➤ ApiClient делает HTTP-запрос
3. Слои приложения
Presentation layer: screens, widgets, частично components
State layer: Cubit
Data layer: services, models, ApiClient
Core/shared layer: network, theme, utils, components, routes, injection_container
4. Почему Cubit, а не full BLoC
В проекте много экранов с линейной логикой:
loadProfile(), saveProfile(), loadWalletData()
Для такого сценария Cubit проще и чище, чем полный Event → State BLoC.
5. Зачем нужен ApiClient
ApiClient — это единая точка для всех запросов в API.
Он нужен, чтобы не писать в каждом сервисе заново:
➤ baseUrl
➤ headers
➤ bearer token
➤ refresh token
➤ обработку ошибок
➤ логи запросов
6. Зачем нужны GetIt и setupDependencies()
GetIt — это контейнер зависимостей.
Он нужен, чтобы не создавать вручную по всему приложению:
➤ ApiClient
➤ ProfileApiService
➤ WalletApiService
➤ WebSocketClient
➤ и другие зависимости
setupDependencies() — это место, где все общие зависимости регистрируются один раз при старте приложения.
7. Почему это лучше, чем создавать всё вручную
Без GetIt пришлось бы:
➤ в каждом cubit руками создавать service
➤ в каждом service руками создавать api client
➤ получать дублирование и сильную связанность
С GetIt:
➤ зависимости централизованы
➤ код чище
➤ переиспользование проще
➤ тестировать удобнее
8. Cubit и State — в чём разница
Cubit — это логика. Он отвечает на вопрос: “что делать?”
State — это текущее состояние экрана. Он отвечает на вопрос: “что сейчас происходит?”
Например в ProfileState:
➤ status
➤ driver
➤ errorMessage
9. Что такое emit()
emit() = отправить новое состояние.
Когда cubit делает emit(...), UI получает новый state и может перерисоваться.
10. Почему используется copyWith()
State обычно делают immutable, то есть его не меняют напрямую.
Поэтому создается новый state на основе старого, меняя только нужные поля.
Было:
status = initial
driver = null
errorMessage = null
Стало:
state.copyWith(status: loading)
11. Как работает Profile flow
Шаг 1: Screen вызывает profileCubit.loadProfile(userId)
Шаг 2: Cubit делает emit(state.copyWith(status: loading))
Шаг 3: Cubit вызывает _apiService.getProfile(id)
Шаг 4: Service вызывает apiClient.get(UrlPaths.driver(id))
Шаг 5: ApiClient идет в backend
Шаг 6: Service превращает JSON в DriverModel
Шаг 7: Cubit делает emit(state.copyWith(status: loaded, driver: driver))
Шаг 8: UI получает новый state и показывает данные
12. Что видит UI по state
Если loading → показать loader
Если loaded → показать данные
Если error → показать ошибку
S — Single Responsibility (Единственная ответственность): Один класс — одна задача. Виджет только рисует, BLoC только обрабатывает логику.
O — Open/Closed (Открытость/Закрытость): Код открыт для расширения, но закрыт для изменения. Используем abstract классы и interfaces.
L — Liskov Substitution (Замещение Лисков): Наследник должен полностью заменять родителя, не ломая логику.
I — Interface Segregation (Разделение интерфейса): Лучше много маленьких интерфейсов, чем один огромный "всемогущий".
D — Dependency Inversion (Инверсия зависимостей): Зависеть от абстракций (интерфейсов), а не от конкретных реализаций.
Разделение приложения на независимые слои (Data, Domain, Presentation):
➤ Data Layer: Самый внешний слой. Работа с API, базами данных (DTO, Repositories implementations, DataSources).
➤ Domain Layer: "Сердце" приложения. Чистый Dart-код без Flutter. Содержит Entities (сущности), UseCases (бизнес-задачи) и абстрактные репозитории.
➤ Presentation Layer: UI часть. Виджеты и стейт-менеджеры (BLoC, Cubit).
DI — это паттерн, при котором объект получает свои зависимости извне, а не создает их сам.
Как используется во Flutter:
Обычно через библиотеку GetIt (Service Locator) или Provider.
Вместо final api = Api(); внутри класса, мы передаем его в конструктор: MyRepo(this.api);.
GetIt.I.registerSingleton<ApiService>(ApiService()); — регистрируем один раз, используем везде.
Зачем? Чтобы легко подменять реальные сервисы на "фейковые" (Mock) во время тестов.
Repository — это посредник между источником данных (API, БД) и бизнес-логикой.
Зачем: BLoC-у всё равно, откуда приходят данные — из интернета или из памяти. Он просто вызывает repository.getUser().
http на Dio, нам придется поменять код только в репозитории, а не во всем приложении.
В: Что такое Инверсия зависимостей?
О: Это принцип, по которому высокоуровневые модули (логика) не зависят от низкоуровневых (сеть, БД). Оба зависят от абстракций (интерфейсов).
В: Зачем нужен слой UseCase в Чистой архитектуре?
О: UseCase описывает конкретное действие пользователя (например, "LoginWithEmail"). Это позволяет видеть всю логику приложения в списке файлов папки domain.
В: В чем разница между GetIt и Provider?
О: GetIt — это Service Locator (глобальный реестр объектов). Provider — это DI на основе дерева виджетов.