fix: location channel manager (#516)

* location channel manager fix

* fix: use interval arg in beginLiveRefresh & remove unused code
This commit is contained in:
callebtc
2026-01-04 21:09:14 +07:00
committed by GitHub
parent 123a24ce34
commit fe5417bf97
@@ -124,10 +124,11 @@ class LocationChannelManager private constructor(private val context: Context) {
} }
/** /**
* Begin periodic one-shot location refreshes while a selector UI is visible * Begin real-time location updates while a selector UI is visible
* Uses requestLocationUpdates for continuous updates, plus a one-shot to prime state immediately
*/ */
fun beginLiveRefresh(interval: Long = 5000L) { fun beginLiveRefresh(interval: Long = 5000L) {
Log.d(TAG, "Beginning live refresh with interval ${interval}ms") Log.d(TAG, "Beginning live refresh (continuous updates)")
if (_permissionState.value != PermissionState.AUTHORIZED) { if (_permissionState.value != PermissionState.AUTHORIZED) {
Log.w(TAG, "Cannot start live refresh - permission not authorized") Log.w(TAG, "Cannot start live refresh - permission not authorized")
@@ -139,22 +140,40 @@ class LocationChannelManager private constructor(private val context: Context) {
return return
} }
// Cancel existing timer // Cancel any existing timer-based refreshers
refreshTimer?.cancel() refreshTimer?.cancel()
refreshTimer = null
// Start new timer with coroutines // Register for continuous updates from available providers
refreshTimer = CoroutineScope(Dispatchers.IO).launch { try {
while (isActive) { if (hasLocationPermission()) {
if (isLocationServicesEnabled()) { val providers = listOf(
requestOneShotLocation() LocationManager.GPS_PROVIDER,
} LocationManager.NETWORK_PROVIDER
delay(interval) )
providers.forEach { provider ->
if (locationManager.isProviderEnabled(provider)) {
// 2s min time, 5m min distance for responsive yet battery-aware updates
locationManager.requestLocationUpdates(
provider,
interval,
5f,
continuousLocationListener
)
Log.d(TAG, "Registered continuous updates for $provider")
} }
} }
// Kick off immediately // Prime state immediately with last known / current location
requestOneShotLocation() requestOneShotLocation()
} }
} catch (e: SecurityException) {
Log.e(TAG, "Missing location permission for continuous updates: ${e.message}")
} catch (e: Exception) {
Log.e(TAG, "Failed to register continuous updates: ${e.message}")
}
}
/** /**
* Stop periodic refreshes when selector UI is dismissed * Stop periodic refreshes when selector UI is dismissed
@@ -163,6 +182,12 @@ class LocationChannelManager private constructor(private val context: Context) {
Log.d(TAG, "Ending live refresh") Log.d(TAG, "Ending live refresh")
refreshTimer?.cancel() refreshTimer?.cancel()
refreshTimer = null refreshTimer = null
// Unregister continuous updates listener
try {
locationManager.removeUpdates(continuousLocationListener)
} catch (_: SecurityException) {
} catch (_: Exception) {
}
} }
/** /**
@@ -297,10 +322,34 @@ class LocationChannelManager private constructor(private val context: Context) {
} }
} }
// Continuous location listener for real-time updates
private val continuousLocationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
Log.d(TAG, "Real-time location: ${location.latitude}, ${location.longitude} acc=${location.accuracy}m")
lastLocation = location
_isLoadingLocation.value = false
computeChannels(location)
reverseGeocodeIfNeeded(location)
}
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
// Deprecated but can still be called on older devices
Log.v(TAG, "Provider status changed: $provider -> $status")
}
override fun onProviderEnabled(provider: String) {
Log.d(TAG, "Provider enabled: $provider")
}
override fun onProviderDisabled(provider: String) {
Log.d(TAG, "Provider disabled: $provider")
}
}
// One-time location listener to get a fresh location update // One-time location listener to get a fresh location update
private val oneShotLocationListener = object : LocationListener { private val oneShotLocationListener = object : LocationListener {
override fun onLocationChanged(location: Location) { override fun onLocationChanged(location: Location) {
Log.d(TAG, "Fresh location received: ${location.latitude}, ${location.longitude}") Log.d(TAG, "One-shot location: ${location.latitude}, ${location.longitude}")
lastLocation = location lastLocation = location
computeChannels(location) computeChannels(location)
reverseGeocodeIfNeeded(location) reverseGeocodeIfNeeded(location)
@@ -315,6 +364,18 @@ class LocationChannelManager private constructor(private val context: Context) {
Log.e(TAG, "Error removing location listener: ${e.message}") Log.e(TAG, "Error removing location listener: ${e.message}")
} }
} }
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
// Required for compatibility with older platform versions
}
override fun onProviderEnabled(provider: String) {
// Required for compatibility with older platform versions
}
override fun onProviderDisabled(provider: String) {
// Required for compatibility with older platform versions
}
} }
// Request a fresh location update using getCurrentLocation instead of continuous updates // Request a fresh location update using getCurrentLocation instead of continuous updates
@@ -663,16 +724,9 @@ class LocationChannelManager private constructor(private val context: Context) {
Log.d(TAG, "Cleaning up LocationChannelManager") Log.d(TAG, "Cleaning up LocationChannelManager")
endLiveRefresh() endLiveRefresh()
// For older Android versions, remove any remaining location listener to prevent memory leaks // Remove listeners to prevent memory leaks
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R) { try { locationManager.removeUpdates(oneShotLocationListener) } catch (_: Exception) {}
try { try { locationManager.removeUpdates(continuousLocationListener) } catch (_: Exception) {}
locationManager.removeUpdates(oneShotLocationListener)
} catch (e: SecurityException) {
Log.e(TAG, "Error removing location listener during cleanup: ${e.message}")
} catch (e: Exception) {
Log.e(TAG, "Error during cleanup: ${e.message}")
}
}
// For Android 11+, getCurrentLocation doesn't need explicit cleanup as it's a one-time operation // For Android 11+, getCurrentLocation doesn't need explicit cleanup as it's a one-time operation
} }
} }