androidengineers.Book a session

Designing a Ride-Sharing App

Geofencing & Background Limits

article20 minHard

Geofencing triggers an event when a device enters or exits a geographic area. It's used for location-based reminders, delivery arrival detection, and proximity alerts. Android's background execution limits add significant complexity.

Geofencing API

class GeofenceManager(private val context: Context) {
    private val geofencingClient = LocationServices.getGeofencingClient(context)

    fun addGeofence(
        id: String,
        latitude: Double,
        longitude: Double,
        radiusMeters: Float = 100f
    ) {
        val geofence = Geofence.Builder()
            .setRequestId(id)
            .setCircularRegion(latitude, longitude, radiusMeters)
            .setExpirationDuration(Geofence.NEVER_EXPIRE)
            .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
            .setLoiteringDelay(30_000)  // 30s dwell before firing DWELL transition
            .build()

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

        // PendingIntent to handle the transition
        val pendingIntent = PendingIntent.getBroadcast(
            context,
            id.hashCode(),
            Intent(context, GeofenceBroadcastReceiver::class.java).apply {
                putExtra("geofence_id", id)
            },
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
        )

        if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
            == PackageManager.PERMISSION_GRANTED
        ) {
            geofencingClient.addGeofences(request, pendingIntent)
        }
    }

    fun removeGeofence(id: String) {
        geofencingClient.removeGeofences(listOf(id))
    }
}

Receiving Transitions

class GeofenceBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val geofencingEvent = GeofencingEvent.fromIntent(intent) ?: return

        if (geofencingEvent.hasError()) {
            Timber.e("Geofencing error: ${geofencingEvent.errorCode}")
            return
        }

        val transition = geofencingEvent.geofenceTransition
        val triggeringGeofences = geofencingEvent.triggeringGeofences ?: return

        when (transition) {
            Geofence.GEOFENCE_TRANSITION_ENTER -> {
                triggeringGeofences.forEach { geofence ->
                    onGeofenceEntered(context, geofence.requestId)
                }
            }
            Geofence.GEOFENCE_TRANSITION_EXIT -> {
                triggeringGeofences.forEach { geofence ->
                    onGeofenceExited(context, geofence.requestId)
                }
            }
        }
    }

    private fun onGeofenceEntered(context: Context, geofenceId: String) {
        // Use WorkManager to do any work (network calls, notifications)
        // BroadcastReceiver must return quickly; don't do network calls here
        val work = OneTimeWorkRequestBuilder<GeofenceArrivalWorker>()
            .setInputData(workDataOf("geofence_id" to geofenceId))
            .build()
        WorkManager.getInstance(context).enqueue(work)
    }
}

Android Background Limits: The Challenge

Android imposes strict limits on background location access:

API LevelRestriction
API 26+Background services killed within minutes
API 29+ACCESS_BACKGROUND_LOCATION permission required for background access
API 31+Exact alarm restrictions
Doze modeLocation updates suspended during idle periods

What still works in background:

  • BroadcastReceiver for geofence transitions (limited execution time)
  • WorkManager (system schedules when resources allow)
  • Foreground Service with FOREGROUND_SERVICE_LOCATION type

Foreground Service for Active Tracking

For ride-hailing driver apps or delivery tracking that needs continuous background location:

class LocationTrackingService : Service() {
    private val locationCallback = object : LocationCallback() {
        override fun onLocationResult(result: LocationResult) {
            result.lastLocation?.let { uploadLocation(it) }
        }
    }

    override fun onCreate() {
        // Required: show persistent notification before requesting location
        startForeground(NOTIFICATION_ID, buildTrackingNotification())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5_000L).build()
        fusedLocationClient.requestLocationUpdates(request, locationCallback, Looper.getMainLooper())
        return START_STICKY
    }

    override fun onDestroy() {
        fusedLocationClient.removeLocationUpdates(locationCallback)
    }

    private fun buildTrackingNotification(): Notification {
        return NotificationCompat.Builder(this, "tracking_channel")
            .setSmallIcon(R.drawable.ic_location)
            .setContentTitle("Location tracking active")
            .setContentText("Your location is being shared")
            .setOngoing(true)  // cannot be dismissed by user
            .build()
    }

    override fun onBind(intent: Intent?) = null
}

Geofence Limits

  • Maximum 100 geofences per app
  • Accuracy depends on available sensors; GPS off → cell/WiFi-based ~100–500m accuracy
  • Geofences are lost on device reboot — re-register in BOOT_COMPLETED receiver

Key Takeaways

ConceptRule
ACCESS_BACKGROUND_LOCATIONRequired for background geofencing on API 29+; user grants separately
BroadcastReceiver for transitionsTrigger WorkManager — don't do long work in the receiver
Foreground ServiceRequired for continuous background location (ride apps, delivery)
Geofence limitMax 100 per app; remove when no longer needed
Re-register on bootGeofences lost on reboot; listen for BOOT_COMPLETED
Doze modeGeofencing still works in Doze (uses coarser positioning); foreground services resume

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Geofencing & Background Limits | Android System Design | Android Engineers