Implementing Location Sharing in Mobile Chat
A "Share location" button in chat looks simple until you encounter iOS 17 differentiating one-time location (requestLocation) from continuous monitoring (startUpdatingLocation), and Android 10+ requiring separate ACCESS_BACKGROUND_LOCATION permission for background updates. Plus, live location sharing (where buddy sees where you're driving in realtime) is a fundamentally different architecture from a single coordinate snapshot.
One-Time Location vs Live Tracking
For one-time "where am I now" — request coordinates once, form message with static map and send as attachment. Buddy sees map preview with marker.
Live tracking — separate message type in chat (e.g., type: "live_location") with lifespan. Google Maps Messenger and WhatsApp limit broadcast to 15-60 minutes. After time expires, backend auto-closes session, message becomes "static".
One-Time on 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() // single request
}
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
onLocation?(location)
}
}
requestLocation() gives exactly one update and stops. Use kCLLocationAccuracyHundredMeters — chat doesn't need meter-level precision, saves battery.
Live Location: Architecture
Broadcasting current position requires three layers:
- Mobile client-sender periodically writes coordinates to server
- Backend stores latest coordinates and broadcasts updates to subscribers (WebSocket / SSE)
- Mobile client-receiver gets updates and moves marker on map
On Android background coordinate updates — through FusedLocationProviderClient with WorkManager or ForegroundService. WorkManager alone won't work: PeriodicWorkRequest has minimum 15-minute interval, useless for live location. Need ForegroundService with status bar notification — user must see app actively using 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
}
}
On iOS live broadcast works through startUpdatingLocation with allowsBackgroundLocationUpdates = true and UIBackgroundModes: location key in Info.plist. Without this key — crash in development, not on review.
Display on Recipient's Map
Recipient sees buddy's marker over their own location. Movement animation — mandatory, otherwise marker "jumps".
Message with live location contains session_id. Recipient subscribes to WebSocket channel of this session:
ws://api.example.com/location-sessions/{session_id}
Every N seconds server publishes {lat, lng, bearing, accuracy}. Bearing needed to rotate icon in direction of movement.
Static Map Preview
For one-time location in chat bubble, render static image through Google Static Maps API or 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
// display in chat cell
}
Snapshot renders async — doesn't block UI on fast scroll.
Timeframe
2–3 days for one-time location with static preview. Live broadcast with ForegroundService / background mode — 4–6 days. Cost calculated individually.







