πŸ—οΈ System Design Hard 2025–26

Design Maps & Live Location Tracking

A deep-dive Android system design breakdown of real-time location tracking β€” covering FusedLocationProviderClient, WebSocket streaming, smooth marker animation, geofencing, battery strategy, and every edge case interviewers probe at senior and staff level.

Understanding the Problem

Design the Android client for maps and live location tracking β€” a driver's position updating in real-time on a passenger's screen in Ola/Uber, or a delivery agent tracked in Swiggy. The core challenges: getting accurate location efficiently without destroying battery, streaming location over a persistent connection, animating a map marker smoothly between GPS fixes, drawing and trimming a route polyline, and navigating Android's complex permission landscape.

πŸ“ŒCore Pattern: FusedLocation + WebSocket + ValueAnimator

Three technologies work in concert. FusedLocationProviderClient fuses GPS, Wi-Fi, and cell towers, selecting the best source for the requested accuracy/battery trade-off. WebSocket provides a bi-directional persistent connection to stream location at low latency, with exponential-backoff reconnection. ValueAnimator linearly interpolates a map marker between GPS fixes at 60 fps so it glides rather than teleports, even with updates every 2–5 seconds.

βœ… Functional Requirements

  • Show the user's own location on the map in real time
  • Track a driver/agent's position received from the server
  • Draw a route polyline, origin β†’ destination
  • Animate the driver marker smoothly between position updates
  • Show ETA and distance, updated as driver moves
  • Geofence: alert when driver enters the pickup radius
  • Driver app uploads location continuously in background

β›” Non-Functional Requirements

  • Location must not drain battery β€” choose right accuracy tier
  • Driver app: Foreground Service required for background location
  • Android 10+ requires explicit ACCESS_BACKGROUND_LOCATION
  • Marker animation must feel smooth (60 fps interpolation)
  • Graceful degradation when GPS signal is weak
  • Map tiles load offline (cached tiles)
  • Camera auto-follows driver unless user manually pans
Data Models

Three core models drive the system. DriverLocation is a single GPS fix uploaded by the driver. TripState is the driver-side state machine that determines accuracy tier and upload interval. LocationUpdateMessage is the WebSocket payload the server fans out to passengers.

DriverLocation
Doublelatitude
Doublelongitude
Floatbearing
Floatspeed (m/s)
Floataccuracy (metres)
Longtimestamp
BooleanisMock
TripState (enum)
IDLELOW_POWER, 60 s interval
SEARCHINGBALANCED, 10 s interval
EN_ROUTEHIGH_ACCURACY, 2 s interval
COMPLETEDLOW_POWER, 60 s interval
LocationUpdateMessage (WS)
StringtripId
Doublelat
Doublelng
Floatbearing
IntetaMinutes
LongserverTimestamp
DriverState (sealed)
Loadingconnecting to WS
Locatedpos, bearing, eta
Nearbywithin geofence radius
Arrivedtrip complete
DisconnectedWS error + attempt#
High-Level Design
Location accuracy vs battery
High Accuracy
PRIORITY_HIGH_ACCURACY
GPS + Wi-Fi + cell. ~3–5 m. High battery. Use for active navigation, driver upload during EN_ROUTE.
Balanced
PRIORITY_BALANCED_POWER
Wi-Fi + cell only. ~50–100 m. Moderate battery. Use for SEARCHING state, passenger blue dot.
Low Power
PRIORITY_LOW_POWER
Cell only. ~1–3 km. Minimal battery. Use for IDLE state β€” driver not on a trip.
Key Android APIs
πŸ“
FusedLocationProviderClient
Fuses GPS, Wi-Fi, cell for best accuracy/battery ratio. Replaces raw LocationManager.
πŸ””
LocationCallback + PendingIntent
Foreground: LocationCallback while service alive. Background/killed: PendingIntent wakes BroadcastReceiver.
πŸ—ΊοΈ
Google Maps SDK
GoogleMap: add/move markers, draw Polyline, animateCamera(), cluster markers with ClusterManager.
πŸ”΄
Foreground Service
Required for driver app background location. Persistent notification. foregroundServiceType=location since Android 10.
🎯
GeofencingClient
Register Geofence with center + radius. OS triggers PendingIntent on ENTER/EXIT β€” no polling needed.
🎞️
ValueAnimator
Interpolates LatLng between GPS fixes over 1 s. Updates marker.position each frame β€” 60 fps glide.
Architecture Diagram
DRIVER APP LocationForeground Service (START_STICKY) FusedLocation HIGH_ACCURACY, 2 s LocationRepository upload via WebSocket WebSocketManager exp. backoff reconnect GeofencingClient pickup radius β†’ PendingIntent β†’ notification Backend stores latest position fans out to passengers computes ETA + polyline PASSENGER APP WebSocketManager receives driver updates LocationViewModel StateFlow<DriverState> MarkerAnimator ValueAnimator LatLng lerp PolylineDrawer route + trimUpTo(driver) Google Maps SDK (SupportMapFragment) GoogleMap: marker, polyline, camera CameraTracker auto-follow vs user-panned mode FusedLocation (passenger) BALANCED_POWER β€” own blue dot push
Driver app uploads location via WS β†’ Backend fans out to passenger β†’ passenger renders animated marker + trimmed polyline on Maps SDK.
Low-Level Design
FusedLocationProviderClient β€” setup
class LocationForegroundService : Service() { private val fusedClient by lazy { LocationServices.getFusedLocationProviderClient(this) } private lateinit var locationCallback: LocationCallback override fun onCreate() { super.onCreate() startForeground(NOTIF_ID, buildNotification()) // required Android 9+ startLocationUpdates() } private fun startLocationUpdates() { val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 2000L) .setMinUpdateIntervalMillis(1000L) .setMinUpdateDistanceMeters(5f) // ignore <5 m movement .setWaitForAccurateLocation(false) // use fused immediately .build() locationCallback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { val loc = result.lastLocation ?: return locationRepository.uploadLocation( lat = loc.latitude, lng = loc.longitude, bearing = loc.bearing, accuracy = loc.accuracy, isMock = loc.isMock() ) } } fusedClient.requestLocationUpdates(request, locationCallback, Looper.getMainLooper()) } override fun onStartCommand(...): Int = START_STICKY // OS restarts if killed override fun onDestroy() { fusedClient.removeLocationUpdates(locationCallback); super.onDestroy() } }
Smooth marker animation with ValueAnimator
object MarkerAnimator { private var activeAnimator: ValueAnimator? = null fun animateTo(marker: Marker, newPos: LatLng, newBearing: Float, durationMs: Long = 1000L) { activeAnimator?.cancel() // prevent animation queue backlog on rapid updates val startLat = marker.position.latitude val startLng = marker.position.longitude val startBearing = marker.rotation activeAnimator = ValueAnimator.ofFloat(0f, 1f).apply { duration = durationMs; interpolator = LinearInterpolator() addUpdateListener { anim -> val t = anim.animatedValue as Float marker.position = LatLng(startLat + (newPos.latitude - startLat) * t, startLng + (newPos.longitude - startLng) * t) var delta = newBearing - startBearing if (delta > 180) delta -= 360 // handle 359Β° β†’ 1Β° wrap if (delta < -180) delta += 360 marker.rotation = startBearing + delta * t } start() } } }
Polyline draw and trim
class PolylineDrawer(private val map: GoogleMap) { private var polyline: Polyline? = null suspend fun drawRoute(origin: LatLng, destination: LatLng) { val points = directionsApi.getRoute(origin, destination) polyline?.remove() polyline = map.addPolyline(PolylineOptions().addAll(points).width(8f) .color(Color.parseColor("#4285F4")).jointType(JointType.ROUND)) } /** Removes the already-travelled portion β€” road disappears behind driver */ fun trimUpTo(driverPos: LatLng) { val pts = polyline?.points ?: return val idx = pts.indexOfMinBy { distanceBetween(it, driverPos) } polyline?.points = pts.drop(idx) } }
Camera auto-follow with user override
class CameraTracker(private val map: GoogleMap) { private var isFollowing = true init { map.setOnCameraMoveStartedListener { reason -> if (reason == GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE) isFollowing = false // user panned β€” stop following } } fun onDriverMoved(newPos: LatLng, bearing: Float) { if (!isFollowing) return map.animateCamera(CameraUpdateFactory.newCameraPosition( CameraPosition.Builder().target(newPos).zoom(17f).bearing(bearing).tilt(45f).build() ), 600, null) } fun resumeFollowing() { isFollowing = true } // Re-center FAB tap }
Geofencing β€” arrival alert
fun addPickupGeofence(lat: Double, lng: Double, radiusM: Float = 200f) { client.addGeofences( GeofencingRequest.Builder() .addGeofence(Geofence.Builder() .setRequestId("PICKUP_ZONE") .setCircularRegion(lat, lng, radiusM) .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER) .setExpirationDuration(Geofence.NEVER_EXPIRE) .build()) .build(), getPendingIntent()) } class GeofenceBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val event = GeofencingEvent.fromIntent(intent) ?: return if (event.geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) { showDriverNearbyNotification(context) geofenceManager.remove() // no longer needed } } }
Flow 1 β€” Passenger Opens Tracking Screen
Fragment / UILocationViewModelWebSocketManagerMarker / MapFusedLocation
1.onViewCreated() calls viewModel.startTracking(tripId)Opens WS to wss://api/trip/{tripId}/trackβ€”β€”β€”
β€”β€”2.WS connects. Server sends initial snapshot {lat, lng, bearing, eta}β€”β€”
β€”3.Emits DriverState.Located(pos, bearing) via StateFlowβ€”β€”β€”
4.Fragment collects StateFlow. Calls markerAnimator.animateTo() + cameraTracker.onDriverMoved()β€”β€”Marker appears. Camera animates with tilt + bearing.Passenger blue dot via map.isMyLocationEnabled = true
β€”β€”β€”5.PolylineDrawer fetches route from Directions API and draws polyline origin β†’ destinationβ€”
Flow 2 β€” Live Driver Position Update
Driver / ServerWS (Passenger)LocationViewModelMarkerAnimatorGoogleMap
1.Driver FusedLocation fires. App sends {lat, lng, bearing} every 2 sβ€”β€”β€”β€”
Server fans out to all passengers on this tripId2.WS receives onMessage(json). Parses LocationUpdateMessage.β€”β€”β€”
β€”β€”3.Emits DriverState.Located(newLatLng, newBearing, etaMinutes)β€”β€”
———4.ValueAnimator lerps marker old→new over 1 000 ms. Bearing rotates smoothly. 60 fps.Marker glides. ETA label updates.
β€”β€”β€”5.polylineDrawer.trimUpTo(driverPos) β€” removes travelled portionPolyline shrinks from origin end.
Flow 3 β€” Driver Enters Pickup Geofence
OS / FusedLocationGeofencingClientBroadcastReceiverNotificationManagerUI / Fragment
1.OS monitors driver against geofences at low power (no GPS chip)β€”β€”β€”β€”
β€”2.Driver crosses 200 m pickup radius. OS fires PendingIntent.β€”β€”β€”
β€”β€”3.onReceive() confirms ENTER for "PICKUP_ZONE".β€”β€”
β€”β€”4.Removes geofence. Posts "Driver is 200 m away" notification.Fires with sound + vibration on CH_DRIVER_NEARBYβ€”
β€”β€”β€”β€”5.If foreground: StateFlow emits DriverState.Nearby β†’ "Driver arriving!" banner
Deep Dives
Location permission landscape β€” Android 10+
Use CaseRequired PermissionWhen PromptedNotes
Passenger blue dotACCESS_FINE_LOCATIONRuntime β€” app openNeeded for HIGH_ACCURACY.
Driver background uploadACCESS_BACKGROUND_LOCATIONSeparate dialog Android 10+, Settings on 11+Request FINE first, then BACKGROUND separately. Most critical.
GeofencingFINE + BACKGROUND_LOCATIONSame as aboveGeofences fire when app is killed β€” needs background permission.
Foreground Service (Android 14+)FOREGROUND_SERVICE_LOCATIONManifest only β€” no dialogAdd android:foregroundServiceType="location" in manifest.
Trip-state-adaptive battery strategy
fun updateLocationPriority(tripState: TripState) { val (priority, intervalMs) = when (tripState) { TripState.IDLE -> Priority.PRIORITY_LOW_POWER to 60_000L TripState.SEARCHING -> Priority.PRIORITY_BALANCED_POWER to 10_000L TripState.EN_ROUTE -> Priority.PRIORITY_HIGH_ACCURACY to 2_000L TripState.COMPLETED -> Priority.PRIORITY_LOW_POWER to 60_000L } fusedClient.removeLocationUpdates(locationCallback) fusedClient.requestLocationUpdates(buildRequest(priority, intervalMs), locationCallback, looper) } // setMinUpdateDistanceMeters(5f) filters noise when stationary at a red light
Edge Cases

These are the questions that separate senior from staff. Each one represents a real production failure mode in a live-tracking app.

πŸ“‘
GPS Signal Lost Mid-Trip

The problem: Driver enters a tunnel or underground parking. GPS satellite signal drops. FusedLocation falls back to Wi-Fi/cell positioning β€” accuracy degrades to 50–200 m. Updates may stop entirely if the driver is deep underground.

The fix: FusedLocation handles the hardware fallback automatically, but you need to surface accuracy to the UI. Check location.accuracy on each callback β€” if it exceeds 100 m, show an accuracy ring around the marker (a semi-transparent circle proportional to accuracy radius). On the passenger side: if no WS message arrives for >10 s, grey out the marker and show "Weak signal β€” tracking paused". Resume automatically when updates return. Never stop the Foreground Service β€” it will resume once the driver exits the tunnel.

Dead reckoning (staff-level): Extrapolate position between fixes using last known speed and bearing: newLat β‰ˆ lastLat + (speed * cos(bearing) * dt / R). Continue animating the marker on the estimated trajectory rather than freezing it.

πŸ”Œ
WebSocket Disconnect During Active Tracking

The problem: The passenger's WebSocket drops mid-trip β€” network switch, server restart, or brief connectivity loss. The marker freezes. The passenger sees stale data with no indication anything is wrong.

The fix: In onFailure(), immediately emit DriverState.Disconnected(attempt = n). UI shows a "Reconnecting..." banner and greys the marker. Reconnect with exponential backoff: delay = min(base * 2^attempt, 30_000) + jitter. Also listen to ConnectivityManager.NetworkCallback.onAvailable() to trigger immediate reconnect when network returns β€” don't wait for the backoff timer. On reconnect, request a fresh server snapshot (last position + ETA) before resuming streaming. Cap at 10 retries then show a manual "Retry" button.

πŸ’€
Foreground Service Killed by OS / Doze Mode

The problem: Android's Low Memory Killer or Doze mode kills the driver's Foreground Service during a long shift. Location uploads stop. The passenger's tracker goes stale.

The fix: Return START_STICKY from onStartCommand() β€” the OS restarts the service with a null Intent when resources free up. The service rebuilds its LocationRequest and resumes uploading. For Doze: Foreground Services are exempt as long as the notification is visible β€” this is why the persistent notification is mandatory, not optional. Additionally, guide the driver to whitelist the app from battery optimisation via Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS), explaining the reason clearly. Don't use PendingIntent for the driver upload path β€” that's for low-frequency geofencing, not 2 s uploads.

πŸŒ€
Rapid Updates Cause Animation Queue Backlog

The problem: WebSocket messages arrive faster than the 1 s animation duration β€” highway driving, server sends updates every 500 ms. Animations queue up, causing the marker to lag further and further behind the driver's actual position, eventually showing a position that's 5–10 seconds stale.

The fix: In MarkerAnimator.animateTo(), always call activeAnimator?.cancel() before starting a new animation. Cancelling mid-animation leaves the marker at its current interpolated position and immediately starts the new one from there β€” no queue, no lag. The marker catches up instantly. Also make animation duration adaptive: if updates arrive every 500 ms, use 500 ms duration rather than 1 000 ms β€” match the animation to the update frequency.

πŸ“΅
Location Permission Revoked Mid-Session

The problem: Android 12 introduced one-time location grants. The user granted "Only this time" β€” when they background the app and return, location access has expired. Or the user goes to Settings and revokes the permission during an active trip.

The fix: FusedLocation stops calling your LocationCallback silently when permission is revoked β€” no exception, just silence. Detect this: if no update arrives within 2Γ— the expected interval, check ContextCompat.checkSelfPermission(). If revoked, emit a PermissionRevoked state and show a non-dismissable dialog explaining the impact, with a direct link to Settings. On the passenger side, if the server stops receiving driver updates it can push a separate "tracking unavailable" WS message.

πŸ“
GPS Jitter When Driver is Stationary

The problem: The driver stops at a red light. GPS chipsets oscillate Β±5–15 m β€” generating constant callbacks even though the car hasn't moved. Without filtering this generates hundreds of unnecessary server uploads per minute and causes the marker to jitter nervously on the passenger's screen.

The fix: Use setMinUpdateDistanceMeters(5f) in LocationRequest.Builder β€” FusedLocation suppresses callbacks if the device has moved less than 5 m since the last update. Additionally check location.speed β€” if speed is 0 m/s, skip the WebSocket upload even if a callback fires. On the animation side: if the new position is within 3 m of the current marker position, skip the ValueAnimator entirely. Combined effect: zero uploads and zero animation churn during red lights.

πŸ—ΊοΈ
Map Tiles Unavailable Offline

The problem: The driver enters an area with no connectivity. The map SDK can't fetch tiles β€” large grey squares appear. The route polyline still displays (it's vector data in memory), but the blank background map makes navigation confusing.

The fix: (1) Use the Maps SDK's built-in offline area support β€” let the user pre-download a region before the trip. (2) Implement a custom TileProvider: override getTile(x, y, zoom), check a local LRU disk cache, return cached bytes if found, otherwise fetch and cache. Pre-download tiles for the route corridor at trip start: compute the route polyline's bounding box, determine tile coordinates at zoom 14–16, and fetch them via WorkManager before the driver departs.

πŸ”„
Config Change / Process Death Mid-Trip

The problem: The passenger rotates their phone or the process is killed under low RAM. The tracking Fragment is recreated. Naively this re-fetches everything, flashes a spinner, and resets the camera.

The fix (config change): The LocationViewModel survives rotation. The WebSocket lives inside the ViewModel and stays connected. The Fragment re-collects the existing StateFlow β€” the last emitted DriverState is replayed immediately (StateFlow always emits current value to new collectors). The marker and camera restore from the last state in under 100 ms.

The fix (process death): The Foreground Service is independent of the Fragment β€” it keeps running. When the passenger app restarts, the ViewModel reconnects to the WebSocket and receives the server's fresh snapshot within one round-trip. Use SavedStateHandle to persist the tripId so the ViewModel reconnects to the correct trip.

Expected at Each Level
πŸ”΅ Mid-Level
  • Knows FusedLocationProviderClient vs raw LocationManager
  • Can set up LocationRequest with interval and priority
  • Understands foreground/background permission difference
  • Can add a marker and draw a polyline with the Maps SDK
  • Knows a Foreground Service is required for background GPS
  • Uses animateCamera() to move the map view
🟒 Senior
  • Implements ValueAnimator LatLng lerp for smooth marker movement
  • Handles bearing wrap-around (359Β° β†’ 1Β°) in animation
  • Cancels active animator to prevent queue backlog on rapid updates
  • CameraTracker: auto-follow disabled on user pan gesture
  • Polyline trimming β€” removes travelled portion as driver moves
  • GeofencingClient for arrival alerts without polling
  • Adapts priority/interval by TripState to save battery
  • Exponential backoff WebSocket reconnection
🟣 Staff+
  • Dead reckoning: extrapolate marker between fixes (Kalman filter)
  • Adaptive animation duration matching update frequency
  • Custom TileProvider + MBTiles for offline map tiles
  • Location spoof detection: isMock, velocity sanity, sensor fusion
  • ClusterManager for many driver markers at city scale
  • Accuracy ring overlay β€” surfaces GPS uncertainty visually
  • Speed-adaptive GPS interval (faster updates on highways)
  • Battery impact metering across driver shift with Battery Historian
Interview Q&A (20 Questions)
Q1EasyWhy use FusedLocationProviderClient instead of GPS directly?
β–Ύ

FusedLocationProviderClient combines GPS, Wi-Fi positioning, Bluetooth beacons, and cell towers, choosing the best source for the requested accuracy/battery trade-off. Raw LocationManager with GPS_PROVIDER only uses satellites β€” cold start takes 30–60 s, fails indoors, and drains battery constantly. FLPC gets a fix in ~1–2 s by using the last known Wi-Fi position immediately while GPS warms up. For consumer apps, always use FLPC over raw LocationManager.

Q2EasyWhat is the difference between PRIORITY_HIGH_ACCURACY and PRIORITY_BALANCED_POWER?
β–Ύ

HIGH_ACCURACY activates the GPS hardware β€” 3–5 m accuracy but significant battery cost. Use it during active trips. BALANCED_POWER uses Wi-Fi and cell towers only β€” no GPS chip activated, 50–100 m accuracy, minimal battery. Use it for the passenger's own blue dot, or the driver app during IDLE/SEARCHING state. Only activate HIGH_ACCURACY when the driver is actively on a trip.

Q3MediumWhy does a driver app need a Foreground Service for location?
β–Ύ

Since Android 8, location updates in a background service are throttled to once per hour. A Foreground Service with a visible notification has much higher OS priority and receives continuous updates. Declare android:foregroundServiceType="location" in the manifest (required since Android 10), and call startForeground(id, notification) within 5 seconds of service start. Return START_STICKY so the OS restarts the service if killed under memory pressure.

Q4MediumHow do you smoothly animate a marker between GPS fixes?
β–Ύ

Use ValueAnimator.ofFloat(0f, 1f) with duration matching the update interval (1 000 ms). In addUpdateListener, linearly interpolate lat/lng: lat = startLat + (endLat - startLat) * t. Also interpolate bearing β€” handle the 359Β° β†’ 1Β° wrap: compute delta, if >180 subtract 360, if <-180 add 360. Crucially, cancel any active animation before starting a new one (activeAnimator?.cancel()) to prevent backlog when updates arrive faster than the animation completes.

Q5MediumWhat is ACCESS_BACKGROUND_LOCATION and why is it separate?
β–Ύ

Introduced in Android 10, it's a separate runtime permission from ACCESS_FINE_LOCATION, granting location access when the app is not visible. You must request FINE first; only after that can you request BACKGROUND. On Android 11+, you can't request both in the same dialog β€” the system forces the user to Settings. The Play Store also requires justification. For driver apps, explain the benefit clearly before requesting.

Q6MediumHow do you detect when a user manually pans the map and stop auto-following?
β–Ύ

Register GoogleMap.setOnCameraMoveStartedListener. The callback receives a reason: REASON_GESTURE means the user is panning β€” set isFollowing = false. REASON_API_ANIMATION means your own code called animateCamera() β€” don't change the flag. When isFollowing is false, skip animateCamera() but still animate the marker. Show a "Re-center" FAB that sets isFollowing = true and snaps back to the driver.

Q7MediumHow does GeofencingClient work and why is it better than polling?
β–Ύ

You register a Geofence with a circular region and transition types. Play Services monitors the device position at the OS level using low-power cell/Wi-Fi β€” no GPS chip activated. When a transition is detected, it fires a PendingIntent to your BroadcastReceiver. Your app code doesn't run during monitoring β€” the OS handles everything and only wakes your app on boundary crossing. Far more battery-efficient than polling your own location every few seconds.

Q8EasyHow do you draw and update a route polyline as the driver moves?
β–Ύ

Fetch the route from the Directions API as a list of LatLng waypoints. Draw with map.addPolyline(PolylineOptions().addAll(points)). Store the Polyline reference. As the driver moves, find the closest waypoint to the driver's current position, then set polyline.points = originalPoints.drop(closestIndex). This gives the "road disappearing behind" effect without a full redraw. Don't remove and re-add the polyline on every update β€” updating points in-place avoids a redraw flash.

Q9EasyWhat is setMinUpdateDistanceMeters and why is it useful?
β–Ύ

setMinUpdateDistanceMeters(5f) suppresses callbacks if the device hasn't moved more than 5 m since the last update, even if the time interval has elapsed. This filters GPS jitter when a driver is stationary β€” the chipset oscillates slightly, generating false position updates. Without this filter you'd upload hundreds of tiny "moves" per minute. Combine with the time interval for both time-based and distance-based filtering.

Q10MediumHow do you handle a WebSocket disconnect during a live tracking session?
β–Ύ

On onFailure(), emit DriverState.Disconnected(attempt = n). UI shows "Reconnecting..." and greys the marker. Reconnect with exponential backoff: min(base * 2^attempt, 30_000) + jitter. Listen to ConnectivityManager.NetworkCallback.onAvailable() to trigger immediate reconnect when network returns β€” don't wait for the timer. On reconnect, request a fresh snapshot from the server before resuming streaming. Cap retries at 10, then show a "Retry" button.

Q11HardHow do you show multiple driver markers efficiently?
β–Ύ

Use ClusterManager from Maps Android Utils. It groups nearby markers into a cluster showing the count, splitting on zoom-in. For updates: maintain a Map<driverId, Marker>. On each position update, set marker.position = newLatLng directly β€” O(1), preserves cluster grouping. For high volumes (100+ drivers): filter to only markers within the visible map bounds via map.projection.visibleRegion.latLngBounds.contains(pos) before adding to ClusterManager.

Q12HardWhat is dead reckoning and when would you apply it?
β–Ύ

Dead reckoning extrapolates position between GPS fixes using last known speed and bearing: newLat β‰ˆ lastLat + (speed * cos(bearing) * dt / R). If the driver is doing 14 m/s at 90Β° and the GPS fix is 1 s late, you estimate the position and keep the marker moving continuously. A Kalman filter combines these estimates with actual measurements, weighted by uncertainty, to produce a smooth filtered track β€” used in Google Maps navigation. Trade-off: errors compound if speed/bearing change during the gap.

Q13EasyHow do you implement a custom map style (dark mode)?
β–Ύ

Generate a JSON style file at mapstyle.withgoogle.com and save it in res/raw/map_style.json. Apply with: map.setMapStyle(MapStyleOptions.loadRawResourceStyle(context, R.raw.map_style)) inside onMapReady(). For dark mode: detect resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES and load a dark JSON. The style is client-side β€” no server changes. Common use: hide POI labels to reduce visual noise during navigation.

Q14EasyHow do you handle location permission denial and guide the user to Settings?
β–Ύ

Three cases after RequestPermission(): (1) Granted β€” proceed. (2) Denied but not permanently β€” show rationale dialog, re-request. (3) Permanently denied (shouldShowRequestPermissionRationale() returns false) β€” show "Open Settings" dialog launching Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS). Never loop permission requests. Show the feature as disabled with a CTA. For background location on Android 11+, the user is always directed to Settings β€” prepare UI for this flow.

Q15HardHow do you implement offline map tiles?
β–Ύ

Two approaches. (1) Maps SDK offline areas β€” built-in support, user downloads a rectangular region. No custom code needed. (2) Custom TileProvider: implement getTile(x, y, zoom) β€” check a local disk cache (LRU), return cached bytes if found, else fetch and cache. Use MBTiles for structured offline storage. Register with map.addTileOverlay(TileOverlayOptions().tileProvider(yourProvider)). For trip-start pre-caching: compute route bounding box, determine tile coordinates at zoom 14–16, fetch via WorkManager before departure.

Q16EasyWhat is getLastLocation() and when should you use it?
β–Ύ

fusedClient.lastLocation resolves instantly from cache β€” no GPS activation β€” but can be null after a fresh reboot. Use it for: showing an initial map position before live updates start, pre-populating a pickup address field, or a coarse check whether the user is in a service area. Don't use it as the main tracking mechanism β€” it's stale by design. Start requestLocationUpdates() for live data and use lastLocation as a quick first position while the live stream warms up.

Q17HardHow do you reduce battery drain on the driver app during a long shift?
β–Ύ

Multiple strategies together: (1) Trip-state-adaptive priority β€” LOW_POWER when IDLE, HIGH_ACCURACY only during EN_ROUTE. (2) setMinUpdateDistanceMeters(5f) β€” suppresses callbacks when stationary. (3) Client-side filtering before WS send β€” skip upload if speed == 0 or delta < 3 m. (4) Batch uploads β€” buffer 3–5 fixes to reduce radio activations. (5) Guide driver to whitelist the app from battery optimisation via ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS β€” only during active trips. (6) Verify with Battery Historian before shipping.

Q18HardHow would you detect GPS spoofing in a driver app?
β–Ύ

Multiple signals: (1) location.isMock (API 31+) β€” true if a mock location app is active. (2) Cross-check GPS vs network location β€” consistent disagreement >1 km is suspicious. (3) Velocity sanity β€” position jumps 10 km in 1 second is physically impossible. (4) Sensor fusion β€” compare GPS bearing with accelerometer/gyroscope; spoofed GPS that "teleports" won't match inertial data. (5) Check if developer options mock locations is enabled. Flag signals server-side in an anti-fraud system β€” don't block locally.

Q19EasyHow does map.isMyLocationEnabled work and what permission does it need?
β–Ύ

map.isMyLocationEnabled = true shows the built-in blue dot for the user's position, plus a "My Location" button. It requires ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION at runtime β€” enabling it without the permission throws SecurityException. Always check ContextCompat.checkSelfPermission() first. The SDK handles all FusedLocation calls internally. Use it for the passenger's position; use your own marker + FusedLocation for the driver's position that needs server upload and custom animation.

Q20HardHow would you implement ETA updating as the driver deviates from the planned route?
β–Ύ

Two approaches. (1) Server-side (standard): server receives each driver position, queries a routing engine from driver's current position to destination, pushes updated ETA back via WebSocket to the passenger. (2) Client-side: detect deviation by checking if the driver is >N metres off the drawn polyline. If deviation exceeds threshold consistently for 10+ seconds, request a new route from the Directions API, redraw the polyline, and recompute ETA from the new route's duration field. Debounce rerouting β€” don't trigger on every minor deviation. Use server-side in production β€” routing is expensive and best centralised.