Dark Theme - On/Off
Pulsate - On
Waves - On

QCharge App Architecture

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 → показать ошибку

Коротко: Screen запускает действие, Cubit управляет состоянием, Service знает API, ApiClient делает сеть. После ответа данные превращаются в model, Cubit эмитит новый state, а UI обновляется.

Принципы SOLID во Flutter

S — Single Responsibility (Единственная ответственность): Один класс — одна задача. Виджет только рисует, BLoC только обрабатывает логику.

O — Open/Closed (Открытость/Закрытость): Код открыт для расширения, но закрыт для изменения. Используем abstract классы и interfaces.

L — Liskov Substitution (Замещение Лисков): Наследник должен полностью заменять родителя, не ломая логику.

I — Interface Segregation (Разделение интерфейса): Лучше много маленьких интерфейсов, чем один огромный "всемогущий".

D — Dependency Inversion (Инверсия зависимостей): Зависеть от абстракций (интерфейсов), а не от конкретных реализаций.

Коротко: SOLID нужен, чтобы код был гибким, его было легко менять и тестировать, не ломая всё приложение.

Clean Architecture (Чистая архитектура)

Разделение приложения на независимые слои (Data, Domain, Presentation):

Data Layer: Самый внешний слой. Работа с API, базами данных (DTO, Repositories implementations, DataSources).

Domain Layer: "Сердце" приложения. Чистый Dart-код без Flutter. Содержит Entities (сущности), UseCases (бизнес-задачи) и абстрактные репозитории.

Presentation Layer: UI часть. Виджеты и стейт-менеджеры (BLoC, Cubit).

Зачем это надо? Чтобы можно было легко заменить базу данных или UI, не трогая бизнес-логику. Суть: Зависимости направлены внутрь. Domain ничего не знает о Data или UI.

Dependency Injection (Внедрение зависимостей)

DI — это паттерн, при котором объект получает свои зависимости извне, а не создает их сам.

Как используется во Flutter:
Обычно через библиотеку GetIt (Service Locator) или Provider. Вместо final api = Api(); внутри класса, мы передаем его в конструктор: MyRepo(this.api);.

Пример: GetIt.I.registerSingleton<ApiService>(ApiService()); — регистрируем один раз, используем везде. Зачем? Чтобы легко подменять реальные сервисы на "фейковые" (Mock) во время тестов.

Паттерн Repository (Репозиторий)

Repository — это посредник между источником данных (API, БД) и бизнес-логикой.

Зачем: BLoC-у всё равно, откуда приходят данные — из интернета или из памяти. Он просто вызывает repository.getUser().

Пример: Если завтра мы сменим библиотеку для запросов с http на Dio, нам придется поменять код только в репозитории, а не во всем приложении.

Блиц-ответы для собеседования

В: Что такое Инверсия зависимостей?
О: Это принцип, по которому высокоуровневые модули (логика) не зависят от низкоуровневых (сеть, БД). Оба зависят от абстракций (интерфейсов).

В: Зачем нужен слой UseCase в Чистой архитектуре?
О: UseCase описывает конкретное действие пользователя (например, "LoginWithEmail"). Это позволяет видеть всю логику приложения в списке файлов папки domain.

В: В чем разница между GetIt и Provider?
О: GetIt — это Service Locator (глобальный реестр объектов). Provider — это DI на основе дерева виджетов.