Implement centralized data caching system
This commit adds a comprehensive caching system that loads all data on app launch and keeps it in memory, eliminating redundant API calls when navigating between screens. Core Implementation: - DataCache: Singleton holding all app data in StateFlow - DataPrefetchManager: Loads all data in parallel on app launch - Automatic cache updates on create/update/delete operations Features: - ✅ Instant screen loads from cached data - ✅ Reduced API calls (no redundant requests) - ✅ Better UX (no loading spinners on navigation) - ✅ Offline support (data remains available) - ✅ Consistent state across all screens Cache Contents: - Residences (all + my residences + summaries) - Tasks (all tasks + tasks by residence) - Documents (all + by residence) - Contractors (all) - Lookup data (categories, priorities, frequencies, statuses) ViewModels Updated: - ResidenceViewModel: Uses cache with forceRefresh option - TaskViewModel: Uses cache with forceRefresh option - Updates cache on successful create/update/delete iOS Integration: - Data prefetch on successful login - Cache cleared on logout - Background prefetch doesn't block authentication Usage: // Load from cache (instant) viewModel.loadResidences() // Force refresh from API viewModel.loadResidences(forceRefresh: true) Next Steps: - Update DocumentViewModel and ContractorViewModel (same pattern) - Add Android MainActivity integration - Add pull-to-refresh support See composeApp/src/commonMain/kotlin/com/example/mycrib/cache/README_CACHING.md for complete documentation and implementation guide. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
210
composeApp/src/commonMain/kotlin/com/example/mycrib/cache/DataCache.kt
vendored
Normal file
210
composeApp/src/commonMain/kotlin/com/example/mycrib/cache/DataCache.kt
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
package com.mycrib.cache
|
||||
|
||||
import com.mycrib.shared.models.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
/**
|
||||
* Centralized data cache for the application.
|
||||
* This singleton holds all frequently accessed data in memory to avoid redundant API calls.
|
||||
*/
|
||||
object DataCache {
|
||||
|
||||
// User & Authentication
|
||||
private val _currentUser = MutableStateFlow<User?>(null)
|
||||
val currentUser: StateFlow<User?> = _currentUser.asStateFlow()
|
||||
|
||||
// Residences
|
||||
private val _residences = MutableStateFlow<List<Residence>>(emptyList())
|
||||
val residences: StateFlow<List<Residence>> = _residences.asStateFlow()
|
||||
|
||||
private val _myResidences = MutableStateFlow<MyResidencesResponse?>(null)
|
||||
val myResidences: StateFlow<MyResidencesResponse?> = _myResidences.asStateFlow()
|
||||
|
||||
private val _residenceSummaries = MutableStateFlow<Map<Int, ResidenceSummaryResponse>>(emptyMap())
|
||||
val residenceSummaries: StateFlow<Map<Int, ResidenceSummaryResponse>> = _residenceSummaries.asStateFlow()
|
||||
|
||||
// Tasks
|
||||
private val _allTasks = MutableStateFlow<TaskColumnsResponse?>(null)
|
||||
val allTasks: StateFlow<TaskColumnsResponse?> = _allTasks.asStateFlow()
|
||||
|
||||
private val _tasksByResidence = MutableStateFlow<Map<Int, TaskColumnsResponse>>(emptyMap())
|
||||
val tasksByResidence: StateFlow<Map<Int, TaskColumnsResponse>> = _tasksByResidence.asStateFlow()
|
||||
|
||||
// Documents
|
||||
private val _documents = MutableStateFlow<List<Document>>(emptyList())
|
||||
val documents: StateFlow<List<Document>> = _documents.asStateFlow()
|
||||
|
||||
private val _documentsByResidence = MutableStateFlow<Map<Int, List<Document>>>(emptyMap())
|
||||
val documentsByResidence: StateFlow<Map<Int, List<Document>>> = _documentsByResidence.asStateFlow()
|
||||
|
||||
// Contractors
|
||||
private val _contractors = MutableStateFlow<List<Contractor>>(emptyList())
|
||||
val contractors: StateFlow<List<Contractor>> = _contractors.asStateFlow()
|
||||
|
||||
// Lookups/Reference Data
|
||||
private val _categories = MutableStateFlow<List<Category>>(emptyList())
|
||||
val categories: StateFlow<List<Category>> = _categories.asStateFlow()
|
||||
|
||||
private val _priorities = MutableStateFlow<List<Priority>>(emptyList())
|
||||
val priorities: StateFlow<List<Priority>> = _priorities.asStateFlow()
|
||||
|
||||
private val _frequencies = MutableStateFlow<List<Frequency>>(emptyList())
|
||||
val frequencies: StateFlow<List<Frequency>> = _frequencies.asStateFlow()
|
||||
|
||||
private val _statuses = MutableStateFlow<List<Status>>(emptyList())
|
||||
val statuses: StateFlow<List<Status>> = _statuses.asStateFlow()
|
||||
|
||||
// Cache metadata
|
||||
private val _lastRefreshTime = MutableStateFlow<Long>(0L)
|
||||
val lastRefreshTime: StateFlow<Long> = _lastRefreshTime.asStateFlow()
|
||||
|
||||
private val _isCacheInitialized = MutableStateFlow(false)
|
||||
val isCacheInitialized: StateFlow<Boolean> = _isCacheInitialized.asStateFlow()
|
||||
|
||||
// Update methods
|
||||
fun updateCurrentUser(user: User?) {
|
||||
_currentUser.value = user
|
||||
}
|
||||
|
||||
fun updateResidences(residences: List<Residence>) {
|
||||
_residences.value = residences
|
||||
updateLastRefreshTime()
|
||||
}
|
||||
|
||||
fun updateMyResidences(myResidences: MyResidencesResponse) {
|
||||
_myResidences.value = myResidences
|
||||
updateLastRefreshTime()
|
||||
}
|
||||
|
||||
fun updateResidenceSummary(residenceId: Int, summary: ResidenceSummaryResponse) {
|
||||
_residenceSummaries.value = _residenceSummaries.value + (residenceId to summary)
|
||||
}
|
||||
|
||||
fun updateAllTasks(tasks: TaskColumnsResponse) {
|
||||
_allTasks.value = tasks
|
||||
updateLastRefreshTime()
|
||||
}
|
||||
|
||||
fun updateTasksByResidence(residenceId: Int, tasks: TaskColumnsResponse) {
|
||||
_tasksByResidence.value = _tasksByResidence.value + (residenceId to tasks)
|
||||
}
|
||||
|
||||
fun updateDocuments(documents: List<Document>) {
|
||||
_documents.value = documents
|
||||
updateLastRefreshTime()
|
||||
}
|
||||
|
||||
fun updateDocumentsByResidence(residenceId: Int, documents: List<Document>) {
|
||||
_documentsByResidence.value = _documentsByResidence.value + (residenceId to documents)
|
||||
}
|
||||
|
||||
fun updateContractors(contractors: List<Contractor>) {
|
||||
_contractors.value = contractors
|
||||
updateLastRefreshTime()
|
||||
}
|
||||
|
||||
fun updateCategories(categories: List<Category>) {
|
||||
_categories.value = categories
|
||||
}
|
||||
|
||||
fun updatePriorities(priorities: List<Priority>) {
|
||||
_priorities.value = priorities
|
||||
}
|
||||
|
||||
fun updateFrequencies(frequencies: List<Frequency>) {
|
||||
_frequencies.value = frequencies
|
||||
}
|
||||
|
||||
fun updateStatuses(statuses: List<Status>) {
|
||||
_statuses.value = statuses
|
||||
}
|
||||
|
||||
fun setCacheInitialized(initialized: Boolean) {
|
||||
_isCacheInitialized.value = initialized
|
||||
}
|
||||
|
||||
private fun updateLastRefreshTime() {
|
||||
_lastRefreshTime.value = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
// Helper methods to add/update/remove individual items
|
||||
fun addResidence(residence: Residence) {
|
||||
_residences.value = _residences.value + residence
|
||||
}
|
||||
|
||||
fun updateResidence(residence: Residence) {
|
||||
_residences.value = _residences.value.map {
|
||||
if (it.id == residence.id) residence else it
|
||||
}
|
||||
}
|
||||
|
||||
fun removeResidence(residenceId: Int) {
|
||||
_residences.value = _residences.value.filter { it.id != residenceId }
|
||||
// Also clear related caches
|
||||
_tasksByResidence.value = _tasksByResidence.value - residenceId
|
||||
_documentsByResidence.value = _documentsByResidence.value - residenceId
|
||||
_residenceSummaries.value = _residenceSummaries.value - residenceId
|
||||
}
|
||||
|
||||
fun addDocument(document: Document) {
|
||||
_documents.value = _documents.value + document
|
||||
}
|
||||
|
||||
fun updateDocument(document: Document) {
|
||||
_documents.value = _documents.value.map {
|
||||
if (it.id == document.id) document else it
|
||||
}
|
||||
}
|
||||
|
||||
fun removeDocument(documentId: Int) {
|
||||
_documents.value = _documents.value.filter { it.id != documentId }
|
||||
}
|
||||
|
||||
fun addContractor(contractor: Contractor) {
|
||||
_contractors.value = _contractors.value + contractor
|
||||
}
|
||||
|
||||
fun updateContractor(contractor: Contractor) {
|
||||
_contractors.value = _contractors.value.map {
|
||||
if (it.id == contractor.id) contractor else it
|
||||
}
|
||||
}
|
||||
|
||||
fun removeContractor(contractorId: Int) {
|
||||
_contractors.value = _contractors.value.filter { it.id != contractorId }
|
||||
}
|
||||
|
||||
// Clear methods
|
||||
fun clearAll() {
|
||||
_currentUser.value = null
|
||||
_residences.value = emptyList()
|
||||
_myResidences.value = null
|
||||
_residenceSummaries.value = emptyMap()
|
||||
_allTasks.value = null
|
||||
_tasksByResidence.value = emptyMap()
|
||||
_documents.value = emptyList()
|
||||
_documentsByResidence.value = emptyMap()
|
||||
_contractors.value = emptyList()
|
||||
_categories.value = emptyList()
|
||||
_priorities.value = emptyList()
|
||||
_frequencies.value = emptyList()
|
||||
_statuses.value = emptyList()
|
||||
_lastRefreshTime.value = 0L
|
||||
_isCacheInitialized.value = false
|
||||
}
|
||||
|
||||
fun clearUserData() {
|
||||
_currentUser.value = null
|
||||
_residences.value = emptyList()
|
||||
_myResidences.value = null
|
||||
_residenceSummaries.value = emptyMap()
|
||||
_allTasks.value = null
|
||||
_tasksByResidence.value = emptyMap()
|
||||
_documents.value = emptyList()
|
||||
_documentsByResidence.value = emptyMap()
|
||||
_contractors.value = emptyList()
|
||||
_isCacheInitialized.value = false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user