Разработка отслеживания статуса заказа в мобильном приложении
Клиент открывает приложение через 40 минут после оформления заказа и видит статус «В обработке» — тот же, что был сразу после оплаты. Звонит в поддержку. Проблема не в логистике: курьер уже едет, координаты обновляются на сервере каждые 30 секунд. Проблема в том, что мобильное приложение не подключено к этому потоку.
Таймлайн статусов и карта курьера — это два разных механизма с разными требованиями к обновлению, и смешивать их в одном polling-запросе — первая архитектурная ошибка.
Таймлайн статусов: WebSocket против polling
Статус заказа меняется редко — 5-7 раз за весь жизненный цикл. Для этого подходит long-polling или SSE (Server-Sent Events): соединение открыто, сервер пушит событие только при смене статуса. WebSocket здесь избыточен, хотя его часто выбирают по инерции.
На 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 писать вручную — трата времени на обработку краевых случаев.
Структура таймлайна на экране
Для отображения прогресса статусов используют RecyclerView (Android) или UICollectionView с кастомным layout (iOS). Типичная ошибка — хранить «прошедшие» статусы только на клиенте. Если пользователь удалил и переустановил приложение, история пропадёт. Все пройденные статусы с временными метками должны возвращаться с сервера в массиве:
{
"currentStatus": "courier_assigned",
"timeline": [
{ "status": "created", "timestamp": "2024-03-15T10:00:00Z" },
{ "status": "confirmed", "timestamp": "2024-03-15T10:02:30Z" },
{ "status": "courier_assigned", "timestamp": "2024-03-15T10: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.
Push-уведомления как резервный канал
Пользователь сворачивает приложение — WebSocket и SSE разрываются. Ключевые смены статуса (курьер забрал заказ, курьер рядом, заказ доставлен) дублируются через FCM/APNs. На iOS нужен UNUserNotificationCenter, на Android — FirebaseMessagingService.
Тонкость: уведомление «курьер рядом» теряет смысл, если пришло через 10 минут после доставки. Сервер должен проверять актуальность события перед отправкой пуша — это серверная логика, не мобильная.
Что входит в работу
- Таймлайн статусов с SSE или FCM-пушами
- Карта курьера с анимированным маркером (Google Maps SDK или MapKit)
- WebSocket или polling для координат с правильным lifecycle (onPause/onResume / viewDidDisappear)
- Обработка оффлайн-состояния: очередь событий и синхронизация при восстановлении сети
Сроки
3–5 дней для полного flow: таймлайн + карта + пуши. Только таймлайн без карты — 1–2 дня. Стоимость рассчитывается индивидуально после анализа требований.







