Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@
## 2024-05-24 - [Idiomatic Jetpack Compose Density-Aware Optimizations]
**Learning:** Working around Density conversions (`.toPx()`) outside of a `Canvas` by creating mutable variables and updating them later during the draw phase is an anti-pattern that defeats the purpose of the optimization by forcing a reference wrapper allocation.
**Action:** When extracting styling objects like `Stroke` out of a `Canvas` that depend on `dp` values, get `LocalDensity.current` and pass it to a `remember` block: `remember(density) { Stroke(width = with(density) { 2.dp.toPx() }) }`.

## 2025-03-22 - Filter High-Frequency Sensor Emissions
**Learning:** Emitting a new data object unconditionally on every high-frequency Android sensor event (e.g., `SENSOR_DELAY_UI` at 60Hz) creates severe GC churn from rapid object allocations and causes downstream Jetpack Compose UI loops to recompose 60 times a second.
**Action:** When working with continuous sensor flows, track the last emitted values and apply a change threshold (e.g., > 1 degree for compass data). Only instantiate and emit the new data object if the threshold is met. This drastically reduces object allocation and UI recompositions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class CompassSensor(context: Context) {
var hasGravity = false
var hasMagnetic = false

// Track last emitted values to prevent excessive object allocation and UI recompositions
var lastEmittedAzimuth = -1000f
var lastEmittedPitch = -1000f
var lastEmittedRoll = -1000f

val listener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
when (event.sensor.type) {
Expand All @@ -63,14 +68,28 @@ class CompassSensor(context: Context) {
val pitch = Math.toDegrees(orientation[1].toDouble()).toFloat()
val roll = Math.toDegrees(orientation[2].toDouble()).toFloat()

trySend(
CompassData(
azimuth = azimuthDegrees,
pitch = pitch,
roll = roll,
direction = getDirectionFromAzimuth(azimuthDegrees)
// Only emit if value has changed by >= 1 degree to save GC allocations
// and prevent constant 60Hz UI recompositions
val azimuthDiff = Math.abs(azimuthDegrees - lastEmittedAzimuth)
val shortestAzimuthDiff = Math.min(azimuthDiff, 360f - azimuthDiff)

val pitchDiff = Math.abs(pitch - lastEmittedPitch)
val rollDiff = Math.abs(roll - lastEmittedRoll)

if (shortestAzimuthDiff >= 1.0f || pitchDiff >= 1.0f || rollDiff >= 1.0f) {
lastEmittedAzimuth = azimuthDegrees
lastEmittedPitch = pitch
lastEmittedRoll = roll

trySend(
CompassData(
azimuth = azimuthDegrees,
pitch = pitch,
roll = roll,
direction = getDirectionFromAzimuth(azimuthDegrees)
)
)
)
}
}
}
}
Expand Down
Loading