Key the weather worker's location skip on the provider, not on a setting

typing.8 checked WeatherSettingsData.managedLocation, but that only exists once the
user has explicitly picked "managed location" for the provider, so
weatherProviderSettings stayed empty and the guard never fired: the worker kept
requesting a location and retrying every 30s.

The real signal is the provider's own capability. BreezyWeatherProvider.getWeatherData()
is a hardcoded noop that also manages its own location, so add WeatherProvider.isPushBased
and check that instead.

Also stop treating a managed location as "pull nothing": those providers still have data
to fetch, they just resolve the position themselves, so they now take the
WeatherLocation path instead of the auto-location one.

Bump to 1.40.2-typing.9 (versionCode 2026091202).
This commit is contained in:
2026-09-12 15:43:10 +02:00
parent 61f3424de5
commit b0072f0fc2
4 changed files with 43 additions and 29 deletions

View File

@@ -34,8 +34,8 @@ android {
applicationId = "de.mm20.launcher2"
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.targetSdk.get().toInt()
versionCode = System.getenv("VERSION_CODE_OVERRIDE")?.toIntOrNull() ?: 2026091201
versionName = "1.40.2-typing.8"
versionCode = System.getenv("VERSION_CODE_OVERRIDE")?.toIntOrNull() ?: 2026091202
versionName = "1.40.2-typing.9"
signingConfig = signingConfigs.getByName("debug")
}

View File

@@ -11,6 +11,14 @@ interface WeatherProvider {
return 1000 * 60 * 60L
}
/**
* Whether this provider pushes weather updates on its own instead of being polled, and
* resolves its location itself. [WeatherUpdateWorker] then has nothing to pull and must not
* request a location for it.
*/
val isPushBased: Boolean
get() = false
suspend fun getWeatherData(location: WeatherLocation): List<Forecast>?
suspend fun getWeatherData(lat: Double, lon: Double): List<Forecast>?
suspend fun findLocation(query: String): List<WeatherLocation>

View File

@@ -79,13 +79,12 @@ internal class WeatherRepositoryImpl(
}
scope.launch {
settings.collectLatest {
if (it.managedLocation) {
// The provider resolves its own location and pushes weather updates, so there
// is nothing to poll for.
val provider = WeatherProvider.getInstance(it.provider)
if (provider.isPushBased) {
// The provider sends its updates itself, so there is nothing to poll for.
WorkManager.getInstance(context).cancelUniqueWork("weather")
return@collectLatest
}
val provider = WeatherProvider.getInstance(it.provider)
val weatherRequest =
PeriodicWorkRequestBuilder<WeatherUpdateWorker>(Duration.ofMillis(provider.getUpdateInterval()))
.build()
@@ -218,20 +217,19 @@ class WeatherUpdateWorker(
override suspend fun doWork(): Result {
Log.d("WeatherUpdateWorker", "Requesting weather data")
val settingsData = settings.first()
val provider = WeatherProvider.getInstance(settingsData.provider)
// A provider with a managed location resolves its position itself and pushes weather
// updates instead of being polled (Breezy Weather does both). There is nothing to pull,
// and its getWeatherData() always returns null - which used to make this worker request a
// location (holding GPS and network listeners for up to ten minutes at a time) and then
// return Result.retry() forever, without ever setting lastUpdate, so the update interval
// check below could never short-circuit it either.
if (settingsData.managedLocation) {
Log.d("WeatherUpdateWorker", "Provider manages its own location, nothing to pull")
// A push-based provider sends its updates on its own and resolves its own location
// (Breezy Weather does both), so there is nothing to pull here. Its getWeatherData()
// always returns null, which used to make this worker request a location - registering
// GPS and network listeners and holding them for up to ten minutes - and then return
// Result.retry() forever, without ever setting lastUpdate, so the interval check below
// could never short-circuit it either.
if (provider.isPushBased) {
Log.d("WeatherUpdateWorker", "Provider pushes updates, nothing to pull")
return Result.success()
}
val provider = WeatherProvider.getInstance(settingsData.provider)
val updateInterval = provider.getUpdateInterval()
val lastUpdate = settingsData.lastUpdate
@@ -240,21 +238,26 @@ class WeatherUpdateWorker(
return Result.failure()
}
val weatherData = if (settingsData.autoLocation) {
val latLon = getLastKnownLocation() ?: settingsData.lastLocation
if (latLon == null) {
Log.e("WeatherUpdateWorker", "Could not get location")
return Result.failure()
val weatherData = when {
// A managed location means the provider resolves the position itself, so requesting
// one here would be wasted work that holds the location stack open.
settingsData.managedLocation, !settingsData.autoLocation -> {
val location = settings.location.first()
if (location == null) {
Log.e("WeatherUpdateWorker", "Location not set")
return Result.failure()
}
provider.getWeatherData(location)
}
settings.setLastLocation(latLon)
provider.getWeatherData(latLon.lat, latLon.lon)
} else {
val location = settings.location.first()
if (location == null) {
Log.e("WeatherUpdateWorker", "Location not set")
return Result.failure()
else -> {
val latLon = getLastKnownLocation() ?: settingsData.lastLocation
if (latLon == null) {
Log.e("WeatherUpdateWorker", "Could not get location")
return Result.failure()
}
settings.setLastLocation(latLon)
provider.getWeatherData(latLon.lat, latLon.lon)
}
provider.getWeatherData(location)
}
return if (weatherData == null) {

View File

@@ -20,6 +20,9 @@ class BreezyWeatherProvider(
) : WeatherProvider, KoinComponent {
private val database: AppDatabase by inject()
// Breezy sends its weather data to us (see pushWeatherData) and manages its own location.
override val isPushBased: Boolean = true
override suspend fun getWeatherData(location: WeatherLocation): List<Forecast>? {
// Noop implementation, because Breezy weather is handled in a special way
return null