refactor: getTasksByResidence is now a thin filter over _allTasks

Was 3 fallback paths (per-residence cache → filter from allTasks →
network). Now: ensure _allTasks fresh, return filter. The per-residence
cache becomes write-only by this path, scheduled for deletion in the
next commit.

Eliminates a class of bugs where the per-residence cache slot could
be missing while _allTasks was stale — the old Path 1+2 would either
return stale data or skip and hit the API redundantly.
This commit is contained in:
Trey t
2026-04-25 10:41:06 -05:00
parent 915a5d4742
commit dea8eed184
@@ -615,37 +615,23 @@ object APILayer {
return result return result
} }
/**
* Returns kanban data for a single residence. Single source of truth
* is `_allTasks`; this function ensures it's fresh, then filters.
*
* Replaces the previous 3-path implementation (per-residence cache →
* filter from allTasks → API) that produced inconsistent results
* when the per-residence cache slot was empty but `_allTasks` was
* stale. Phase 3 deletes the per-residence cache entirely.
*/
suspend fun getTasksByResidence(residenceId: Int, forceRefresh: Boolean = false): ApiResult<TaskColumnsResponse> { suspend fun getTasksByResidence(residenceId: Int, forceRefresh: Boolean = false): ApiResult<TaskColumnsResponse> {
// 1. Check residence-specific cache first val allTasksResult = getTasks(forceRefresh = forceRefresh)
if (!forceRefresh && DataManager.isCacheValid(DataManager.tasksByResidenceCacheTime[residenceId] ?: 0L)) { if (allTasksResult is ApiResult.Error) return allTasksResult
val cached = DataManager.tasksByResidence.value[residenceId]
if (cached != null) {
return ApiResult.Success(cached)
}
}
// 2. Try filtering from allTasks cache before hitting API (optimization)
// This avoids a redundant API call when we already have all tasks loaded
if (!forceRefresh && DataManager.isCacheValid(DataManager.tasksCacheTime)) {
val filtered = DataManager.getTasksForResidence(residenceId) val filtered = DataManager.getTasksForResidence(residenceId)
if (filtered != null) { ?: return ApiResult.Error("Tasks unavailable", 0)
// Cache the filtered result for future use
DataManager.setTasksForResidence(residenceId, filtered)
return ApiResult.Success(filtered) return ApiResult.Success(filtered)
} }
}
// 3. Fallback: Fetch from API
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
val result = taskApi.getTasksByResidence(token, residenceId)
// Update DataManager on success
if (result is ApiResult.Success) {
DataManager.setTasksForResidence(residenceId, result.data)
}
return result
}
suspend fun createTask(request: TaskCreateRequest): ApiResult<TaskResponse> { suspend fun createTask(request: TaskCreateRequest): ApiResult<TaskResponse> {
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401) val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)