androidkotlinandroid-viewmodelspeedometer

Kotlin Speedometer is not giving correct speed


My Android app has a speedometer that tells how fast the user is driving. However, it is not working smoothly. It tells the speed but not the exact speed and does not update the speed smoothly.

Am I doing something wrong here? Is there any other way to fetch user speed?

class SpeedometerVM(application: Application) : AndroidViewModel(application) {

private val _speed = MutableStateFlow(0f)  // Speed in km/h
val speed = _speed.asStateFlow()

private val _isActive = MutableStateFlow(false)
val isActive = _isActive.asStateFlow()

private val _accelerationStatus =
    MutableStateFlow("Neutral") // Acceleration/Deceleration status
val accelerationStatus = _accelerationStatus.asStateFlow()

// Variable to store previous speed with timestamp for more accurate acceleration calculation
private var previousSpeed = 0f
private var lastUpdateTime = 0L

private val fusedLocationClient by lazy {
    LocationServices.getFusedLocationProviderClient(getApplication<Application>())
}

// Keep a reference to the callback so we can remove it later
private val locationCallback = object : LocationCallback() {
    override fun onLocationResult(locationResult: LocationResult) {
        locationResult.lastLocation?.let { location ->
            val currentTimestamp = System.currentTimeMillis()
            val speedInMetersPerSecond = location.speed

            // Only update if we have a valid speed reading
            if (location.hasSpeed()) {
                val speedInKmPerHour = speedInMetersPerSecond * 3.6f // Convert m/s to km/h

                // Apply a small smoothing filter to reduce jitter
                val smoothedSpeed = if (_speed.value == 0f) {
                    speedInKmPerHour
                } else {
                    _speed.value * 0.7f + speedInKmPerHour * 0.3f
                }

                _speed.value = smoothedSpeed

                // If enough time has passed, calculate acceleration status
                if (lastUpdateTime > 0 && (currentTimestamp - lastUpdateTime) > 500) {
                    val speedDiff = smoothedSpeed - previousSpeed

                    // Only update status if change exceeds threshold
                    val newStatus = when {
                        speedDiff > 0.5 -> "Accelerating"
                        speedDiff < -0.5 -> "Decelerating"
                        else -> "Neutral"
                    }

                    if (newStatus != _accelerationStatus.value) {
                        _accelerationStatus.value = newStatus
                    }
                }

                // Update the previous values
                previousSpeed = smoothedSpeed
                lastUpdateTime = currentTimestamp
            }
        }
    }
}

fun startLocationUpdates() = registerLocationUpdates()

private fun registerLocationUpdates() {
    val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000L)
        .setMinUpdateIntervalMillis(500) // Set minimum update time
        .setMaxUpdateDelayMillis(2000) // Set maximum delay
        .build()
    viewModelScope.launch {
        try {
            fusedLocationClient.requestLocationUpdates(
                locationRequest,
                locationCallback,
                Looper.getMainLooper()
            )
            _isActive.value = true
        } catch (e: SecurityException) {
            _isActive.value = false
            // Handle permission exception
            logger("SpeedometerViewModel", "Location permission error: ${e.message}")
        }
    }
}

/**
 * Stops location updates
 */
fun stopLocationUpdates() {
    viewModelScope.launch {
        fusedLocationClient.removeLocationUpdates(locationCallback)
        _isActive.value = false
        _speed.value = 0.0f
        _accelerationStatus.value = "Stable"
        previousSpeed = 0.0f
    }
}

override fun onCleared() {
    super.onCleared()
    if (_isActive.value) {
        stopLocationUpdates()
    }
}

}

Collecting this stateflow using

    val speed by speedVM.speed.collectAsStateWithLifecycle()

Solution

  • You using : collectAsStateWithLifecycle() is correct

    Just make sure that :

    If your speed still jumps too much. You can apply a low-pass filter like this , instead of just 70–30 blend:

    val alpha = 0.25f

    val smoothedSpeed = _speed.value + alpha * (newSpeed - _speed.value)

    This will give smoother transitions and avoids jittery behavior. Hope it helps.