Розробка трекинга передвиження курьера в мобільному додатку
Трекинг курьера — це задача рівня складності «здається простою, поки не почнеш». Показати іконку курьера на карті клієнта і рухати її у реальному часі — технічно це WebSocket, координати, анімація маркера. Але продакшн-система включає: dead-reckoning при втраті сигналу GPS, map snapping до доріг, логіку станів курьера, батарейний компромісс на стороні додатку курьера і надійну серверну шину.
Архітектура системи цілком
Три компоненти:
- Додаток курьера — збирає GPS, передає координати на сервер
- Сервер — приймає, зберігає останню позицію, розсилає обновлення підписникам
- Додаток клієнта — отримує обновлення, анімує маркер
Це важливо проектувати як систему, а не як «фічу в одному додатку». Вибір протоколу впливає на всі три компоненти.
Додаток курьера: збір і передача координат
На Android — FusedLocationProviderClient з play-services-location в Foreground Service. Повідомлення в статус-барі обов'язково з Android 8+. Інтервал обновлення — компромісс: 5 секунд дає хорошу точність, 15 секунд економить батарею. Для пішого курьера — Priority.PRIORITY_HIGH_ACCURACY + 5 секунд. Для авто — Priority.PRIORITY_BALANCED_POWER_ACCURACY + 10 секунд з setMinUpdateDistanceMeters(20f): зайві точки при стоянні на світлофорі не потрібні.
На iOS — CLLocationManager з desiredAccuracy: kCLLocationAccuracyBestForNavigation в активному режимі і переключенням на Significant Location Changes при уходе в фон (докладніше у статті з фонового трекингу). activityType = .automotiveNavigation на iPhone активує додаткову фільтрацію шуму GPS при русі на авто.
Фільтрація аномальних точок на клієнті — обов'язкова. GPS на міських вулицях регулярно выдає скачки на 50-200 метрів (multipath від будівель). Простий фільтр: відкидаємо точку, якщо horizontalAccuracy > 50 метрів або розраховуна швидкість між двома точками > 200 км/ч.
func shouldAcceptLocation(_ location: CLLocation) -> Bool {
guard location.horizontalAccuracy > 0,
location.horizontalAccuracy <= 50 else { return false }
if let lastLocation = lastAcceptedLocation {
let timeDelta = location.timestamp.timeIntervalSince(lastLocation.timestamp)
let distance = location.distance(from: lastLocation)
let impliedSpeed = distance / timeDelta // метри/секунду
if impliedSpeed > 55.6 { return false } // > 200 км/ч
}
return true
}
Буферизація в локальній БД + відправка пачками при наявності мережі — стандартна схема. При втраті мережі точки накапливаются в Room / Core Data, WorkManager / background fetch відправляє їх при відновленні.
Сервер: шина реального часу
Для невеликих нагрузок (до кількох тисяч курьерів одночасно) — Socket.IO на Node.js або FastAPI з WebSocket. Клієнт-курьер публікує координати в топік courier/{id}/position, сервер розсилає підписникам — додаткам клієнтів, які замовили цього курьера.
Для масштабу — MQTT broker (Mosquitto, EMQ X, AWS IoT Core). MQTT легше за WebSocket по трафіку, краще переживає нестабільні мобільні з'єднання за рахунок QoS-рівнів і сесій з persist.
Зберігання поточної позиції — Redis: SET courier:{id}:position "{lat,lon,timestamp}" з TTL 60 секунд. Якщо за хвилину нових координат немає — курьер вважається офлайн. Історія маршруту — TimescaleDB або InfluxDB для time-series даних.
Map snapping до доріг. Сирі GPS-координати курьера прискакують відносно дороги. Google Roads API snapToRoads або nearestRoads вирівнює трек по дорожному графу. Для високонавантажених систем — OSRM self-hosted: безплатно, швидше (< 10 мс vs 50-100 мс у Google), без квот.
Додаток клієнта: плавна анімація маркера
Отримуємо нову координату курьера через WebSocket/MQTT кожні 5-10 секунд. Без анімації маркер «прискакує» — некомфортно. Потрібно інтерполювати рух між двома точками за N секунд.
На Android через ValueAnimator:
val animator = ValueAnimator.ofFloat(0f, 1f).apply {
duration = 3000
interpolator = LinearInterpolator()
addUpdateListener { animation ->
val fraction = animation.animatedFraction
val lat = prevLat + (newLat - prevLat) * fraction
val lon = prevLon + (newLon - prevLon) * fraction
marker.position = LatLng(lat, lon)
}
}
animator.start()
На iOS — CADisplayLink або UIView.animate з кастомним timing function. Кут повороту маркера-стрілки розраховуємо через atan2(deltaLon, deltaLat) — курьер дивиться в напрямку руху.
У Flutter — AnimatedWidget з Tween<LatLng> або TweenAnimationBuilder. Пакет google_maps_flutter не анімує маркери встроєно, але Marker(position: interpolatedPosition) в setState кожні 16 мс в Ticker дає плавність 60 FPS.
Стани курьера
Курьер не завжди їде. Схема станів: idle → assigned → picking_up → delivering → completed. Кожен стан впливає на UI клієнта: «шукаємо курьера», «курьер їде до ресторану», «курьер везе ваш замовлення». Переходи станів — серверна логіка, не клієнтська. Додаток курьера відправляє события (прийняв замовлення, забрав, вишов до клієнта), сервер змінює стан і нотифицирует клієнта через той же канал.
Процес роботи
Проектування протоколу і серверної архітектури. Розробка додатку курьера з фоновим трекингом. Реалізація серверної шини. Розробка UI для клієнта з анімацією. Нагрузочне тестування шини: 100/500/1000 одночасних курьерів.
Строк: від двох до чотирьох тижнів залежно від кількості платформ, наявності готового серверного API і вимог до масштабування.







