Реалізація отправки геолокації у чаті мобільного додатку
Кнопка «Поділитися місцезнаходженням» у чаті виглядає простою фічею, поки не зіткнешся з тим, що iOS 17 розрізняє разовое місцезнаходження (requestLocation) та постійний моніторинг (startUpdatingLocation), а Android 10+ вимагає окремого дозволу ACCESS_BACKGROUND_LOCATION для фонових оновлень. Крім того, жива геолокація (коли собеседник бачить, куди ти їдиш у реальному часі) — це принципово інша архітектура порівняно з одиночним снімком координат.
Разове місцезнаходження vs живе відстеження
Для разового «де я зараз» достатньо запросити координати один раз, сформувати сповіщення зі статичною картою та відправити його у чат як attachment. Собеседник бачить превью карти з маркером.
Живе відстеження — це окремий тип сповіщення у чаті (наприклад, type: "live_location") з часом життя. Google Maps Messenger та WhatsApp обмежують трансляцію 15-60 хвилинами. За закінченням часу бекенд автоматично закриває сессію, та сповіщення переходить у «статичний» стан.
Разове на iOS
import CoreLocation
class LocationManager: NSObject, CLLocationManagerDelegate {
private let manager = CLLocationManager()
var onLocation: ((CLLocation) -> Void)?
func requestOnce() {
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
manager.requestWhenInUseAuthorization()
manager.requestLocation() // однократний запит
}
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
onLocation?(location)
}
}
requestLocation() дає рівно одне оновлення та зупиняється. Використовуйте kCLLocationAccuracyHundredMeters — для чата точність до метра не потрібна, а батарею економить.
Жива геолокація: архітектура
Трансляція поточного положення вимагає трьох шарів:
- Мобільний клієнт-відправник періодично пише координати на сервер
- Бекенд зберігає останні координати та рассилає оновлення підписникам (WebSocket / SSE)
- Мобільний клієнт-отримувач отримує оновлення та рухає маркер на карті
На Android оновлення координат у фоні — через FusedLocationProviderClient з WorkManager або ForegroundService. З WorkManager одним не обійтись: PeriodicWorkRequest має мінімальний інтервал 15 хвилин, що для живої геолокації марне. Потрібен ForegroundService з сповіщенням у статусбарі — користувач повинен бачити, що додаток активно використовує GPS.
class LocationTrackingService : Service() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
result.lastLocation?.let { location ->
sendLocationToServer(location.latitude, location.longitude)
}
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(NOTIFICATION_ID, buildNotification())
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000L)
.setMinUpdateIntervalMillis(3000L)
.build()
fusedLocationClient.requestLocationUpdates(request, locationCallback, mainLooper)
return START_STICKY
}
}
На iOS жива трансляція у фоні працює через startUpdatingLocation з allowsBackgroundLocationUpdates = true та ключем UIBackgroundModes: location у Info.plist. Без цього ключа — крах у розробці, а не на ревью.
Відображення на карті у отримувача
Отримувач бачить маркер собеседника поверх власного місцезнаходження. Анімація переміщення — обов'язкова, інакше маркер «прибігає».
Сповіщення у чаті з живою геолокацією містить session_id. Отримувач підписується на WebSocket-канал цієї сессії:
ws://api.example.com/location-sessions/{session_id}
Кожні N секунд сервер публікує {lat, lng, bearing, accuracy}. Bearing потрібен для повороту іконки у напрямку руху.
Статичне превью карти
Для разового місцезнаходження у пузирі чата рендеримо статичне зображення через Google Static Maps API або MapKit Snapshot:
// iOS MapKit Snapshot
let options = MKMapSnapshotter.Options()
options.region = MKCoordinateRegion(
center: coordinate,
latitudinalMeters: 500,
longitudinalMeters: 500
)
options.size = CGSize(width: 240, height: 160)
MKMapSnapshotter(options: options).start { snapshot, _ in
guard let snapshot = snapshot else { return }
let image = snapshot.image
// відображаємо у ячейці чата
}
Снімок рендерится асинхронно — не блокує UI при швидкому скролі.
Терміни
2–3 дні для разового місцезнаходження зі статичним превью. Жива трансляція з ForegroundService / background mode — 4–6 днів. Вартість рассчитується індивідуально.







