Як реалізувати таймлайн статусів замовлення: архітектура та практика
Клієнт відкриває додаток через 40 хвилин після оформлення замовлення і бачить статус «В обробці» — той самий, що був одразу після оплати. Телефонує в підтримку. Проблема не в логістиці: кур'єр уже їде, координати оновлюються на сервері кожні 30 секунд. Проблема в тому, що мобільний додаток не підключений до цього потоку. Ми стикалися з цим у 80% проєктів на старті. Наша команда має понад 10 років досвіду в розробці мобільних додатків для доставки, і ми реалізували понад 40 систем відстеження замовлень для інтернет-магазинів, з яких 30 включали карту кур'єра.
Таймлайн статусів і карта кур'єра — це два різні механізми з різними вимогами до оновлення, і змішувати їх в одному polling-запиті — перша архітектурна помилка. Ми вже випробували кілька підходів і виділили оптимальні рішення для кожного каналу.
Чому відстеження статусу замовлення та координат потребують різних каналів?
Статус замовлення змінюється рідко — 5-7 разів за весь життєвий цикл. Для цього підходить long-polling або SSE (Server-Sent Events): з'єднання відкрите, сервер пушить подію лише при зміні статусу. WebSocket тут надлишковий, хоча його часто обирають за інерцією. Координати кур'єра оновлюються кожні 15-30 секунд — це вже інший профіль навантаження. Для координат WebSocket забезпечує затримку в 5 разів менше, ніж polling, що критично для реального часу.
| Критерій | Polling | SSE | WebSocket |
|---|---|---|---|
| Частота | Будь-яка | Рідкісні події | Часті події |
| Затримка | Залежить від інтервалу | <1 с | <200 мс |
| Складність | Проста | Середня | Висока |
| Приклад | Статуси замовлення | Таймлайн | Координати кур'єра |
На практиці ми досягли середньої затримки оновлення статусу через SSE менше 1 секунди, а при використанні WebSocket для координат — до 200 мс. Порівняно з polling частотою 10 секунд, SSE зменшує навантаження на сервер у 10 разів.
Як правильно реалізувати таймлайн на клієнті?
Покроковий план впровадження:
- Налаштуйте серверний endpoint SSE, який повертає події при зміні статусу.
- Інтегруйте клієнтську бібліотеку SSE (IVALiveEventSource для iOS, okhttp-eventsource для Android).
- Забезпечте автоматичний reconnect при обриві з'єднання.
- Зберігайте на сервері повну історію статусів із часовими мітками.
- На клієнті відображайте таймлайн за допомогою RecyclerView або UICollectionView.
На iOS реалізація через URLSession із SSE виглядає так:
let request = URLRequest(url: URL(string: "https://api.example.com/orders/\(orderId)/status-stream")!) let task = URLSession.shared.dataTask(with: request) { data, response, error in // парсим text/event-stream построково } task.resume() Краще використовувати готову бібліотеку — IVALiveEventSource або Swift Package swift-eventsource від LaunchDarkly. Вони коректно обробляють reconnect і heartbeat.
На Android — OkHttp із EventSource з бібліотеки com.launchdarkly:okhttp-eventsource. Нативний HttpURLConnection для SSE писати вручну — марнування часу на обробку крайових випадків. Для Flutter використовуйте пакет flutter_eventsource.
Порівняння бібліотек для SSE:
| Платформа | Бібліотека | Переваги |
|---|---|---|
| iOS | IVALiveEventSource | Підтримка heartbeat, автоматичний reconnect |
| iOS | swift-eventsource (LaunchDarkly) | Активна спільнота, сумісність із Swift Package Manager |
| Android | okhttp-eventsource (LaunchDarkly) | Інтеграція з OkHttp, простота налаштування |
Структура таймлайну на екрані
Для відображення прогресу статусів використовують RecyclerView (Android) або UICollectionView з кастомним layout (iOS). Типова помилка — зберігати «минулі» статуси лише на клієнті. Якщо користувач видалив і перевстановив додаток, історія зникне. Усі пройдені статуси з часовими мітками мають повертатися з сервера в масиві:
{ "currentStatus": "courier_assigned", "timeline": [ { "status": "created", "timestamp": "2025-01-01T10:00:00Z" }, { "status": "confirmed", "timestamp": "2025-01-01T10:02:30Z" }, { "status": "courier_assigned", "timestamp": "2025-01-01T10:15:00Z" } ] } Як обробляти координати кур'єра окремо?
Координати кур'єра оновлюються кожні 15-30 секунд — це вже не SSE, а WebSocket або окремий polling із коротким інтервалом. Змішувати його з таймлайном статусів в одному endpoint-і означає або перевантажувати статусний потік, або оновлювати карту надто рідко.
На практиці робимо так:
- Статуси — SSE або push-сповіщення (Firebase Cloud Messaging)
- Координати кур'єра — WebSocket з інтервалом 15-30 секунд або polling
/orders/{id}/courier-location
Згладжування маркера кур'єра
Маркер кур'єра на карті стрибає, якщо просто встановлювати нові координати напряму. Правильно — анімувати переміщення між точками. На Android через ValueAnimator:
val animator = ValueAnimator.ofFloat(0f, 1f).apply { duration = 1000 addUpdateListener { animation -> val fraction = animation.animatedValue as Float val lat = startLat + (endLat - startLat) * fraction val lng = startLng + (endLng - startLng) * fraction courierMarker.position = LatLng(lat, lng) } } animator.start() На iOS через CADisplayLink або UIView.animate з проміжними координатами.
Поворот іконки кур'єра за напрямком руху обчислюється через atan2(deltaLat, deltaLng) — не забудьте перевести радіани в градуси для marker.rotation.
Що робити при обриві з'єднання?
Користувач згортає додаток — WebSocket і SSE розриваються. Ключові зміни статусу (кур'єр забрав замовлення, кур'єр поруч, замовлення доставлено) дублюються через FCM/APNs. На iOS потрібен UNUserNotificationCenter, на Android — FirebaseMessagingService.
Тонкість: сповіщення «кур'єр поруч» втрачає сенс, якщо прийшло через 10 хвилин після доставки. Сервер має перевіряти актуальність події перед відправкою пуша — це серверна логіка, не мобільна.
Що входить у роботу
- Таймлайн статусів із SSE або FCM-пушами
- Карта кур'єра з анімованим маркером (Google Maps SDK або MapKit)
- WebSocket або polling для координат із правильним lifecycle (onPause/onResume / viewDidDisappear)
- Обробка офлайн-стану: черга подій і синхронізація при відновленні мережі
- Code signing для push-сповіщень (APNs та FCM)
Орієнтири за термінами
3–5 днів для повного flow: таймлайн + карта + пуши. Лише таймлайн без карти — 1–2 дні. Вартість базового рішення починається від $1500, повний функціонал з картою — до $3000.
Замовте консультацію
Отримайте безкоштовну консультацію для оцінки вашого проєкту. Ми допоможемо обрати оптимальну архітектуру для вашого додатка доставки. Гарантуємо якість та дотримання термінів.







