Sprint 3‐Favorites - G33-Moviles-2026-1/Wiki GitHub Wiki
The current Flutter favorites implementation does not use a dedicated cache layer for favorites retrieval. Instead, it relies on local SQLite persistence for room snapshots and pending mutations, which supports optimistic UI updates and eventual synchronization.
The current Kotlin favorites implementation does not use a network-only favorites flow. Instead, it relies on local Room persistence for favorite room snapshots and pending mutations, which supports optimistic UI updates and eventual synchronization.
| Title | Offline Add/Remove Favorite from Results Screen |
|---|---|
| Event description | The user taps the favorite icon on a room card in the results screen while the device has no active internet connection or the backend is temporarily unreachable. |
| System response |
Flutter: The application applies the favorite change optimistically in Flutter local state and persists the mutation in local SQLite. If the room was not previously a favorite, a room snapshot is stored locally with syncState = pending_add and an add mutation row is inserted. If the room was already a favorite, the local row is marked pending_remove and a remove mutation row is inserted. A background sync attempt is triggered immediately; if it still fails, the mutation remains pending and is retried the next time favorites are loaded or another sync-triggering favorites action occurs.Kotlin: The application applies the favorite change optimistically in Kotlin local state and persists the mutation in local Room. If the room was not previously a favorite, a favorite snapshot is stored locally and an add mutation is queued. If the room was already a favorite, the local row is removed and a delete mutation is queued. A background sync attempt is triggered immediately; if it still fails, the mutation remains pending and is retried when connectivity returns, when favorites are opened, or when another sync-triggering favorites action occurs. |
| Possible antipatterns | • 4.6: The favorite change is lost because the app waits for the backend and discards the user action offline. • 5.2: The sync failure happens silently and the pending mutation is never retried. • 8.1: Tapping the same favorite icon offline sometimes updates the UI optimistically and other times leaves the icon unchanged. • 3.2: A raw Dio/network exception is shown instead of preserving the optimistic local state. |
| Caching and retrieving strategy | 2 - Network only |
| Storage type | Flutter: SQLite, Kotlin: Room |
| Stored data type | Flutter: Favorite room snapshot (roomId, buildingCode, buildingName, roomNumber, capacity, reliability, utilities, syncState, savedAt, updatedAt, lastError) + favorite mutation (opId, roomId, operation, attemptCount, lastError, createdAt, updatedAt)Kotlin: Favorite room snapshot (userKey, roomId, name, building, buildingCode, capacity, utilitiesJson) + pending favorite mutation (id, actionType=ADD_FAVORITE/DELETE_FAVORITE, payload, localClassId=userKey::roomId) |
| Title | Offline Add/Remove Favorite from Room Detail |
|---|---|
| Event description | The user taps the favorite action from the room detail screen while there is no internet connection or the backend cannot be reached. |
| System response | Flutter: The application follows the same Flutter favorites mutation flow used in results: it writes the favorite snapshot locally, marks the row as pending_add or pending_remove, inserts the corresponding mutation row, and immediately reflects the change in local favorite state. A background sync attempt is launched right away. If that attempt fails because connectivity is still unavailable, the mutation is kept in SQLite and retried later when the favorites repository sync path runs again.Kotlin: The application follows the same Kotlin favorites mutation flow used in results: it writes the favorite snapshot locally, queues the corresponding add or delete mutation, and immediately reflects the change in local favorite state. A background sync attempt is launched right away. If that attempt fails because connectivity is still unavailable, the mutation is kept in Room and retried later when the favorites sync path runs again after reconnection or other sync triggers. |
| Possible antipatterns | • 4.6: The heart icon changes back after tap because the app depends entirely on immediate server confirmation. • 5.4: The action fails offline but the UI remains silent, leaving the user unsure whether the room was saved or removed. • 8.2: Repeating the same tap sequence offline produces inconsistent local states in detail versus results. • 7: The room stays permanently stuck in a pending state because the retry path is never re-entered after connectivity returns. |
| Caching and retrieving strategy | 2 - Network only |
| Storage type | Flutter: SQLite, Kotlin: Room |
| Stored data type | Flutter: Favorite room snapshot (roomId, buildingCode, buildingName, roomNumber, capacity, reliability, utilities, syncState, savedAt, updatedAt, lastError) + favorite mutation (opId, roomId, operation, attemptCount, lastError, createdAt, updatedAt)Kotlin: Favorite room snapshot (userKey, roomId, name, building, buildingCode, capacity, utilitiesJson) + pending favorite mutation (id, actionType=ADD_FAVORITE/DELETE_FAVORITE, payload, localClassId=userKey::roomId) |
| Title | Offline Remove Favorite from Favorites Screen with Undo |
|---|---|
| Event description | The user removes a room from the favorites screen while offline, and optionally taps Undo from the snackbar before it disappears. |
| System response | Flutter: The application removes the room from the visible Flutter favorites list immediately by marking it pending_remove, then shows a snackbar with an Undo action. If the user does nothing, the room remains hidden from the visible favorites list and the pending remove stays stored locally until the next sync attempt. If the user taps Undo before the snackbar expires, the room is restored to the list immediately and the notifier issues the inverse local mutation so the room returns to the saved favorites state in Flutter. The background sync attempt remains best-effort: if connectivity is still unavailable, pending mutations stay in SQLite until later synchronization.Kotlin: The application removes the room from the visible Kotlin favorites list immediately, then shows a snackbar with an Undo action. If the user does nothing, the room remains hidden from the visible favorites list and the pending remove stays stored locally until the next sync attempt. If the user taps Undo before the snackbar expires, the room is restored to the list immediately and the app issues the inverse local mutation so the room returns to the saved favorites state in Kotlin. The background sync attempt remains best-effort: if connectivity is still unavailable, pending mutations stay in Room until later synchronization. |
| Possible antipatterns | • 4.6: The room disappears from favorites with no way to restore it locally. • 5.5: The app removes the room but never tells the user that undo is available. • 8.1: Sometimes the removed room can be undone and other times it cannot, under the same offline conditions. • 3.3: The app reports “No favorites found” without clarifying that the room is only hidden pending sync/removal. |
| Caching and retrieving strategy | 2 - Network only |
| Storage type | Flutter: SQLite |
| Stored data type | Flutter: Favorite room snapshot (roomId, buildingCode, buildingName, roomNumber, capacity, reliability, utilities, syncState, savedAt, updatedAt, lastError) + favorite mutation (opId, roomId, operation, attemptCount, lastError, createdAt, updatedAt) + transient snackbar undo UI stateKotlin: Favorite room snapshot (userKey, roomId, name, building, buildingCode, capacity, utilitiesJson) + pending favorite mutation (id, actionType=ADD_FAVORITE/DELETE_FAVORITE, payload, localClassId=userKey::roomId) |
Stores favorite room snapshots locally in SQLite so the favorites list can be rendered from persisted data, and stores pending add/remove mutations in a second SQLite table so offline favorite actions can be retried later by the Flutter repository sync path. Room snapshots also preserve utility information and other room metadata for favorites list rendering and pre-seeded room detail navigation.
| Column | Type | Description |
|---|---|---|
| roomId | TEXT (PK) | Unique room identifier |
| buildingCode | TEXT | Building code used for display and room-detail seeding |
| buildingName | TEXT nullable | Human-readable building name |
| roomNumber | TEXT | Room number portion of the room identifier |
| capacity | INTEGER | Cached room capacity |
| reliability | REAL | Cached reliability score |
| utilitiesJson | TEXT | JSON-encoded array of utility identifiers |
| syncState | TEXT | Local sync status (clean, pending_add, pending_remove) |
| lastError | TEXT nullable | Last synchronization error message for this row |
| savedAt | DATETIME | Timestamp of initial local persistence |
| updatedAt | DATETIME | Timestamp of the latest local update |
Lifecycle: Upserted after successful favorites loads and also during optimistic add flows. Existing rows are marked pending_remove on optimistic delete, excluded from the visible favorites list while pending, and hard-deleted after a successful remove sync. Clean rows not present in the server response are deleted during favorites refresh reconciliation.
| Column | Type | Description |
|---|---|---|
| opId | TEXT (PK) | Unique mutation operation id |
| roomId | TEXT | Room affected by the mutation |
| operation | TEXT | Pending operation type (add or remove) |
| attemptCount | INTEGER | Number of sync attempts made for this mutation |
| lastError | TEXT nullable | Last sync error returned for this mutation |
| createdAt | DATETIME | Mutation creation time |
| updatedAt | DATETIME | Last mutation update time |
Lifecycle: Inserted whenever Flutter records an optimistic add or remove that still needs backend synchronization. Mutation rows are deleted after successful sync, updated with attemptCount + 1 and lastError on failed sync attempts, and may be cleared immediately if a pending add is undone locally before ever reaching the backend.
Stores favorite room snapshots locally in Room (SQLite) so the favorites list can be rendered from persisted data, and stores pending add/remove mutations in a shared Room sync table so offline favorite actions can be retried later by the favorites repository sync path. Favorite snapshots preserve room metadata and utilities for favorites list rendering and room-detail navigation.
| Column | Type | Description |
|---|---|---|
| id | INTEGER (PK, autogen) | Internal local row id |
| userKey | TEXT | User/session scope key for favorites partitioning |
| roomId | TEXT | Unique room identifier per user scope (with userKey, unique index) |
| name | TEXT nullable | Room display name |
| building | TEXT nullable | Human-readable building name |
| buildingCode | TEXT nullable | Building code used for display/detail |
| capacity | INTEGER nullable | Cached room capacity |
| utilitiesJson | TEXT | JSON-encoded array of utility identifiers |
Lifecycle: Inserted on optimistic add and on successful favorites refresh persistence. Deleted on optimistic remove. During refresh persistence, Kotlin clears all favorites for the current userKey and inserts the new merged set saved by the repository.
| Column | Type | Description |
|---|---|---|
| id | INTEGER (PK, autogen) | Internal queued action id |
| actionType | TEXT | Pending action type (ADD_FAVORITE or DELETE_FAVORITE) |
| payload | TEXT | Serialized request payload (ADD) or roomId (DELETE) |
| localClassId | TEXT nullable | Mutation key (userKey::roomId) used to coalesce latest intent |
Lifecycle: Inserted whenever Kotlin records an optimistic add/remove that still needs backend synchronization (offline or request failure). Before insert, existing favorite actions for the same mutation key are deleted, so only the latest pending intent remains. Queued rows are deleted after successful sync; if sync fails due to connectivity/server issues, they remain pending for later retries. Invalid queued actions are dropped defensively during sync processing.