Розробка чату в мобільному додатку (один на один)
Приватний чат між двома користувачами — не «просто список повідомлень». Тут проблема зазвичай не у відображенні, а в синхронізації станів: користувач на iOS відправив повідомлення, Android-співрозмовник бачить його через 3 секунди з дублем, тому що WebSocket відвалився і клієнт перевідправив через REST. Або історія не завантажується при поганому з'єднанні, тому що пагінація реалізована через OFFSET і timeout б'є по 500-й сторінці.
Ми в TrueTech вирішуємо ці проблеми з 5-річним досвідом. За цей час ми реалізували 40+ проєктів з чатами, включаючи месенджери для EdTech та FinTech. Гарантуємо стабільну роботу навіть при агресивній економії трафіку. Зв'яжіться з нами, щоб обговорити ваш проєкт — ми оцінимо задачу за 1 день.
Який транспорт обрати для real-time чату?
Для чату один на один достатньо WebSocket-з'єднання. На iOS — URLSessionWebSocketTask (нативно, без залежностей) або Starscream якщо потрібна більш гнучка обробка heartbeat. На Android — OkHttp WebSocket з коробки через Retrofit-екосистему або Ktor WebSocket Client для Kotlin Multiplatform. Як зазначено в документації Apple, ці бібліотеки підтримують TLS 1.3, що гарантує безпеку транспорту.
Критичний момент — реконнект. WebSocket рветься при зміні мережі (Wi-Fi → 4G), при блокуванні фону iOS, при агресивному Doze Mode на Android. Потрібен exponential backoff: перший реконнект через 1s, потім 2s, 4s, 8s, cap 30s. Після реконнекту — запит missed messages з last_message_id щоб не пропустити те, що прийшло поки з'єднання було розірвано. Ми гарантуємо, що повідомлення не втрачаються завдяки ідемпотентності та replay-механізму.
Firebase Realtime Database або Firestore — альтернатива власному WS-серверу для невеликих проєктів. Швидко стартувати, але при складній бізнес-логіці (модерація, шифрування на рівні сервера) виникає обмеження Cloud Functions.
Що входить в розробку чату під ключ?
| Компонент | Опис | Термін |
|---|---|---|
| WebSocket-сервер | Налаштування WS, реконнект, heartbeat, missed messages | 1-2 дні |
| Модель даних | Проектування схеми повідомлень та діалогів, міграції | 1 день |
| UI чату | Список повідомлень, індикатори набору, статуси доставки | 2-3 дні |
| Пагінація | cursor-based, інвертований список, плавне підвантаження | 1 день |
| Push-сповіщення | APNs/FCM, розшифрування прев'ю, deep link | 1-2 дні |
| E2E-шифрування | Signal Protocol, управління ключами (опціонально) | 2-3 дні |
Базовий набір включає всі пункти, крім E2E. Ми також надаємо документацію API, інструкції з розгортання та 2 тижні підтримки після запуску. Замовте розробку чату під ключ – отримайте консультацію інженера безкоштовно.
Як ми будуємо чат: архітектура та ключові рішення
Модель даних
Повідомлення: id, conversation_id, sender_id, body, type (text/image/file/system), status (sent/delivered/read), client_message_id (UUID генерується на клієнті), created_at. client_message_id — ключ ідемпотентності: якщо клієнт перевідправляє після таймауту, сервер не створює дубль.
Conversation: id, participant_ids[], last_message_id, last_message_at. Індекс: (participant_ids, last_message_at DESC) — щоб список діалогів у користувача вибирався швидко.
Пагінація історії
Cursor-based по (created_at, id): завантажуємо останні N повідомлень, при скролі вгору передаємо before_cursor. Це працює стабільно при швидкій вставці нових повідомлень — OFFSET «пливе», коли між сторінками з'являються нові записи.
На iOS список повідомлень — UICollectionView з інвертованим layout (нові знизу): transform = CGAffineTransform(scaleX: 1, y: -1) на collectionView і кожній комірці. При додаванні нового повідомлення insertItems з scrollToItem — без reloadData який викликає мерехтіння. На Android — LazyColumn(reverseLayout = true) в Jetpack Compose.
Статуси доставки та прочитання
Delivered: сервер підтверджує отримання повідомлення (ack в WS-протоколі) та оновлює статус. Read: клієнт-отримувач відправляє read receipt коли conversation відкрито і повідомлення видиме на екрані (через Intersection Observer на вебі або через UICollectionView.indexPathsForVisibleItems на iOS).
Статуси в UI: одна галочка (sent), дві сірі (delivered), дві сині (read) — класика. Змінюємо через локальний update в DiffableDataSource без мережевого запиту.
Шифрування
Для базового end-to-end: Signal Protocol через libsignal-client (Rust-бібліотека з біндингами для iOS/Android). Ключі зберігаються в Keychain (iOS) / Android Keystore. Сервер бачить тільки зашифрований blob — навіть при компрометації БД переписка не читається.
Якщо E2E не обов'язкове — шифрування на транспорті (TLS 1.3) + шифрування у спокої на стороні сервера достатньо для більшості випадків.
Push-сповіщення коли чат закрито
FCM (Android) і APNs (iOS). На iOS потрібен UNUserNotificationCenter + UNNotificationServiceExtension якщо потрібно показати прев'ю зашифрованого повідомлення: Extension розшифровує payload перед показом без передачі ключів серверу.
Deep link при тапі на пуш — відкриває конкретний conversation: myapp://chat/conversation/{id}. Реалізується через UIApplicationDelegate.application(_:open:options:) або onOpenURL в SwiftUI.
Етапи та терміни
Базовий чат (WS-з'єднання, історія з пагінацією, статуси, пуши) — 5 робочих днів на одну платформу. Додавання медіа-вкладень, E2E-шифрування, індикатора набору тексту (typing indicator через WS-event) — ще 3-5 днів. Flutter — трохи швидше за рахунок єдиної кодової бази. Стоимость розробки чату під ключ залежить від складності та визначається після аналізу. Зв'яжіться з нами для точної оцінки вашого проєкту.







