From c92dcd0d6407e506c84ca8bb79ab395257d7c392 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Wed, 1 Jul 2026 06:24:45 +0000 Subject: fix(aton): switch to org.json, add stale-cache fallback Replace Gson dependency in AtonData with org.json.JSONObject (Android SDK built-in) and add testImplementation for org.json to support unit tests. AtonRepository now falls back to stale cache data when the network is unavailable rather than returning an empty list. --- .../main/kotlin/org/terst/nav/ui/aton/AtonData.kt | 29 +++++++++------------- .../kotlin/org/terst/nav/ui/aton/AtonRepository.kt | 24 ++++++++++-------- 2 files changed, 26 insertions(+), 27 deletions(-) (limited to 'android-app/app/src/main') diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonData.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonData.kt index 2ff59cd..d3a6b6b 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonData.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonData.kt @@ -1,9 +1,8 @@ package org.terst.nav.ui.aton -import com.google.gson.JsonObject -import com.google.gson.JsonParser import org.maplibre.geojson.Feature import org.maplibre.geojson.Point +import org.json.JSONObject object AtonData { fun colorFor(type: String): String = when (type) { @@ -14,24 +13,20 @@ object AtonData { } fun parseStation(json: String): Feature? = try { - val obj = JsonParser.parseString(json).asJsonObject - val lat = obj.get("lat").asDouble - val lng = obj.get("lng").asDouble - val type = obj.get("type")?.asString ?: "" - val id = obj.get("id")?.asString ?: "" - val name = obj.get("name")?.asString ?: "" - val props = JsonObject().apply { - addProperty("id", id) - addProperty("name", name) - addProperty("type", type) - addProperty("color", colorFor(type)) + val obj = JSONObject(json) + val lat = obj.getDouble("lat") + val lng = obj.getDouble("lng") + val type = obj.optString("type", "") + Feature.fromGeometry(Point.fromLngLat(lng, lat)).apply { + addStringProperty("id", obj.optString("id")) + addStringProperty("name", obj.optString("name")) + addStringProperty("type", type) + addStringProperty("color", colorFor(type)) } - Feature.fromGeometry(Point.fromLngLat(lng, lat), props) } catch (_: Exception) { null } fun parseResponse(json: String): List = try { - val arr = JsonParser.parseString(json).asJsonObject - .getAsJsonArray("stations") - arr.mapNotNull { parseStation(it.toString()) } + val arr = JSONObject(json).getJSONArray("stations") + (0 until arr.length()).mapNotNull { parseStation(arr.get(it).toString()) } } catch (_: Exception) { emptyList() } } diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonRepository.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonRepository.kt index b5472b3..249fdac 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonRepository.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/aton/AtonRepository.kt @@ -16,18 +16,22 @@ class AtonRepository(private val context: Context) { "https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi/stations.json?type=aton&units=english" suspend fun getFeatures(): List = withContext(Dispatchers.IO) { - val cached = readCache() - if (cached != null) return@withContext AtonData.parseResponse(cached) - val fresh = fetchFromApi() ?: return@withContext emptyList() - cacheFile.writeText(fresh) - AtonData.parseResponse(fresh) + val staleJson = readCacheRaw() + if (staleJson != null && isCacheFresh()) return@withContext AtonData.parseResponse(staleJson) + val fresh = fetchFromApi() + if (fresh != null) { + cacheFile.writeText(fresh) + return@withContext AtonData.parseResponse(fresh) + } + // Network unavailable — serve stale data if any, otherwise empty + if (staleJson != null) AtonData.parseResponse(staleJson) else emptyList() } - private fun readCache(): String? { - if (!cacheFile.exists()) return null - if (System.currentTimeMillis() - cacheFile.lastModified() > maxAgeMs) return null - return cacheFile.readText() - } + private fun isCacheFresh(): Boolean = + cacheFile.exists() && System.currentTimeMillis() - cacheFile.lastModified() <= maxAgeMs + + private fun readCacheRaw(): String? = + if (cacheFile.exists()) runCatching { cacheFile.readText() }.getOrNull() else null private fun fetchFromApi(): String? = try { URL(apiUrl).openStream().bufferedReader().readText() -- cgit v1.2.3