Design Maps & Live Location Tracking

1. Understanding the Problem

Design the Android client for a maps and live location tracking feature β€” think an Ola/Uber driver's position updating in real-time on a passenger's screen, or a delivery tracking screen in Swiggy. The core challenges: getting accurate location efficiently without destroying battery, sending/receiving location over a persistent connection, animating a marker smoothly across the map, drawing the route polyline, and handling the complex Android location permission landscape (foreground vs background).

πŸ“Œ Pattern: FusedLocation + WebSocket + Animated Marker

Three technologies work together: FusedLocationProviderClient β€” Google's smart location provider that picks the best source (GPS, Wi-Fi, cell) for the requested accuracy/power trade-off. WebSocket β€” bi-directional persistent connection to stream location updates with low latency. ValueAnimator β€” smoothly interpolates a map marker between GPS fixes so it glides rather than teleports, even when updates arrive every 2–5 seconds.

Learn This Pattern β†’

βœ… Functional Requirements

  • Show user's own location on the map in real time
  • Track a driver/delivery agent's position (received from server)
  • Draw a route polyline from origin to destination
  • Animate the driver marker smoothly between position updates
  • Show ETA and distance, updated as driver moves
  • Geofence: alert when driver enters a radius of the pickup point
  • Location continues updating in background (driver app)

β›” 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/unavailable
  • Map tile loading works offline (cached tiles)
  • Handle camera auto-follow vs user-panned map interaction

2. The Set Up

Location accuracy vs battery β€” choosing the right priority

High Accuracy
PRIORITY_HIGH_ACCURACY
GPS + Wi-Fi + cell. ~3–5 m accuracy. High battery drain. Use for: active navigation, driver GPS upload.
Balanced
PRIORITY_BALANCED_POWER
Wi-Fi + cell only. ~50–100 m accuracy. Moderate battery. Use for: background ETA tracking, geofence monitoring.
Low Power
PRIORITY_LOW_POWER
Cell only. ~1–3 km accuracy. Minimal battery. Use for: city-level awareness, not tracking.

Key Android APIs at a glance

πŸ“
FusedLocationProviderClient
Google Play Services API. Fuses GPS, Wi-Fi, cell for best accuracy/battery ratio. Replaces raw LocationManager for all consumer apps.
πŸ””
LocationCallback + PendingIntent
Foreground: LocationCallback receives updates while service is alive. Background/killed: PendingIntent wakes a BroadcastReceiver.
πŸ—ΊοΈ
Google Maps SDK
GoogleMap API: add/move markers, draw Polyline, control camera with animateCamera(), cluster dense markers with ClusterManager.
πŸ”΄
Foreground Service
Required for driver app to collect location in background. Mandatory persistent notification. serviceType=location in manifest since Android 10.
🎯
GeofencingClient
Register a Geofence with center + radius. OS triggers a PendingIntent when the device enters/exits β€” no polling needed.
🎞️
ValueAnimator
Interpolates LatLng between GPS fixes over animation duration (e.g., 1 s). Updates marker position on each animation frame β€” smooth 60 fps glide.

3. High-Level Design

Architecture Overview β€” Maps & Live Location Tracking
DRIVER APP LocationForeground Service (START_STICKY) FusedLocation HIGH_ACCURACY, 2 s interval LocationRepository upload to server via WS WebSocketManager exponential backoff GeofencingClient pickup radius alert β†’ PendingIntent β†’ notification Backend Server stores latest position fans out to passenger WS computes ETA / polyline PASSENGER APP WebSocketManager receives driver updates LocationViewModel StateFlow<DriverState> MarkerAnimator ValueAnimator LatLng lerp PolylineDrawer route + redraws on update Google Maps SDK (SupportMapFragment) GoogleMap: marker, polyline, camera CameraTracker auto-follow vs user-panned mode FusedLocation (passenger) BALANCED_POWER β€” own location on map push

4. Low-Level Design

FusedLocationProviderClient β€” location request 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 on Android 9+
        startLocationUpdates()
    }

    private fun startLocationUpdates() {
        val request = LocationRequest.Builder(
            Priority.PRIORITY_HIGH_ACCURACY,
            2000L   // intervalMillis β€” target 2 s between fixes
        )
            .setMinUpdateIntervalMillis(1000L)   // fastest if GPS provides faster
            .setMinUpdateDistanceMeters(5f)        // ignore updates < 5 m movement
            .setWaitForAccurateLocation(false)     // don't wait for GPS lock β€” use fused
            .build()

        locationCallback = object : LocationCallback() {
            override fun onLocationResult(result: LocationResult) {
                val location = result.lastLocation ?: return
                // Upload to server via WebSocket
                locationRepository.uploadLocation(
                    lat = location.latitude,
                    lng = location.longitude,
                    bearing = location.bearing,     // heading in degrees β€” for marker rotation
                    accuracy = location.accuracy    // horizontal accuracy in metres
                )
            }
        }

        fusedClient.requestLocationUpdates(request, locationCallback, Looper.getMainLooper())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return START_STICKY   // OS restarts service if killed
    }

    override fun onDestroy() {
        fusedClient.removeLocationUpdates(locationCallback)
        super.onDestroy()
    }
}

Smooth marker animation with ValueAnimator

object MarkerAnimator {

    /**
     * Smoothly moves a marker from its current position to [newPosition]
     * over [durationMs] milliseconds using spherical linear interpolation.
     * Also rotates marker bearing so the car icon faces the direction of travel.
     */
    fun animateTo(
        marker: Marker,
        newPosition: LatLng,
        newBearing: Float,
        durationMs: Long = 1000L
    ) {
        val startLat = marker.position.latitude
        val startLng = marker.position.longitude
        val startBearing = marker.rotation

        ValueAnimator.ofFloat(0f, 1f).apply {
            duration = durationMs
            interpolator = LinearInterpolator()

            addUpdateListener { animator ->
                val t = animator.animatedValue as Float

                // Linearly interpolate lat/lng
                val lat = startLat + (newPosition.latitude  - startLat) * t
                val lng = startLng + (newPosition.longitude - startLng) * t
                marker.position = LatLng(lat, lng)

                // Interpolate bearing β€” handle 359Β° β†’ 1Β° wrap-around
                var delta = newBearing - startBearing
                if (delta > 180) delta -= 360
                if (delta < -180) delta += 360
                marker.rotation = startBearing + delta * t
            }

            start()
        }
    }
}

Drawing and updating a route Polyline

class PolylineDrawer(private val map: GoogleMap) {
    private var polyline: Polyline? = null

    /** Fetches route from Directions API and draws it */
    suspend fun drawRoute(origin: LatLng, destination: LatLng) {
        val points = directionsApi.getRoute(origin, destination)  // list of LatLng
        polyline?.remove()
        polyline = map.addPolyline(
            PolylineOptions()
                .addAll(points)
                .width(8f)
                .color(Color.parseColor("#4285F4"))   // Google blue
                .jointType(JointType.ROUND)
                .startCap(RoundCap())
                .endCap(CustomCap(BitmapDescriptorFactory.fromResource(R.drawable.ic_arrow)))
        )
    }

    /** Trims the polyline up to the driver's current position β€” "travelled" portion disappears */
    fun trimUpTo(driverPos: LatLng) {
        val pts = polyline?.points ?: return
        val closestIdx = pts.indexOfMinBy { distanceBetween(it, driverPos) }
        polyline?.points = pts.drop(closestIdx)
    }
}

Camera auto-follow with user override detection

class CameraTracker(private val map: GoogleMap) {

    private var isFollowing = true

    init {
        // If user manually pans the map, disable auto-follow
        map.setOnCameraMoveStartedListener { reason ->
            if (reason == GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE) {
                isFollowing = false
            }
        }
    }

    /** Called every time a new driver position arrives */
    fun onDriverMoved(newPos: LatLng, bearing: Float) {
        if (!isFollowing) return
        val camPos = CameraPosition.Builder()
            .target(newPos)
            .zoom(17f)
            .bearing(bearing)      // rotate map to face direction of travel
            .tilt(45f)             // 3D tilt for navigation feel
            .build()
        map.animateCamera(CameraUpdateFactory.newCameraPosition(camPos), 600, null)
    }

    /** "Re-center" button taps re-enable following */
    fun resumeFollowing() { isFollowing = true }
}

Geofencing β€” arrival alert at pickup point

class GeofenceManager(private val context: Context) {

    private val client = LocationServices.getGeofencingClient(context)

    fun addPickupGeofence(pickupLat: Double, pickupLng: Double, radiusMeters: Float = 200f) {
        val geofence = Geofence.Builder()
            .setRequestId("PICKUP_ZONE")
            .setCircularRegion(pickupLat, pickupLng, radiusMeters)
            .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
            .setExpirationDuration(Geofence.NEVER_EXPIRE)
            .build()

        val request = GeofencingRequest.Builder()
            .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
            .addGeofence(geofence)
            .build()

        val pendingIntent = getPendingIntent()   // points to GeofenceBroadcastReceiver
        client.addGeofences(request, pendingIntent)
    }

    fun removePickupGeofence() = client.removeGeofences(listOf("PICKUP_ZONE"))
}

// BroadcastReceiver β€” fires when driver enters the 200 m pickup circle
class GeofenceBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val event = GeofencingEvent.fromIntent(intent) ?: return
        if (event.geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
            // Show "Your driver is nearby!" notification
            showDriverNearbyNotification(context)
        }
    }
}

LLD Whiteboard

LLD β€” Driver Location Update Flow (Passenger Side)
WebSocket driver position msg LocationViewModel emits DriverState MarkerAnimator ValueAnimator lerp PolylineDrawer trimUpTo(driverPos) GoogleMap marker + polyline + cam CameraTracker animateCamera() TrackingFragment collectLatestLifecycle
Flow 1 β€” Passenger Opens Tracking Screen
Fragment / UI LocationViewModel WebSocketManager MarkerAnimator / Map FusedLocation
1.TrackingFragment appears. onViewCreated() calls viewModel.startTracking(tripId) Opens WebSocket connection to wss://api/trip/{tripId}/track β€” β€” β€”
β€” β€” 2.WebSocket connects. Server sends initial driver snapshot: {lat, lng, bearing, eta} β€” β€”
β€” 3.Emits DriverState.Located(pos, bearing) via StateFlow β€” β€” β€”
4.Fragment collects StateFlow. Calls markerAnimator.animateTo() and cameraTracker.onDriverMoved() β€” β€” Marker appears at initial position. Camera animates to driver with tilt + bearing. Passenger's own position shown as 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 App / Server WebSocket (Passenger) LocationViewModel MarkerAnimator GoogleMap
1.Driver FusedLocation fires. Driver app sends {lat, lng, bearing} to server via WebSocket every 2 s β€” β€” β€” β€”
Server fans out to all passengers watching this tripId 2.Passenger WebSocket receives onMessage(json). Parses DriverLocationUpdate. β€” β€” β€”
β€” β€” 3.ViewModel emits new DriverState.Located(newLatLng, newBearing, etaMinutes) β€” β€”
β€” β€” β€” 4.ValueAnimator lerps marker from old pos to new pos over 1 000 ms. Bearing rotates smoothly. Runs at 60 fps. Marker glides on map. ETA label in UI updates.
β€” β€” β€” 5.polylineDrawer.trimUpTo(driverPos) β€” removes the already-travelled portion of the polyline Polyline shrinks from the origin end toward driver.
Flow 3 β€” Driver Enters Pickup Geofence
FusedLocation / OS GeofencingClient GeofenceBroadcastRx NotificationManager UI / Fragment
1.OS continuously monitors driver position against registered geofences (low-power, handled by system) β€” β€” β€” β€”
β€” 2.Driver crosses 200 m radius around pickup point. OS triggers the registered PendingIntent β€” β€” β€”
β€” β€” 3.onReceive() called. GeofencingEvent.fromIntent() confirms ENTER transition for "PICKUP_ZONE" β€” β€”
β€” β€” 4.Removes geofence (no longer needed). Posts "Driver is 200 m away β€” head to pickup" notification. Notification fires with sound + vibration on CH_DRIVER_NEARBY β€”
β€” β€” β€” β€” 5.If app is in foreground: LiveData / StateFlow emits DriverState.Nearby β†’ UI shows "Driver arriving!" banner

5. Deep Dives

Location permission landscape β€” Android 10+ complexity

Use Case Required Permission When Prompted Notes
User's location on map (passenger) ACCESS_FINE_LOCATION Runtime β€” app open Shown once. Needed for PRIORITY_HIGH_ACCURACY.
Driver location in background ACCESS_BACKGROUND_LOCATION Separate dialog on Android 10+ β€” taken to Settings on 11+ Must request FINE first, then background separately. Most critical.
Geofencing ACCESS_FINE_LOCATION + ACCESS_BACKGROUND_LOCATION Same as above Geofences fire even when app is killed β€” needs background permission.
Foreground Service location FOREGROUND_SERVICE_LOCATION (Android 14+) Declared in manifest β€” no dialog New in API 34. Must add android:foregroundServiceType="location".

Battery optimisation for the driver app

// Switch accuracy based on trip state
fun updateLocationPriority(tripState: TripState) {
    val (priority, intervalMs) = when (tripState) {
        TripState.IDLE        -> Priority.PRIORITY_LOW_POWER          to 60_000L  // 1/min
        TripState.SEARCHING  -> Priority.PRIORITY_BALANCED_POWER       to 10_000L  // 1/10s
        TripState.EN_ROUTE   -> Priority.PRIORITY_HIGH_ACCURACY         to 2_000L   // 1/2s
        TripState.COMPLETED  -> Priority.PRIORITY_LOW_POWER          to 60_000L
    }
    // Re-register LocationRequest with new parameters
    fusedClient.removeLocationUpdates(locationCallback)
    fusedClient.requestLocationUpdates(buildRequest(priority, intervalMs), locationCallback, looper)
}

// setMinUpdateDistanceMeters filters out noise when stationary
// β€” if driver is stopped at a red light, identical GPS fixes aren't uploaded

6. 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
  • Uses setMinUpdateDistanceMeters to filter stationary noise
  • 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 trip state to save battery
🟣 Staff+
  • Dead reckoning: extrapolate marker position between fixes (Kalman filter)
  • Map clustering with ClusterManager for many driver markers
  • Custom tile provider for offline map tile caching
  • Speed-adaptive interval: increase GPS rate when driver is moving fast
  • Location spoof detection: compare GPS vs network vs sensor fusion
  • Multi-stop route with ordered waypoints and live rerouting
  • Battery impact dashboard: alert if driver app consumes > X % per hour

7. Interview Q&A (20 Questions)

Q1. Why use FusedLocationProviderClient instead of GPS directly?
Easyβ–Ύ

FusedLocationProviderClient (FLPC) is Google's smart location provider that internally combines GPS, Wi-Fi positioning, Bluetooth beacons, and cell towers, choosing the best available source for the requested accuracy and battery trade-off. Raw LocationManager with GPS_PROVIDER only uses satellites β€” it's slow to get a fix (cold start can take 30–60 s), drains battery constantly, and fails indoors. FLPC gets a fix in ~1–2 s because it uses the last known location from Wi-Fi as an immediate result while GPS warms up. For consumer apps, always use FLPC over raw LocationManager.

Q2. What is the difference between PRIORITY_HIGH_ACCURACY and PRIORITY_BALANCED_POWER?
Easyβ–Ύ

PRIORITY_HIGH_ACCURACY enables GPS hardware β€” it can achieve 3–5 metre accuracy but uses significant battery. Use it when precise turn-by-turn navigation or real-time driver tracking is needed (active trip). PRIORITY_BALANCED_POWER uses Wi-Fi and cell towers only β€” no GPS chip activated. Accuracy degrades to 50–100 metres but battery impact is minimal. Use it when you just need city-block precision, e.g., checking if the driver is in the general area. For the passenger app just showing their own location as a blue dot, BALANCED is sufficient. Only activate HIGH_ACCURACY when the user is actively navigating.

Q3. Why does a driver app need a Foreground Service for location?
Mediumβ–Ύ

Android aggressively kills background processes to conserve battery. A plain background service will be killed within minutes. A Foreground Service with a persistent notification signals to the OS that this is intentional, user-visible, ongoing work β€” it has much higher priority and is rarely killed. Since Android 8, location updates in a background service (no foreground promotion) are throttled to once per hour. For real-time driver tracking at 2 s intervals, a Foreground Service is mandatory. Declare android:foregroundServiceType="location" in the manifest (required since Android 10) and call startForeground(id, notification) within 5 seconds of service start.

Q4. How do you smoothly animate a marker between GPS fixes?
Mediumβ–Ύ

GPS fixes arrive at 1–5 second intervals. Without animation, the marker teleports β€” jarring UX. Use ValueAnimator.ofFloat(0f, 1f) with the animation duration matching the update interval (e.g., 1 000 ms). In addUpdateListener, linearly interpolate the latitude and longitude: lat = startLat + (endLat - startLat) * t. Update marker.position on each frame. Also interpolate marker.rotation for the bearing β€” but handle the 359Β° β†’ 1Β° wrap: compute the delta, if >180 subtract 360, if <-180 add 360. The LinearInterpolator gives a constant-speed glide that matches expected car movement.

Q5. What is ACCESS_BACKGROUND_LOCATION and why is it separate?
Mediumβ–Ύ

Introduced in Android 10, ACCESS_BACKGROUND_LOCATION is a separate runtime permission from ACCESS_FINE_LOCATION. It grants the app the ability to receive location updates when the app is not visible to the user. You must request FINE first, and only after it's granted can you request BACKGROUND. On Android 11+, requesting both in the same dialog is not allowed β€” the system forces the user to go to Settings to grant background location (no runtime dialog). The Play Store also reviews background location use and requires a compelling justification. For driver apps, the justification is clear: the driver's location must update while they navigate. Always use it minimally and explain the benefit to the user before requesting.

Q6. How do you detect when a user manually pans the map and stop auto-following?
Mediumβ–Ύ

Register GoogleMap.setOnCameraMoveStartedListener. The callback receives a reason integer. REASON_GESTURE means the user is physically panning/zooming β€” set a isFollowing = false flag. REASON_API_ANIMATION means your own code called animateCamera() β€” don't change the flag. When a new driver position arrives: if isFollowing is false, skip animateCamera() but still animate the marker. Show a "Re-center" FAB button that sets isFollowing = true and calls animateCamera() to snap back to the driver. This pattern matches the Uber/Ola UX exactly.

Q7. How does the GeofencingClient work and why is it better than polling?
Mediumβ–Ύ

You register a Geofence with a circular region (center + radius) and transition types (ENTER/EXIT/DWELL). The GeofencingClient passes this to Google Play Services, which monitors the device's position against all registered geofences at the OS level β€” using low-power cell/Wi-Fi positioning, not GPS. When a transition is detected, it fires a PendingIntent to your BroadcastReceiver or IntentService. This is far more battery-efficient than polling: your app code doesn't run at all during monitoring. The OS handles everything and only wakes your app when the boundary is crossed. Remove the geofence immediately after the event to avoid lingering monitoring.

Q8. How do you draw and update a route polyline as the driver moves?
Easyβ–Ύ

Fetch the route from the Google Directions API (or a Routes API equivalent) as a list of LatLng waypoints. Draw it with map.addPolyline(PolylineOptions().addAll(points)). Store the Polyline reference. As the driver moves, call polyline.remove() and re-draw with the updated points β€” or more efficiently: trim the waypoints list by removing all points before the driver's current position. Find the closest point in the list to the driver's LatLng, then call polyline.points = originalPoints.drop(closestIndex). This gives the appearance of the road "disappearing behind" the driver without a full redraw.

Q9. What is setMinUpdateDistanceMeters and why is it useful?
Easyβ–Ύ

setMinUpdateDistanceMeters(5f) tells FusedLocation to suppress callbacks if the device has moved less than 5 metres since the last update, even if the time interval has elapsed. This filters out GPS jitter β€” when a driver is stationary (waiting at a signal), the GPS chipset still oscillates slightly, generating false position updates. Without this filter, you'd upload hundreds of tiny "moves" per minute to your backend, burning bandwidth and battery. Setting a threshold of 5–10 metres ensures only meaningful movement triggers a callback and a server upload. Combine with the time interval to get both time-based and distance-based filtering.

Q10. How do you handle a WebSocket disconnect during a live tracking session?
Mediumβ–Ύ

Implement exponential backoff in the WebSocket manager: on onFailure() or onClosing(), schedule a reconnect after min(baseDelay * 2^attempt, maxDelay) milliseconds, with jitter (e.g., add a random 0–1 s). Show a UI indicator ("Reconnecting...") while disconnected. Stop showing the driver marker moving β€” optionally grey it out. On reconnection, request a fresh snapshot from the server (last known position + ETA). Cap the retry counter; after N consecutive failures, show "Unable to track β€” check connection" and offer a manual retry button. On Android, also listen to ConnectivityManager network callbacks to trigger reconnection immediately when the network comes back.

Q11. How do you show multiple driver markers efficiently (e.g., available taxis near me)?
Hardβ–Ύ

Use ClusterManager from the Maps Android Utils library. It groups nearby markers into a single cluster marker showing the count, and splits them apart as the user zooms in. For updating positions: maintain a Map<driverId, Marker>. On each position update, call marker.position = newLatLng directly rather than removing and re-adding β€” this is O(1) and preserves the cluster grouping. For very high volumes (100+ drivers), consider only rendering drivers within the visible map bounds: use map.projection.visibleRegion.latLngBounds.contains(driverPos) to filter before adding to ClusterManager. Off-screen markers waste GPU memory.

Q12. What is dead reckoning and when would you apply it?
Hardβ–Ύ

Dead reckoning extrapolates the driver's position between GPS fixes using last known speed and bearing. If a fix arrives at time T with speed 14 m/s and bearing 90Β°, and the next fix is delayed by 1 second, you can estimate the position as newLat β‰ˆ lastLat + (speed * cos(bearing) * dt / R). This makes the marker move continuously rather than jumping in steps. A Kalman filter combines dead reckoning estimates with actual GPS measurements, weighing each by its uncertainty, to produce a smooth, filtered track. Used in Google Maps navigation to keep the blue dot moving smoothly between 1 s GPS fixes. For an interview, describe the concept and the trade-off: extra computation for smoother UX.

Q13. How do you implement a custom map style (dark mode / branded)?
Easyβ–Ύ

Use the Google Maps SDK's MapStyleOptions. Create a JSON style file in res/raw/map_style.json (generate it at mapstyle.withgoogle.com). Apply it with: map.setMapStyle(MapStyleOptions.loadRawResourceStyle(context, R.raw.map_style)). Call this inside onMapReady() after you have the GoogleMap reference. For dark mode: detect resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES and load a dark style JSON. The style is client-side β€” no server changes needed. Commonly used: hide POI labels to reduce visual noise during navigation, change road colors to brand palette.

Q14. How do you handle location permission denial and guide the user to Settings?
Easyβ–Ύ

Three cases after ActivityResultContracts.RequestPermission(): (1) Granted β€” proceed normally. (2) Denied but not permanently β€” show a rationale dialog explaining why location is needed ("We need location to show your position on the map"), then re-request. (3) Permanently denied (shouldShowRequestPermissionRationale() returns false) β€” show a dialog with "Open Settings" that launches Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) so the user can manually enable it. Never request the permission in a loop. Show the feature as disabled with a CTA rather than crashing. For background location on Android 11+, the user is always directed to Settings β€” prepare UI for this flow.

Q15. How do you implement offline map tiles?
Hardβ–Ύ

Two approaches. (1) Google Maps SDK offline areas: the SDK itself supports caching tiles for a selected geographic region β€” the user downloads a rectangular area in advance. This works automatically without custom code. (2) Custom TileProvider: implement TileProvider.getTile(x, y, zoom) β€” check a local tile cache (LRU disk cache), return cached bytes if present, else fetch from tile server and cache. Use MBTiles format for structured offline storage. Register with map.addTileOverlay(TileOverlayOptions().tileProvider(yourProvider)). For navigation apps: pre-download tiles for the route corridor ahead of time using the route bounds to determine which tiles to cache.

Q16. What is the Maps SDK's getLastLocation() and when should you use it?
Easyβ–Ύ

fusedClient.lastLocation returns a Task that resolves with the most recently known location from FusedLocation's cache β€” it's instant (no GPS activation) but can be null if no location has been acquired recently (fresh reboot, no recent app used location). Use it for: showing an initial map position before live updates start ("Where was I last?"), pre-populating a pickup address field, or a coarse check to see if 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 only as a quick first position while the live stream warms up.

Q17. How do you reduce battery drain on the driver app during a long shift?
Hardβ–Ύ

Several strategies together: (1) Adaptive interval: reduce frequency when stationary (setMinUpdateDistanceMeters) and increase only when moving fast. (2) Trip-state-based priority: drop to PRIORITY_BALANCED_POWER when idle, only switch to HIGH_ACCURACY during an active trip. (3) Server-side filtering: send location only when delta exceeds a threshold β€” client-side minUpdateDistanceMeters does some of this, but explicit client filtering before the WebSocket send adds another layer. (4) Batch uploads: if the server allows it, buffer 3–5 fixes locally and send as a batch β€” fewer network radio activations. (5) Request the driver's device to be excluded from Doze mode only during active trips via PowerManager.isIgnoringBatteryOptimizations() and guide the user to whitelist the app.

Q18. How would you detect GPS spoofing in a driver app?
Hardβ–Ύ

Multiple signals. (1) location.isMock (API 31+) / location.isFromMockProvider() (deprecated but works pre-31) β€” returns true if a mock location app is active. (2) Cross-check GPS location against network/cell location: if they consistently disagree by >1 km, flag as suspicious. (3) Velocity sanity check: if the position jumps 10 km in 1 second, that's physically impossible. (4) Sensor fusion: compare GPS bearing with accelerometer/gyroscope data β€” a spoofed GPS that "teleports" won't match inertial sensor data. (5) Check if developer options "Allow mock locations" is enabled: Settings.Secure.getInt(cr, Settings.Secure.ALLOW_MOCK_LOCATION). Flag these signals and escalate to a server-side anti-fraud system rather than blocking locally β€” the driver could appeal if flagged incorrectly.

Q19. How does map.isMyLocationEnabled work and what permission does it need?
Easyβ–Ύ

map.isMyLocationEnabled = true enables the built-in blue dot showing the user's current position, plus a "My Location" button in the top-right corner. It requires ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION to be granted at runtime β€” if you enable it without the permission, it throws a SecurityException. Always check ContextCompat.checkSelfPermission() before setting it to true. The SDK handles all the FusedLocation calls internally β€” you don't need to manually manage your own location updates for the blue dot. Use it for the passenger's position; use your own marker + FusedLocation for the driver's position that needs server upload and custom animation.

Q20. How would you implement ETA updating as the driver deviates from the planned route?
Hardβ–Ύ

Two approaches. (1) Server-side: the server receives each driver position update and computes remaining distance using the Directions API or a routing engine (OSRM). It pushes the updated ETA back via WebSocket to the passenger. This is the standard approach β€” routing is expensive and best done server-side. (2) Client-side: the passenger app detects deviation by checking if the driver's position is >N metres off the drawn polyline. If deviated beyond a threshold, request a new route from the Directions API from driver's current position to destination, redraw the polyline, and recompute ETA from the new route's duration field. Debounce rerouting β€” don't trigger on every minor deviation, wait until the driver is consistently off-route for 10+ seconds.