Development of Courier Movement Tracking in a Mobile Application
Courier tracking is a complexity level task that "seems simple until you start." Showing a courier icon on the customer's map and moving it in real time is technically WebSocket, coordinates, marker animation. But a production system includes: dead-reckoning on GPS signal loss, map snapping to roads, courier state logic, battery compromise on app side, and reliable server bus.
System Architecture Overall
Three components:
- Courier app — collects GPS, transmits coordinates to server
- Server — receives, stores last position, distributes updates to subscribers
- Customer app — receives updates, animates marker
This must be designed as a system, not as "feature in one app." Protocol choice affects all three components.
Courier App: Collection and Transmission of Coordinates
On Android — FusedLocationProviderClient from play-services-location in Foreground Service. Notification in status bar is mandatory with Android 8+. Update interval is compromise: 5 seconds gives good accuracy, 15 seconds saves battery. For foot courier — Priority.PRIORITY_HIGH_ACCURACY + 5 seconds. For car — Priority.PRIORITY_BALANCED_POWER_ACCURACY + 10 seconds with setMinUpdateDistanceMeters(20f): extra points at traffic light don't help.
On iOS — CLLocationManager with desiredAccuracy: kCLLocationAccuracyBestForNavigation in active mode and switch to Significant Location Changes when going background (see background tracking article). activityType = .automotiveNavigation on iPhone activates extra GPS noise filtering for car movement.
Anomalous point filtering on client is mandatory. GPS on city streets regularly outputs jumps of 50-200 meters (multipath from buildings). Simple filter: drop point if horizontalAccuracy > 50 meters or calculated speed between two points > 200 km/h.
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 // meters/second
if impliedSpeed > 55.6 { return false } // > 200 km/h
}
return true
}
Buffer in local DB + send in batches on internet availability — standard scheme. On network loss, points accumulate in Room / Core Data, WorkManager / background fetch sends them on restoration.
Server: Real-time Bus
For small loads (up to several thousand couriers simultaneously) — Socket.IO on Node.js or FastAPI with WebSocket. Courier client publishes coordinates to topic courier/{id}/position, server distributes to subscribers — customer apps that ordered this courier.
For scale — MQTT broker (Mosquitto, EMQ X, AWS IoT Core). MQTT is lighter than WebSocket on traffic, better survives unstable mobile connections via QoS levels and persistent sessions.
Store current position in Redis: SET courier:{id}:position "{lat,lon,timestamp}" with 60-second TTL. No coordinates in a minute — courier is offline. Route history — TimescaleDB or InfluxDB for time-series data.
Map snapping to roads. Raw GPS coordinates jump relative to road. Google Roads API snapToRoads or nearestRoads aligns track to road graph. For high-load systems — OSRM self-hosted: free, faster (< 10 ms vs 50-100 ms Google), no quotas.
Customer App: Smooth Marker Animation
Receive new courier coordinate via WebSocket/MQTT every 5-10 seconds. Without animation, marker "jumps" — uncomfortable. Need to interpolate movement between two points over N seconds.
On Android via 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()
On iOS — CADisplayLink or UIView.animate with custom timing function. Calculate marker arrow rotation via atan2(deltaLon, deltaLat) — courier faces direction of movement.
In Flutter — AnimatedWidget with Tween<LatLng> or TweenAnimationBuilder. google_maps_flutter package doesn't animate markers natively, but Marker(position: interpolatedPosition) in setState every 16 ms in Ticker gives smooth 60 FPS.
Courier States
Courier is not always moving. State scheme: idle → assigned → picking_up → delivering → completed. Each state affects customer UI: "finding courier," "courier going to restaurant," "courier delivering your order." State transitions are server logic, not client. Courier app sends events (accepted order, picked up, reached customer), server changes state and notifies client via same channel.
Workflow
Protocol and server architecture design. Develop courier app with background tracking. Implement server bus. Develop customer UI with animation. Load test bus: 100/500/1000 concurrent couriers.
Timeline: from two to four weeks depending on number of platforms, ready server API availability, and scaling requirements.







