androidengineers.Book a session

Designing a Ride-Sharing App

Driver-Rider Matching

article20 minHard

Driver-rider matching is the core of any ride-share app. The client must display live driver locations, animate movement smoothly, show ETA updates in realtime, and handle the state machine from request to dropoff.

Matching State Machine

enum class RideStatus {
    IDLE,               // no active request
    REQUESTING,         // user submitted request; waiting for match
    DRIVER_FOUND,       // matched — driver on the way
    DRIVER_ARRIVED,     // driver at pickup location
    IN_TRIP,            // trip in progress
    COMPLETED,          // arrived at destination
    CANCELLED           // cancelled by driver or rider
}

data class RideState(
    val status: RideStatus = RideStatus.IDLE,
    val driverId: String? = null,
    val driverName: String? = null,
    val driverAvatarUrl: String? = null,
    val driverLocation: LatLng? = null,
    val vehicleType: String? = null,
    val licensePlate: String? = null,
    val etaSeconds: Int? = null,
    val tripRoute: List<LatLng> = emptyList()
)

Realtime Driver Location via WebSocket

class RideWebSocket @Inject constructor(
    private val okHttpClient: OkHttpClient
) {
    private var webSocket: WebSocket? = null
    private val _events = MutableSharedFlow<RideEvent>(replay = 0)
    val events: SharedFlow<RideEvent> = _events

    fun connect(rideId: String) {
        val request = Request.Builder()
            .url("wss://api.example.com/rides/$rideId/stream")
            .build()

        webSocket = okHttpClient.newWebSocket(request, object : WebSocketListener() {
            override fun onMessage(webSocket: WebSocket, text: String) {
                val event = parseRideEvent(text)
                _events.tryEmit(event)
            }

            override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
                _events.tryEmit(RideEvent.ConnectionError(t.message ?: "Unknown error"))
                scheduleReconnect(rideId)
            }
        })
    }

    private fun scheduleReconnect(rideId: String) {
        // Exponential backoff reconnect
    }

    fun disconnect() { webSocket?.close(1000, "Ride completed") }
}

sealed class RideEvent {
    data class DriverLocationUpdate(val location: LatLng, val heading: Float) : RideEvent()
    data class EtaUpdate(val seconds: Int) : RideEvent()
    data class StatusChange(val status: RideStatus) : RideEvent()
    data class ConnectionError(val message: String) : RideEvent()
}

Smooth Driver Marker Animation

class DriverMarkerAnimator(private val marker: Marker) {

    fun animateToPosition(target: LatLng, heading: Float) {
        val start = marker.position
        val valueAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
            duration = 800  // match GPS update interval
            interpolator = LinearInterpolator()
            addUpdateListener { animator ->
                val fraction = animator.animatedFraction
                val lat = start.latitude + (target.latitude - start.latitude) * fraction
                val lng = start.longitude + (target.longitude - start.longitude) * fraction
                marker.position = LatLng(lat, lng)
            }
        }
        valueAnimator.start()

        // Animate rotation separately for smooth bearing change
        val rotationAnimator = ObjectAnimator.ofFloat(marker.rotation, heading).apply {
            duration = 500
            addUpdateListener { marker.rotation = it.animatedValue as Float }
        }
        rotationAnimator.start()
    }
}

ViewModel: Orchestrating the Ride

@HiltViewModel
class RideViewModel @Inject constructor(
    private val rideApi: RideApi,
    private val rideWebSocket: RideWebSocket
) : ViewModel() {

    private val _state = MutableStateFlow(RideState())
    val state: StateFlow<RideState> = _state

    fun requestRide(pickup: LatLng, destination: LatLng, vehicleType: String) {
        _state.value = _state.value.copy(status = RideStatus.REQUESTING)

        viewModelScope.launch {
            try {
                val ride = rideApi.requestRide(pickup, destination, vehicleType)
                _state.value = _state.value.copy(
                    status = RideStatus.DRIVER_FOUND,
                    driverId = ride.driverId,
                    driverName = ride.driverName,
                    driverLocation = ride.driverLocation,
                    etaSeconds = ride.etaSeconds,
                    licensePlate = ride.licensePlate
                )
                connectToRideStream(ride.rideId)
            } catch (e: Exception) {
                _state.value = _state.value.copy(status = RideStatus.IDLE)
            }
        }
    }

    private fun connectToRideStream(rideId: String) {
        rideWebSocket.connect(rideId)
        viewModelScope.launch {
            rideWebSocket.events.collect { event ->
                when (event) {
                    is RideEvent.DriverLocationUpdate ->
                        _state.value = _state.value.copy(driverLocation = event.location)
                    is RideEvent.EtaUpdate ->
                        _state.value = _state.value.copy(etaSeconds = event.seconds)
                    is RideEvent.StatusChange ->
                        _state.value = _state.value.copy(status = event.status)
                    is RideEvent.ConnectionError -> { /* reconnect */ }
                }
            }
        }
    }

    override fun onCleared() {
        rideWebSocket.disconnect()
        super.onCleared()
    }
}

Key Takeaways

PatternRule
State machineModel ride as an enum; every UI decision flows from RideStatus
WebSocket for locationGPS updates every 2–5s; WebSocket push is the right transport
Animate, don't jumpValueAnimator over 800ms interpolates between GPS positions
Heading animationRotate marker smoothly with ObjectAnimator to match travel direction
Reconnect on errorExponential backoff; never leave user staring at stale position
Cancel on viewModel clearedAlways disconnect() in onCleared(); prevent leak

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Driver-Rider Matching | Android System Design | Android Engineers