androidengineers.Book a session

Designing a Ride-Sharing App

Location Strategies & Battery Trade-offs

article25 minHard

Location is one of the most battery-intensive operations on Android. Choosing the right location provider, update interval, and priority directly determines whether your app drains a user's battery in an hour or runs comfortably all day.

Location Providers

ProviderAccuracyBatteryLatency
GPS (PRIORITY_HIGH_ACCURACY)3–10mHigh30–60s cold start
Network + GPS fusion10–50mMedium1–5s
Network only (PRIORITY_BALANCED_POWER)100–1000mLow~1s
Passive (PRIORITY_PASSIVE)VariesMinimalWhenever another app gets a fix

Fused Location Provider

FusedLocationProviderClient is the recommended API — it automatically selects the best source:

class LocationManager(private val context: Context) {
    private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)

    // Get last known location (no GPS activation — minimal battery)
    suspend fun getLastLocation(): Location? {
        return if (ContextCompat.checkSelfPermission(
                context, Manifest.permission.ACCESS_FINE_LOCATION
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            fusedLocationClient.lastLocation.await()
        } else null
    }

    // Continuous updates — for navigation or active tracking
    fun requestContinuousUpdates(callback: (Location) -> Unit): LocationCallback {
        val request = LocationRequest.Builder(
            Priority.PRIORITY_HIGH_ACCURACY,    // GPS + network fusion
            5_000L                              // update interval: 5 seconds
        )
        .setMinUpdateDistanceMeters(10f)        // only update if moved >10m
        .setMinUpdateIntervalMillis(2_000L)     // fastest possible update
        .build()

        val locationCallback = object : LocationCallback() {
            override fun onLocationResult(result: LocationResult) {
                result.lastLocation?.let(callback)
            }
        }

        fusedLocationClient.requestLocationUpdates(
            request, locationCallback, Looper.getMainLooper()
        )

        return locationCallback
    }

    fun stopUpdates(callback: LocationCallback) {
        fusedLocationClient.removeLocationUpdates(callback)
    }
}

Strategy Selection by Use Case

fun buildLocationRequest(strategy: LocationStrategy): LocationRequest {
    return when (strategy) {
        // Navigation: high accuracy, frequent updates
        LocationStrategy.NAVIGATION -> LocationRequest.Builder(
            Priority.PRIORITY_HIGH_ACCURACY, 1_000L  // 1s interval
        ).setMinUpdateDistanceMeters(2f).build()

        // Ride tracking (driver): medium accuracy, less frequent
        LocationStrategy.RIDE_TRACKING -> LocationRequest.Builder(
            Priority.PRIORITY_BALANCED_POWER, 5_000L  // 5s interval
        ).setMinUpdateDistanceMeters(10f).build()

        // Background geofencing check: passive + low accuracy
        LocationStrategy.BACKGROUND -> LocationRequest.Builder(
            Priority.PRIORITY_LOW_POWER, 60_000L  // 1 min interval
        ).setMinUpdateDistanceMeters(100f).build()

        // One-time city-level location
        LocationStrategy.COARSE -> LocationRequest.Builder(
            Priority.PRIORITY_PASSIVE, Long.MAX_VALUE
        ).setMaxUpdates(1).build()
    }
}

enum class LocationStrategy { NAVIGATION, RIDE_TRACKING, BACKGROUND, COARSE }

Permission Handling

class LocationPermissionHelper(private val activity: ComponentActivity) {
    private val permissionLauncher = activity.registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        when {
            permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true ->
                onFineLocationGranted()
            permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true ->
                onCoarseLocationGranted()
            else ->
                onLocationDenied()
        }
    }

    fun requestForegroundLocation() {
        permissionLauncher.launch(arrayOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
        ))
    }

    // Background location requires explicit separate prompt and user going to settings on API 30+
    fun requestBackgroundLocation() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            permissionLauncher.launch(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
        }
    }
}

Battery Optimization Rules

// Stop updates when app goes to background (if not needed there)
lifecycle.addObserver(object : DefaultLifecycleObserver {
    override fun onStop(owner: LifecycleOwner) {
        locationManager.stopUpdates(locationCallback)
    }
    override fun onStart(owner: LifecycleOwner) {
        locationManager.requestContinuousUpdates { location -> handleLocation(location) }
    }
})

Key Takeaways

StrategyBattery costUse for
PRIORITY_HIGH_ACCURACYHighActive navigation, real-time tracking
PRIORITY_BALANCED_POWERMediumBackground periodic checks
PRIORITY_LOW_POWERLowCity-level background location
lastLocationMinimalOne-time lookup; show map center
setMinUpdateDistanceMetersSaves batteryDon't update if user hasn't moved
Stop in backgroundCriticalAlways stop when not needed

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Location Strategies & Battery Trade-offs | Android System Design | Android Engineers