Flutter Client / Design Patterns

Архитектура Flutter клиента

Слоистая структура приложения, управление состоянием и оффлайн-синхронизация.

Архитектурные слои

Приложение разделено на 4 четких слоя по принципам Clean Architecture:

СлойПапкаНазначение
Presentationlib/presentationUI Виджеты, экраны, анимации и стили темы. Рендеринг интерфейса.
State Management (Logic)lib/logicПровайдеры (ChangeNotifier) — бизнес-логика интерфейса, связующее звено между UI и сервисами.
Domainlib/domainИнтерфейсы и независимые сервисы (API, Crypto, WebRTC), выполняющие операции ввода-вывода и криптографию.
Datalib/dataDrift SQLite база данных, сетевые источники (WS), репозитории и модели данных.

Подробное устройство каталогов

📁 lib/
📁 core/ — Системные константы, ThemeManager конфигурация
📁 data/ — Реализация данных
📁 datasources/ — Drift база данных (local_db_service.dart), WebSocket клиент (ws_client.dart)
📁 models/ — Dart-классы для парсинга JSON: contact_model, message_model, session_model
📁 repositories/ — Реализация репозиториев (contact_repository.dart) для работы с кэшем
📁 domain/ — Логика предметной области
📁 services/ — api_service (HTTP REST), crypto_service (E2EE), webrtc_service (Звонки)
📁 logic/ — ChangeNotifier провайдеры
📄 auth_provider.dart — Контроль сессий, FaceID/PIN блокировка, приватный ключ в ОЗУ
📄 contact_provider.dart — Списки чатов, WebSocket обработка, квитанции ACK
📁 presentation/ — UI элементы
📁 screens/ — Все экраны интерфейса (25 штук)
📁 widgets/ — Компоненты UI (пузыри сообщений, карточки чатов, поля ввода)

Реактивная оффлайн-синхронизация

Клиент работает по принципу Offline-First. Для этого используется Drift SQLite. Поток сообщений рендерится с помощью StreamBuilder, подписанного на Drift-запрос:

dartРеактивный стрим сообщений
Stream<List<Message>> watchMessagesForChat(int contactId) {
  return (select(messages)
    ..where((t) => (t.senderId.equals(contactId) & t.receiverId.equals(myId)) | 
                   (t.senderId.equals(myId) & t.receiverId.equals(contactId)))
    ..orderBy([(t) => OrderingTerm(expression: t.id, mode: OrderingMode.desc)]))
    .watch();
}

Когда с WebSocket сервера приходят новые данные через ws_client.dart, они парсятся и сразу записываются в Drift БД. Благодаря Stream-подписке интерфейс обновляется автоматически со скоростью 60/120 кадров в секунду, даже если в этот момент у пользователя прервалось сетевое подключение.