Sprint 3 - Moviles-G23-20262/General-Wiki GitHub Wiki
In this sprint we made CampusSwap usable on unreliable campus Wi-Fi. Every core flow now has an answer for losing the connection, from browsing and publishing to chatting, confirming the exchange and rating the other student. To support it we added local storage, caching, background synchronization and concurrency strategies. We also completed features started in Sprint 2: the real backend chat, account registration, form validation and server-side Smart Matching.
Eventual connectivity means the app keeps working when the connection drops or comes and goes, which is common on campus Wi-Fi (elevators, basements, crowded classrooms). For each scenario we describe the event, how the app responds, the anti-patterns we avoid, and where data is stored. Storage types follow the classification from the lectures (Local > Private > DB / Preferences / Files / Cache).
| Event description | The user opens Home, or is already on it, and the phone has no internet or the server can't be reached. |
| System response | Home shows the last listings downloaded from the server instead of an empty screen. An amber banner says "You're offline. Showing listings saved 5 min ago." When the connection returns, the banner disappears and the feed refreshes on its own, with no user action. |
| Possible anti-patterns | Lost content (empty feed), Blocked application, Non-informative message |
| Caching and retrieving strategy | Network falling back to cache |
| Storage type | Local > Private > DB (Room, table cached_materials) |
| Stored data type | Rows with each listing's fields, its position in the feed and the time it was cached |
| Author | Daniel Diab |
| Event description | The seller finishes the 4-step sell form and taps Publish while offline, or the connection drops while publishing. |
| System response | The listing is saved on the phone, and the confirmation screen says "Saved on your phone. We'll publish it automatically as soon as you reconnect, even if you close the app." The listing appears right away in the feed and in the seller's profile, labeled "Waiting to publish". When there is a connection, a background job sends it (POST /materials) and the real listing replaces the local copy. If the server rejects it, the listing shows the server's reason and a Discard option. |
| Possible anti-patterns | Lost content (lost form data), Stuck progress notification, Blocked application |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > DB (Room, table pending_listings) |
| Stored data type | One row per queued listing: title, description, course, price, condition, category, owner, attempts, failure reason |
| Author | Daniel Diab |
| Event description | While writing a listing, the user loses signal, leaves the screen or the OS kills the app. |
| System response | The form saves itself while the user types, about 0.4 s after they stop. When the user opens Sell again, the draft is restored with the message "We restored the draft you left unfinished." Closing the form asks whether to keep the draft or discard it. |
| Possible anti-patterns | Lost content (lost text) |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > Preferences (Jetpack DataStore, sell_draft) |
| Stored data type | The draft serialized as JSON (Gson) under one key |
| Author | Daniel Diab |
| Event description | The buyer or seller writes a message, or sends a photo, while offline. |
| System response | The message appears in the thread right away, with a cloud icon ("Waiting for connection"), and a banner explains it will be sent on reconnect. Messages wait in the outbox and are sent in order once the network is back (open room → POST /messages). The icon changes to a tick when the server has the message. Queued messages survive an app restart. |
| Possible anti-patterns | Lost content (lost message), Stuck progress notification, Non-informative message |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > DB (Room, table pending_messages) |
| Stored data type | One row per message: local id, owner, listing, content (text or uploaded photo URL), attempts, failure reason |
| Author | Daniel Diab (outbox and sync), Carla González Mina (real chat screen and photos) |
| Event description | The user opens a chat or the conversations inbox while offline, even right after restarting the app. |
| System response | The chat shows the conversation as it was the last time it loaded, with the counterpart's name, and the banner says "You're offline. Showing this conversation as it was 10 min ago. New messages are sent when you reconnect." The inbox does the same ("Showing your conversations as they were 10 min ago"). Messages still waiting in the outbox appear after the saved ones. The saved copy never marks messages as read and never shows an old unread count. If nothing was ever saved for that chat, the screen shows an offline state instead of an infinite spinner. Everything refreshes on its own when the connection returns. |
| Possible anti-patterns | Lost content (empty chat), Stuck progress notification, Blocked application, Non-informative message |
| Caching and retrieving strategy | Network falling back to cache |
| Storage type | Local > Private > DB (Room, tables cached_responses and pending_messages) |
| Stored data type | The last server response of each thread, of the inbox and of the counterpart's name, as JSON, keyed by user and listing, with the time it was saved |
| Author | Carla González Mina (offline state), Daniel Diab (saved threads and inbox) |
| Event description | The user opens the meeting point screen and the meeting points or the shared free times can't be downloaded. |
| System response | The app uses the meeting points loaded in this session or, after a restart, the copy saved on the phone, and only then the pre-mapped safe campus points bundled with the app. The screen says "Offline: showing the campus zones saved on this phone." The shared free times also come from the last saved copy, without the ones that already passed, so the user can still pick a place and a time and propose the meeting (see EC11). |
| Possible anti-patterns | Lost content (blank map, no times), Blocked application |
| Caching and retrieving strategy | Network falling back to cache, then to bundled data |
| Storage type | Local > Private > DB (Room, cached_responses) + bundled app data |
| Stored data type | List of meeting points (shared by every account) and the free times of each listing (per user) as JSON |
| Author | Carla González Mina (bundled points and offline state), Daniel Diab (saved copy and free times) |
| Event description | The app records analytics events (screen views, listings opened, messages, smart matches) while there is no connection, or the app crashes. |
| System response | Events are written to disk first and sent to the backend in batches of 20, or every 15 s. A batch that fails stays queued for the next attempt, so no business-question data is lost. |
| Possible anti-patterns | Lost content (lost data) |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > Files (pending.jsonl) |
| Stored data type | One JSON line per event |
| Author | Daniel Diab (disk queue), Carla González Mina (HTTP sink to the backend) |
| Event description | The user tries to log in or register while offline. |
| System response | The button shows progress only while the request is in flight. Then a clear message appears ("Can't reach the server. Check your connection and try again.") and the typed data stays in the form. |
| Possible anti-patterns | Stuck progress notification, Non-informative message, Lost content (lost text) |
| Caching and retrieving strategy | N/A |
| Storage type | N/A |
| Stored data type | N/A |
| Author | Daniel Diab |
| Event description | The buyer is at the meeting point and taps "I'm here — confirm with GPS" (or confirms by hand), but the phone has no signal, which is common inside buildings. |
| System response | The GPS check still runs on the phone. The confirmation, with the measured coordinates, is saved and the screen says "You're at the spot, but offline. The confirmation is saved on your phone and sent as soon as you reconnect." The rating step is unlocked right away. When there is a connection, the background job places and completes the order on the server (POST /exchanges/{id}/complete) and the screen changes to Confirmed on its own, even if the user left it. If the server refuses it, the screen shows the server's reason once and lets the user try again. |
| Possible anti-patterns | Lost content (lost confirmation), Blocked application, Stuck progress notification |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > DB (Room, table pending_actions) |
| Stored data type | One row per action: kind CHECK_IN, listing, owner, JSON payload (buyer, seller, price, meeting point, latitude, longitude), attempts, failure reason |
| Author | Daniel Diab |
| Event description | After the exchange, the user writes the rating (stars, tags, review) and taps Send rating while offline. |
| System response | The rating is saved on the phone and the screen says "You're offline. Your rating is saved on your phone and sent as soon as you reconnect." It is sent after the check-in of the same exchange, never before, because the server only accepts ratings for completed exchanges. If the app is restarted, the written rating is shown again from the saved copy. When it is delivered, the message changes to "Saved — it now counts toward their rating." |
| Possible anti-patterns | Lost content (lost review text), Non-informative message |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > DB (Room, table pending_actions) |
| Stored data type | One row with kind RATING and a JSON payload (rated user, stars, tags, review, confirmed condition) |
| Author | Daniel Diab |
| Event description | The user picks a meeting point and a free time and taps Propose while offline. |
| System response | The proposal is saved and the user goes back to the chat, where it appears as their own message ("Meeting proposal: Library, Mon 5 Oct 12:00 – 13:00") with the cloud icon. When there is a connection it is sent (POST /meeting-proposals) and replaced by the real proposal card. Before sending, the app checks the time hasn't already passed. If it has, the message says "Meeting proposal not sent: That time already passed before you got back online. Propose a new one."
|
| Possible anti-patterns | Lost content, Non-informative message, outdated data sent as if it were current |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > DB (Room, table pending_actions) |
| Stored data type | One row with kind PROPOSAL and a JSON payload (meeting point, time slot with ISO start and end) |
| Author | Daniel Diab |
| Event description | The user scrolls listings or opens a chat with photos while offline, including after closing the app. |
| System response | Photos that were seen before load from the phone, with no spinner and no data use. A listing photo that was never downloaded shows the category placeholder instead of an empty box. |
| Possible anti-patterns | Lost content (empty images), Stuck progress notification |
| Caching and retrieving strategy | Cache, then network |
| Storage type | Local > Private > Cache (memory LruCache + files in cacheDir/images) |
| Stored data type | Decoded bitmaps in memory and the original image bytes on disk, one file per URL |
| Author | Daniel Diab |
| Event description | The user opens the wishlist, the notifications or the alerts screen while offline, even after restarting the app. |
| System response | Each screen shows the copy saved the last time it loaded. Notifications say "You're offline. Showing the last notifications we loaded." Alerts say "You're offline. These are the alerts saved on this phone; changes need a connection." Changes to alerts show up immediately. If the server can't save one, it is undone and the screen says "We couldn't save that change to your alerts. Check your connection and try again." |
| Possible anti-patterns | Lost content, Non-informative message, Inconsistent state (a change that looks saved but isn't) |
| Caching and retrieving strategy | Network falling back to cache |
| Storage type | Local > Private > DB (Room, cached_responses) |
| Stored data type | Last server response of the wishlist, notifications and alerts as JSON, per user |
| Author | Daniel Diab |
In the Flutter app an offline situation reaches the code as a NetworkException thrown by ApiClient (no answer, DNS failure or the 20 s timeout). Until Sprint 2 every repository turned it into the message "Can't reach the server" and the user lost whatever was in progress. In Sprint 3 each core flow has an answer for it. Storage types follow the classification from the lectures (Local > Private > DB / Preferences / Files / Cache).
| Event description | The user opens Home, or is already on it, and the phone has no internet or the server can't be reached. |
| System response | Home shows the last listings downloaded instead of an empty screen. An amber banner says "You're offline. Showing listings saved 5 min ago." When the connection returns, the banner disappears and the feed refreshes on its own. If nothing was ever saved (first launch offline), the screen shows "Connect once to load listings" with a Retry button, not an endless spinner. |
| Possible anti-patterns | Lost content (empty feed), Blocked application, Non-informative message. |
| Caching and retrieving strategy | Network falling back to cache. |
| Storage type | Local > Private > DB (sqflite, table cached_responses) |
| Stored data type | One JSON row per user and screen (materials:) with the time it was saved. |
| Author | Sabrina Noguera |
| Event description | The user types in the search box or applies filters and sorting while offline. |
| System response | The search runs on the listings saved on the phone, so the filters and the SortStrategy keep working. A bar above the results says "Waiting for connection. Searching the listings saved on this phone." The empty state says whether it is empty because nothing matches or because nothing was saved. When the connection returns, the listings refresh and the results update by themselves. |
| Possible anti-patterns | Non-informative message ("No items found" with no reason), Blocked application, Lost content. |
| Caching and retrieving strategy | Network falling back to cache. |
| Storage type | Local > Private > DB (sqflite, cached_responses) |
| Stored data type | One JSON row per user and screen (materials:) with the time it was saved. (The listings JSON of EC1) |
| Author | !!!!! |
| Event description | The seller finishes the new-listing form and taps Publish while offline, or the connection drops while the photos are uploading. |
| System response | The photos are copied to the app's own folder and the listing is saved on the phone. The confirmation says "Saved on your phone. We'll publish it as soon as you reconnect." The listing shows at the top of My Listings with the label "Waiting to publish" (it is not in the public feed yet). When there is a connection, the sync service uploads the photos (POST /uploads) and then creates the listing (POST /materials), and the real listing replaces the local one. Photos already uploaded in a failed attempt are not uploaded again. If the server refuses it, the listing shows the server's reason with Retry and Discard. |
| Possible anti-patterns | Lost content form data and photos, Stuck progress notification, Blocked application |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > DB (sqflite, table pending_listings) + Local > Private > Files (pending_photos/) |
| Stored data type | One row per queued listing: client id, owner, title, description, course, price, condition, category, photo paths, already uploaded URLs, attempts, failure reason |
| Author | Sabrina Noguera |
| Event description | While filling the form, the user loses signal, switches to another app or the OS kills the app. Today the form lives only in TextEditingControllers and widget state, so everything is lost. |
| System response | The form saves itself about 0.4 s after the user stops typing, including the chosen photos. When the user opens the form again, the draft is restored with the message "We restored the draft you left unfinished." Closing the form asks whether to keep or discard the draft. The draft is deleted once the listing is queued or published. |
| Possible anti-patterns | Lost content such as text and photos |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > Preferences (shared_preferences, key sell_draft) + Files for the photos |
| Stored data type | The draft as one JSON string (title, description, course, price, condition, category, photo paths) |
| Author | !!!!! |
| Event description | A student fills the registration form, or logs in, and the phone has no connection. |
| System response | A "Waiting for connection" bar is shown while offline. The button shows progress only while the request is in flight; if it fails, the message says "Can't reach the server. Check your connection and try again." and everything typed stays in the form. The registration data is also saved while typing, so if the app is closed the student returns to the form already filled. Passwords are never written to disk: the student only has to type them again. |
| Possible anti-patterns | Lost content (lost text), Stuck progress notification, Non-informative message, storing credentials in plain text |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > Preferences (shared_preferences, key register_draft) |
| Stored data type | JSON with the non-sensitive fields (name, major, institutional email). Password and confirmation are excluded |
| Author | Sabrina Noguera. |
| Event description | The user opens the app with a valid token in secure storage, but there is no connection. Now, restoreSession() catches the NetworkException and returns null, which sends the user to the login screen. |
| System response | The app enters offline mode with the last saved profile, shows the offline banner and the saved screens, and lets the user keep writing messages and listings into the outbox. When the connection returns, it validates the token (GET /auth/me). If the server answers 401, the session ends and the login screen asks for the password again; queued items stay saved under their owner and are only sent by that user. |
| Possible anti-patterns | Blocked application (cannot get in without Wi-Fi), Lost content |
| Caching and retrieving strategy | Network falling back to cache |
| Storage type | Local > Private > Preferences (flutter_secure_storage, JWT, already used) + Local > Private > DB (cached_responses, key me:<userId>) |
| Stored data type | The JWT, and the last user profile as JSON |
| Author | !!!! |
| Event description | The buyer or seller writes a message while offline, or the connection drops while sending. |
| System response | Like WhatsApp: the message appears in the thread right away with a clock icon ("Waiting for connection") and a banner explains it will be sent when the connection returns. Messages wait in the outbox and are sent in order once the network is back (open room, then POST /messages). The icon changes to a tick when the server has it, and later to the existing read receipt. Queued messages survive an app restart. If the server refuses one, it shows "Not sent" with Retry and Delete, and the messages after it in that conversation wait, so they never arrive out of order. |
| Possible anti-patterns | Lost content (lost message), Stuck progress notification, Non-informative message |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > DB (sqflite, table pending_messages) |
| Stored data type | One row per message: client id (UUID), owner, chat room, content, creation time, attempts, status, failure reason |
| Author | Sabrina Noguera. |
| Event description | The user opens a chat or the conversations list while offline, even right after restarting the app. |
| System response | The chat shows the conversation as it was the last time it loaded, and the banner says "You're offline. Showing this conversation as it was 10 min ago. New messages are sent when you reconnect." The inbox does the same. Messages still in the outbox appear after the saved ones. The saved copy never marks messages as read and never shows an old unread count. The 4 s and 12 s polling timers are paused while offline (this also saves battery) and restart when the connection returns. If nothing was ever saved for that chat, the screen shows an offline state instead of an infinite spinner. |
| Possible anti-patterns | Lost content (empty chat), Stuck progress notification, Blocked application, Non-informative message |
| Caching and retrieving strategy | Network falling back to cache |
| Storage type | Local > Private > DB (sqflite, cached_responses and pending_messages) |
| Stored data type | The last server response of each thread and of the inbox as JSON, keyed by user and chat room, with the time it was saved |
| Author | Sabrina Noguera. |
| Event description | The user opens the meeting point screen and the zones or the shared free times can't be downloaded. |
| System response | The app uses the zones loaded in this session or, after a restart, the saved copy, and only then the safe campus zones bundled with the app. The screen says "Offline: showing the campus zones saved on this phone." The GPS distance and walking-time ranking keep working, because the sensor and the calculation are on the phone. The OpenStreetMap background may not load offline, so the markers are drawn over a plain background and the zone list below stays usable. Free times come from the last saved copy without the slots that already passed, so the user can still choose a place and a time and propose the meeting (EC12). |
| Possible anti-patterns | Lost content (blank map, no times), Blocked application |
| Caching and retrieving strategy | Network falling back to cache, then to bundled data |
| Storage type | Local > Private > DB (sqflite, cached_responses) + bundled app asset (assets/campus_zones.json) |
| Stored data type | The zone list (shared by every account) and the free times of each chat (per user) as JSON |
| Author | !!!!! |
| Event description | The buyer is at the meeting point, checks the item and taps confirm (with GPS), but the phone has no signal, which is common inside buildings. |
| System response | The GPS check still runs on the phone. The confirmation, with the measured coordinates and the received condition, is saved, and the screen says "You're at the spot, but offline. The confirmation is saved on your phone and sent as soon as you reconnect." The rating step is unlocked right away. When there is a connection, the sync service calls POST /exchanges/{id}/complete and the screen changes to Confirmed on its own, even if the user left it. If the server refuses it (for example because the exchange was cancelled), the screen shows the reason once. |
| Possible anti-patterns | Lost content (lost confirmation), Blocked application, Stuck progress notification |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > DB (sqflite, table pending_actions) |
| Stored data type | One row per action: kind CHECK_IN, exchange, owner, JSON payload (received condition, latitude, longitude), attempts, failure reason |
| Author | !!!!! |
| Event description | After the exchange the user is writing the rating (stars, tags, review) and the connection drops or the app closes, or taps Send rating while offline. |
| System response | While writing, the stars, tags and review are saved as a draft and shown again when the screen reopens. If the user taps Send offline, the rating is saved on the phone and the screen says "You're offline. Your rating is saved on your phone and sent as soon as you reconnect." It is sent after the check-in of the same exchange, never before, because the server only accepts ratings for completed exchanges. When it is delivered the message changes to "Saved. It now counts toward their rating." |
| Possible anti-patterns | Lost content (lost review text), Non-informative message |
| Caching and retrieving strategy | N/A |
| Storage type | Draft: Local > Private > Preferences (shared_preferences, rating_draft:<exchangeId>). Queued rating: Local > Private > DB (pending_actions) |
| Stored data type | Draft JSON (stars, tags, review). One row with kind RATING and a JSON payload (rated user, stars, tags, review) |
| Author | Andrés Botero. |
| Event description | The user picks a meeting point and a free time and taps Propose while offline. |
| System response | The proposal is saved and the user goes back to the chat, where it appears as their own message ("Meeting proposal: Library, Mon 5 Oct 12:00 – 13:00") with the clock icon. When there is a connection it is sent and replaced by the real proposal card. Before sending, the app checks that the time has not passed. If it has, the message says "Meeting proposal not sent: that time already passed before you got back online. Propose a new one." |
| Possible anti-patterns | Lost content, Non-informative message, outdated data sent as if it were current |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > DB (sqflite, pending_actions) |
| Stored data type | One row with kind PROPOSAL and a JSON payload (zone, start and end time in ISO format) |
| Author | !!!! |
| Event description | The phone has no Wi-Fi but has mobile data, and the user has not allowed the app to sync over mobile data. Many students have limited plans, and photos are the heaviest part of the app. |
| System response | A bar says "Waiting for Wi-Fi. Turn on 'Use mobile data' in Profile to send now." Browsing and reading still work, because they are small requests. The outbox (messages, listings with photos, check-ins, ratings) waits for Wi-Fi unless the user turns the option on, or taps "Send now over mobile data" for a single sync. The option is off by default and is remembered. |
| Possible anti-patterns | Non-informative message, Lost content (queued items that silently never leave), unexpected data use |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > Preferences (shared_preferences, key allow_mobile_data) |
| Stored data type | One boolean |
| Author | Sabrina Noguera. |
| Event description | The user scrolls listings or opens a chat with photos while offline, including after closing the app. |
| System response | Photos seen before load from the phone, with no spinner and no data use. A photo that was never downloaded shows the category placeholder instead of an empty box or an error. |
| Possible anti-patterns | Lost content (empty images), Stuck progress notification |
| Caching and retrieving strategy | Cache, then network |
| Storage type | Local > Private > Cache (Flutter ImageCache in memory + cached_network_image files in the cache folder) |
| Stored data type | Decoded images in memory and the original image bytes on disk, one file per URL |
| Author | !!!!! |
| Event description | The user opens the wishlist or the notifications while offline, even after restarting the app. |
| System response | Each screen shows the copy saved the last time it loaded. Notifications say "You're offline. Showing the last notifications we loaded." Tapping the heart already updates instantly; if the server cannot save the change, it is undone and the screen says "We couldn't save that change. Check your connection and try again." Opening a notification offline is also undone, so the unread dot never lies. |
| Possible anti-patterns | Lost content, Non-informative message, Inconsistent state (a change that looks saved but isn't) |
| Caching and retrieving strategy | Network falling back to cache |
| Storage type | Local > Private > DB (sqflite, cached_responses) |
| Stored data type | The last server response of the wishlist and the notifications as JSON, per user |
| Author | !!!! |
| Event description | The app records analytics events (screens, listings opened, searches, messages, meeting confirmed) while there is no connection or the app crashes. |
| System response | Events are written to disk first and sent in batches of 10, or every 30 s. A batch that fails stays queued for the next attempt, so no data is lost. Each event keeps the time it really happened (not the time it was sent) and a client UUID, so a retried batch never creates duplicates. |
| Possible anti-patterns | Lost data and duplicated data |
| Caching and retrieving strategy | N/A |
| Storage type | Local > Private > Files (pending.jsonl) |
| Stored data type | One JSON line per event |
| Author | !!!! |
-
Network monitoring.
NetworkConnectivityObserverlistens to Android'sConnectivityManagerdefault network and exposesisOnlineas aStateFlow. The UI collects it to show offline banners in Home, Sell and Chat, and the app uses it to react when the connection comes back. - Online-first with cache fallback (Home, chats, inbox, meeting points, free times, wishlist, notifications, alerts). The app always asks the server first and saves every successful answer. If the request fails, it shows the last saved copy and says how old it is. Copies are saved per user, so another account on the same phone never sees them.
- Offline-first writes with an outbox (publishing, chat, check-in, rating, meeting proposal). User actions are first written to Room and shown in the UI immediately. Sending them is a separate step, so the user never waits on the network or loses what they did.
-
Background synchronization with WorkManager.
OutboxWorkerruns only with aCONNECTEDnetwork constraint. It retries with exponential backoff and survives app restarts and reboots, so queued items are sent even if the app is closed when the signal returns. The work is unique (APPEND_OR_REPLACE), so a sync that is already running is never duplicated. - Clear ending for every item. Each queued item ends as sent (removed), retry later (no network, 5xx, expired session) or refused (4xx, shown with the server's reason). Messages to the same conversation stop at the first failure, so they never arrive out of order.
- Ordered delivery. The outbox sends listings, then messages, then exchange actions, oldest first, and stops at the first one that must wait. A rating never reaches the server before the check-in of the same exchange.
- Validate before sending late. A meeting proposal saved offline is checked again before it is sent; if its time already passed, it is refused on the phone with an explanation instead of sending an outdated proposal.
- Screens follow the outbox. Screens observe the outbox tables, so a check-in, a rating or a proposal changes from saved to confirmed by itself when the background job delivers it.
- Optimistic updates with rollback (smart alerts). Creating, pausing or deleting an alert shows up immediately. If the server doesn't confirm it, the change is undone and the user is told why.
-
Server-side Smart Matching. Alerts live on the server, which creates the
SMART_MATCHnotification when a matching listing is published. The match is not lost if the interested student's phone is offline or the app is closed at that moment; it shows up the next time notifications load. - Per-user outbox. Each queued row stores its owner. Only that user's session sends it, so another account logging in on the same phone never posts in someone else's name.
- Refresh on reconnect. When the connection returns, Home, the wishlist, the alerts and the notifications reload and the outbox is flushed immediately.
-
Network monitoring. A
ConnectivityServicewrapsconnectivity_plusand exposes one state (online,offline,waitingForWifi) as aValueNotifier. The interface type alone does not prove that the server is reachable, so aNetworkExceptionfromApiClientalso sets the state tooffline, and the next successful request returns it to 'online'. Screens listen to it to show the banners, and the app uses it to react when the connection returns.
Why: one source of truth, so no screen guesses its own connection state.
- Online-first with cache fallback (Home, search, chats, inbox, meeting points, free times, wishlist, notifications, profile). The app always asks the server first and saves every successful answer. If the request fails, it shows the last saved copy and says how old it is. Every load replaces the saved copy, so sold or removed listings disappear. Copies are saved per user.
Why: the server is the source of truth because listings and chats change often.
- Offline-first writes with an outbox (publishing, chat, check-in, rating, meeting proposal). The action is first written to sqflite and shown in the UI immediately. Sending it is a separate step, so the user never waits on the network and never loses what they did.
Why: these are the actions where losing the data costs the user the most.
-
Sync service.
OutboxSyncServiceflushes the outbox when the connection returns, the app comes back to the foreground (WidgetsBindingObserver), a retry timer with exponential backoff fires while the app is open, or aworkmanagertask runs while the app is closed (Android). Only one flush runs at a time.
Why: the student usually reconnects when the app is not even open.
-
Clear ending for every item. Each queued item ends as sent, retry later (no network, 5xx error, expired session) or refused (4xx error, shown with the server's reason). Messages to the same conversation stop at the first failure, so they never arrive out of order.
-
Ordered delivery with dependencies. The outbox sends listings, then messages, then exchange actions, oldest first. A rating never goes before the check-in of the same exchange.
-
Idempotent retries. Every queued item has a client UUID that is sent with the request. If a request reached the server but the answer was lost, sending it again must not create a second message or listing.
-
Draft auto-save with debounce. The new-listing form, the registration form (without passwords) and the rating are saved about 0.4 s after the user stops typing, and are restored when the screen opens again.
Why: the OS can kill the app at any time and the form state lives only in memory.
-
Validate before sending late. A meeting proposal saved offline is checked again before it is sent. If its time has passed, it is refused on the phone with an explanation.
-
Screens follow the outbox. Screens listen to the outbox, so a check-in, a rating or a proposal changes from saved to confirmed by itself when the sync service delivers it.
-
Optimistic updates with rollback (wishlist, notifications). The change shows immediately. If the server does not confirm it, the change is undone, and the user is told why. This is already how
MarketplaceState.toggleFavoriteworks. -
Per-user outbox. Each queued row stores its owner. Only that user's session sends it, so another account on the same phone never posts in someone else's name, and logging out never deletes another user's pending work.
-
Optional mobile data. The sync service checks the
allow_mobile_dataoption before it flushes. With it off and no Wi-Fi, items wait and the user is told why. -
Offline session start. If the token is stored but the network is down at launch, the app opens with the saved profile instead of the login screen, and validates the token when the connection returns.
-
Refresh on reconnect. When the connection returns, Home, the wishlist, the notifications, and the open chat reload, then the outbox is flushed immediately.
| Where | Strategy | Tool | Why | Author |
|---|---|---|---|---|
| Home listings | Network falling back to cache. Every successful response replaces the cache, so sold or removed listings disappear. | Room (cached_materials) |
Listings change often, so the server is the source of truth. The cache only guarantees the feed is never empty offline and keeps the same order as the server. | Daniel Diab |
| Photos (listings and chat) | Cache, then network, in two levels: memory, then disk, then download. |
LruCache with 1/8 of the heap, sized by each bitmap's bytes, + disk cache of 50 MB in cacheDir/images that removes the least recently used files (ImageCache, ImageDiskCache) |
Scrolling shouldn't download or decode the same photo again, which saves mobile data, battery and memory, and photos seen before still show offline. Photos are reduced to 1080 px before going into memory, and only bytes that decode as an image are saved to disk. | Daniel Diab |
| Chat threads, inbox and counterpart name | Network falling back to the last response for the same user | Room (cached_responses) |
A conversation must be readable offline, but the server is the source of truth, so every load replaces the saved copy. | Daniel Diab |
| Meeting points | Network falling back to the session copy, then to the saved copy, then to bundled data | In-memory copy + Room (cached_responses) + bundled points |
Safe campus spots rarely change, and the meeting flow must always offer a place. | Carla González Mina, Daniel Diab |
| Free times for a meeting | Network falling back to the last response, minus the times already past | Room (cached_responses) |
Lets the user propose a meeting offline without offering a time that is already over. | Daniel Diab |
| Notifications | Network falling back to the session copy, then to the saved copy for the same user | In-memory copy + Room (cached_responses) |
Notifications stay visible after a failed refresh or a restart, and are never shown to another user. | Carla González Mina, Daniel Diab |
| Wishlist and smart alerts | Network falling back to the last response for the same user | Room (cached_responses) |
The user can check what they saved and what they are waiting for, even offline. | Daniel Diab |
| Where | Strategy | Tool | Why | Author |
|---|---|---|---|---|
| Home listings and search | Network falling back to cache. Every successful response replaces the cache, so sold or removed listings disappear. | sqflite (cached_responses) |
Listings change often, so the server is the source of truth. The cache only guarantees the feed and the search are never empty offline. | Sabrina Noguera. |
| Photos (listings and chat) | Cache, then network, in two levels: memory, then disk, then download. | Flutter ImageCache (size set at start-up) + cached_network_image / flutter_cache_manager (limit of 200 files, 30 days) |
Scrolling should not download or decode the same photo again. It saves mobile data, battery and memory, and photos seen before still show offline. | !!!!! |
| Chat threads, inbox and counterpart name | Network falling back to the last response for the same user. | sqflite (cached_responses) |
A conversation must be readable offline, but the server is the source of truth, so every load replaces the saved copy. | <Author> |
| Meeting points | Network falling back to the session copy, then to the saved copy, then to bundled data. | In-memory copy + sqflite + assets/campus_zones.json
|
Safe campus zones rarely change, and the meeting flow must always offer a place. | Sabrina Noguera. |
| Free times for a meeting | Network falling back to the last response, minus the times already past. | sqflite (cached_responses) |
Lets the user propose a meeting offline without offering a time that is already over. | !!!!! |
| Wishlist and notifications | Network falling back to the last response for the same user. | sqflite (cached_responses) |
They stay visible after a failed refresh or a restart and are never shown to another user. | !!!! |
| Signed-in profile | Network falling back to the last saved profile. | sqflite (cached_responses) + secure storage for the token |
Lets the app open offline with a valid session. | !!!! |
Android allows real multi-threading. We use Kotlin coroutines with dispatchers so the main (UI) thread only renders, and any work that may block runs on another thread.
-
Dispatchers.Defaultfor CPU work. Mapping server DTOs to Room entities and to UI models (MaterialRepository), and turning saved responses to and from JSON (ResponseCache), run on Default, so large lists and long threads don't freeze scrolling. -
Dispatchers.IOfor blocking I/O. Image downloads, disk reads and decoding (ImageCache) and session persistence (SessionManagerscope) run on IO. -
One download per image, shared by everyone who asks. When several cards ask for the same photo at once,
ImageCachekeeps the download in progress in aConcurrentHashMap<String, Deferred>and every caller awaits the same job. The job runs in the cache's ownSupervisorJobscope, so a card that scrolls away doesn't cancel the download for the others. -
Thread-safe caches.
LruCacheis safe across threads. The disk cache uses@Synchronizedmethods, and writes go to a temporary file that is then renamed, so a photo is never half-written if the app dies. -
Room's own executor. All DAO calls are
suspendfunctions orFlows, so Room runs them off the main thread and the UI observes the outbox reactively. -
Reactive streams (
Flow/StateFlow). The network state, the outbox tables and the chat state are streams. The ViewModels collect them inviewModelScope, and Compose recomposes only what changed. -
Background worker (WorkManager +
CoroutineWorker). Outbox sync runs outside the UI lifecycle, in its own worker thread, with constraints and retries managed by the OS. It sends items one by one, in order, so actions that depend on each other never race. -
Debouncing with
collectLatest. Draft auto-save waits 400 ms after the user stops typing, and cancels the previous pending write, instead of writing to disk on every key. -
Periodic polling in a coroutine. The open chat refreshes on a timer inside
viewModelScope, and the job is cancelled when the screen closes. - Batching. Analytics events are sent in batches by size or time instead of one request per event.
-
Reacting to memory pressure. When the app goes to the background (
onTrimMemory), half of the image memory cache is released; the disk copy stays. -
Thread-safe callbacks. The
ConnectivityManagercallback runs on a system thread and only writes to aStateFlow, which is safe to read from the main thread.
Dart runs your code in one thread per isolate (the UI isolate), with an event loop. async/await does not block that thread while it waits for the network or the disk, but heavy calculations do. So we use Futures for waiting work and isolate for CPU work, so the UI isolate only renders.
-
async/awaitfor I/O. Network calls (http) and database calls (sqflite runs its queries on a native background thread) never block the UI. -
Isolates for CPU work (
Isolate.run/compute). Decoding and mapping large JSON lists (listings, long chat threads), encoding the saved responses to JSON, and resizing and compressing photos before they are uploaded or queued (max 1080 px). Without this, scrolling and typing would freeze on a mid-range phone. -
Parallel loading with
Future.wait. Listings and wishlist, and exchanges and notifications, already load at the same time (MarketplaceState.load,AccountState.load). Sprint 3 does the same for the photos of one listing (up to 3 uploads at once;Future.waitkeeps their order). -
Single-flight sync. The outbox flush has a lock (
Completer), so the "connection returned", "app resumed" and "timer" triggers can never run two flushes at once and send an item twice. -
Debouncing with
Timer. The draft auto-save waits 400 ms after the user stops typing and cancels the previous pending write; the search box waits 300 ms. -
Periodic polling with
Timer.periodic. The open chat refreshes every 4 s and the inbox every 12 s (already inChatState). -
Streams and listenables. The connection state is a stream (
onConnectivityChanged) that feeds aValueNotifier. The outbox and the states areChangeNotifiers, and the screens rebuild only what changed withListenableBuilder. -
Dropping stale answers.
MarketplaceStateandAccountStatealready use a_generationcounter, so an answer that arrives after a logout is ignored. The new states reuse it. -
Background task (
workmanager). The sync runs outside the app lifecycle, in its own isolate, with a "network connected" constraint and OS-managed retries. That isolate has to build its own database connection,ApiClientand token. On iOS the system decides when background tasks run, so there the sync is guaranteed only when the app is open or comes to the foreground. - Batching. Analytics events are sent in batches by size or time instead of one request per event.
-
Reacting to memory pressure.
didHaveMemoryPressureclears the in-memory image cache; the disk copy stays.
| Type | What is stored | Tool | Feature | Author |
|---|---|---|---|---|
| Local > Private > DB | Cached listings (cached_materials), queued listings (pending_listings), queued chat messages (pending_messages) |
Room (SQLite) + KSP | Offline Home, offline publishing, offline chat | Daniel Diab |
| Local > Private > DB | Queued check-ins, ratings and meeting proposals (pending_actions, payload as JSON) |
Room (SQLite) | Offline exchange steps | Daniel Diab |
| Local > Private > DB | Last server response per user and screen: chat threads, inbox, free times, meeting points, wishlist, notifications, alerts (cached_responses) |
Room (SQLite) + Gson | Offline chats and saved copies | Daniel Diab |
| Local > Private > Preferences | Session (JWT, user id, email, name) | Jetpack DataStore (session) |
Persistent login | Carla González Mina |
| Local > Private > Preferences | Unfinished listing draft as JSON | Jetpack DataStore (sell_draft) + Gson |
Sell draft auto-save | Daniel Diab |
| Local > Private > Files | Pending analytics events (pending.jsonl) |
App-specific files | Analytics pipeline | Daniel Diab |
| Local > Private > Files | Photos for listings being published offline (listing-photos) |
App-specific files (filesDir) |
Offline publishing with photos | Santiago Bernal |
| Local > Private > Cache | Downloaded photos, one file per URL (SHA-256 name), max 50 MB | cacheDir/images |
Photos offline | Daniel Diab |
We chose Room for data that is structured, queried and observed (lists of listings, messages and pending actions with states). For saved copies of server answers, cached_responses keeps one JSON row per user and screen: they are always read whole, so a table per screen would add no value. DataStore is for small key-value data that is read whole: the session and a single draft. A file is for an append-only log of events. Downloaded photos go to cacheDir, because Android may delete them when space is low and the app can always download them again.
The Room database went from version 3 to 4 with an explicit migration that creates cached_responses and pending_actions, so users who update the app keep their queued listings and messages.
| Type | What is stored | Tool | Feature | Author |
|---|---|---|---|---|
| Local > Private > DB | Queued chat messages (pending_messages), queued listings (pending_listings) |
sqflite (SQLite) | Offline chat, offline publishing | Sabrina Noguera. |
| Local > Private > DB | Queued check-ins, ratings and meeting proposals (pending_actions, payload as JSON) |
sqflite | Offline exchange steps | !!!!! |
| Local > Private > DB | Last server response per user and screen: listings, chat threads, inbox, free times, meeting points, wishlist, notifications, profile (cached_responses) |
sqflite + dart:convert
|
Offline browsing, search, chats and saved copies | !!!!! |
| Local > Private > Preferences | Unfinished listing draft (sell_draft) |
shared_preferences | Sell draft auto-save | !!!!! |
| Local > Private > Preferences | Registration draft without passwords (register_draft) |
shared_preferences | Account creation offline | Sabrina Noguera. |
| Local > Private > Preferences | Rating draft (rating_draft:<exchangeId>) |
shared_preferences | Rating offline | Andrés Botero. |
| Local > Private > Preferences |
allow_mobile_data option |
shared_preferences | Optional data use | Sabrina Noguera. |
| Local > Private > Preferences (encrypted) | Session token (JWT) | flutter_secure_storage (already used) | Persistent login | !!!! |
| Local > Private > Files | Photos of listings being published offline (pending_photos/) |
path_provider (documents folder) |
Offline publishing with photos | !!!!! |
| Local > Private > Files | Pending analytics events (pending.jsonl) |
path_provider |
Analytics pipeline. | !!!!! |
| Local > Private > Cache | Downloaded photos, one file per URL |
cached_network_image (cache folder) |
Photos offline | !!!!! |
We chose sqflite for data that is structured, queried, and has states (messages and pending actions with attempts and failure reasons). For saved copies of server answers, cached_responses keeps one JSON row per user and screen: they are always read whole, so a table per screen would add no value. shared_preferences is for small key-value data read whole: drafts and the data option. The token stays in flutter_secure_storage because it is a credential and is encrypted by the OS keystore, and drafts never contain passwords. Files are for photos (binary data does not belong in the database) and for an append-only log of events. Downloaded photos go to the cache folder because the OS may delete them when space is low and the app can always download them again, whereas queued photos go to the documents folder because they cannot be recovered.
| # | Feature | What it does | Files | Author |
|---|---|---|---|---|
| 1 | Account registration | Name, major, institutional email (.edu / .edu.co), password with confirmation, against POST /auth/register. Errors such as "email already taken" appear in the form, and a successful registration starts the session. |
screens/auth/RegisterScreen, data/auth/AuthRepository, data/remote/AuthApi, navigation/CampusSwapApp
|
Daniel Diab |
| 2 | Input validation and limits | Shared rules for every form: names letters-only, emails without spaces, passwords 8–72 characters with letters and numbers, prices $1.000–$5.000.000 COP formatted while typing, title and description minimums with counters, length limits in search, alerts, address and chat, max 5 units per cart item, address required for home delivery. |
domain/InputValidation, components/Formatting, screens/sell/SellScreen, screens/cart/*, screens/search/SearchScreen, screens/home/AlertsScreen
|
Daniel Diab |
| 3 | Copy/paste blocking | No copy, cut or paste menu in the auth and sell fields; passwords also reject pasting from the keyboard clipboard. |
components/NoClipboard, screens/auth/*, screens/sell/SellScreen
|
Daniel Diab |
| 4 | Filled bottom-bar icons | The active tab shows a filled icon and a bold label. |
components/CampusIcons, navigation/BottomNavBar, navigation/Destinations
|
Daniel Diab |
| 5 | Offline Home | Room cache of listings, offline banner showing the age of the saved copy, automatic refresh on reconnect. |
data/local/CampusSwapDatabase, data/materials/MaterialRepository, data/connectivity/ConnectivityObserver, components/OfflineBanner, screens/home/HomeScreen
|
Daniel Diab |
| 6 | Offline publishing | Outbox in Room plus OutboxWorker (WorkManager) that sends listings with POST /materials; "Waiting to publish" and "Not published" states with Discard. |
data/sync/*, data/local/CampusSwapDatabase, data/materials/MaterialRemoteDataSource, screens/sell/SellScreen, screens/product/ProductDetailScreen, components/ProductCard
|
Daniel Diab |
| 7 | Sell draft auto-save | The draft is saved to DataStore while typing and restored when the form is opened again. |
data/local/DraftStore, screens/sell/SellScreen
|
Daniel Diab |
| 8 | Real chat with the backend | Replaces the simulated chat: messages and meeting proposals come from /chatrooms and /messages, the open thread refreshes by polling, and messages are sent through the offline outbox. |
data/ChatRepository, data/ChatRoomRepository, data/remote/ChatRoomsApi, data/remote/MessagesApi, screens/chat/ChatViewModel, screens/chat/ChatScreen
|
Carla González Mina |
| 9 | Conversations inbox | List of the user's conversations with the last message and unread count, so sellers can open and answer conversations. |
data/ConversationsRepository, screens/chat/ConversationsViewModel, screens/chat/MessagesScreen
|
Carla González Mina |
| 10 | Photos in the chat | Send a photo from the camera or the gallery; it is uploaded with POST /uploads and shown in the thread. |
data/remote/UploadsApi, components/RemoteImage, screens/chat/ChatScreen, res/xml/file_paths.xml
|
Carla González Mina |
| 11 | Campus map | The meeting point screen draws the real campus, with buildings, streets, paths and green areas. |
screens/meeting/CampusGeometry, screens/meeting/CampusMapProjection, res/raw/campus_buildings.json, screens/meeting/MeetingPointScreen
|
Carla González Mina |
| 12 | Shared meeting point | The other person is anchored to a meeting point both sides can see, and the screen explains the point that is on screen. |
data/location/CounterpartLocationSource, screens/meeting/MeetingPointViewModel, domain/MeetingPointRanking
|
Carla González Mina |
| 13 | Contact-seller event | The first chat message is reported to the backend as contacting the seller, completing the data for BQ2. |
analytics/AnalyticsEvent, analytics/BackendEventMapper, screens/chat/ChatViewModel
|
Carla González Mina |
| 14 | Offline chats and inbox | Every chat thread, the inbox and the counterpart's name are saved per user, and shown offline with their age; saved copies never mark messages as read. |
data/local/ResponseCache, data/ChatRepository, data/ConversationsRepository, screens/chat/*
|
Daniel Diab |
| 15 | Offline exchange steps | Check-in, rating and meeting proposal are queued in pending_actions and sent in order by the outbox; the screens change to confirmed by themselves when they are delivered. Free times are saved so a meeting can be proposed offline. |
data/sync/PendingActions, data/sync/OutboxSync, data/sync/OutboxRepository, screens/rating/*, screens/meeting/MeetingPointViewModel, data/MeetingProposalRepository
|
Daniel Diab |
| 16 | Two-level photo cache | Memory LruCache plus a 50 MB disk cache, one shared download per photo, 1080 px reduction and 10 s timeouts. Used by listings and chat photos. |
data/local/ImageCache, data/local/ImageDiskCache, components/RemoteImage, CampusSwapApplication
|
Daniel Diab |
| 17 | Server-side Smart Matching | Alerts are saved on the backend (/smart-alerts). When a listing is published, the backend sends one SMART_MATCH notification to each student whose alert it matches (never to the seller). The app no longer computes match notifications on the phone. |
App: data/alerts/*, screens/home/AlertsScreen, data/AppViewModel. Backend: src/smart-alerts/*, src/materials/materials.service, Prisma model SmartAlert
|
Daniel Diab |
| 18 | Meeting confirmation event (BQ4) |
MEETING_CONFIRMED is reported once, when the proposal is accepted, with its chatRoomId, so the analytics backend can count the messages and time before the agreement. The backend enum was extended to accept it. |
screens/chat/ChatViewModel, analytics/BackendEventMapper, backend prisma/schema.prisma
|
Daniel Diab |
| 19 | Offline wishlist and notifications | Both lists are saved per user and shown after a restart without a connection; notifications also load in the background on log-in and reconnect, so the unread badge is up to date. |
data/wishlist/WishlistRepository, data/notifications/NotificationRepository, navigation/CampusSwapApp
|
Daniel Diab |
| # | Feature | What it does | Files | Author |
|---|---|---|---|---|
| 1 | Connectivity state and offline banners | Extends the existing connectivity state with the kind of connection, and shows the same offline banner in Home, chat, wishlist, and notifications (before, only Search had one); screens refresh when the connection returns. |
lib/Presentation/State Management/connectivity_state.dart, lib/Data/data_sources/connectivity_data_source.dart, lib/Presentation/Widgets/offline_banner.dart
|
!!!!! |
| 2 | Local database and response cache | sqflite database with pending_messages, pending_listings, pending_actions and cached_responses, and a cache that saves every server answer per user. |
lib/Data/local/app_database.dart, lib/Data/local/response_cache.dart
|
!!!!! |
| 3 | Offline Home and search | Home and search show the saved listings with the age of the copy, and a "Waiting for connection" message. |
lib/Presentation/State Management/marketplace_state.dart, lib/Presentation/Screens/home_screen.dart, lib/Presentation/Screens/search_screen.dart
|
Sabrina Noguera. |
| 4 | Offline publishing | Listings and photos are queued and published by the sync service, with "Waiting to publish" and "Not published" states. |
lib/Data/local/pending_listing_store.dart, lib/Presentation/Screens/new_listing_smart_screen.dart, lib/Presentation/ViewModels/smart_listing_view_model.dart
|
Sabrina Noguera. |
| 5 | Sell form draft | The form saves itself while typing and is restored. |
lib/Data/local/draft_store.dart, lib/Presentation/Screens/new_listing_smart_screen.dart
|
Sabrina Noguera. |
| 6 | Registration draft and offline messages | Registration data (without passwords) survives a closed app; login and registration explain the lack of connection. |
lib/Presentation/Screens/register_screen.dart, lib/Presentation/Screens/login_screen.dart
|
!!!!! |
| 7 | Offline session start | The app opens with the saved profile when there is no connection at launch. |
lib/Data/Repositories/auth_repository_impl.dart, lib/Domain/use_cases/restore_session_use_case.dart
|
!!!!! |
| 8 | Offline chat with outbox | Messages appear at once with a clock icon, wait in the outbox and are sent in order. |
lib/Data/local/pending_message_store.dart, lib/Presentation/State Management/chat_state.dart, lib/Presentation/Screens/chat_view.dart
|
Sabrina Noguera. |
| 9 | Offline chats and inbox | Threads and inbox are saved per user and shown offline with their age; the saved copy never marks messages as read. |
lib/Presentation/State Management/chat_state.dart, lib/Presentation/Screens/messages_screen.dart
|
!!!!! |
| 10 | Offline exchange steps | Check-in, rating and meeting proposal are queued in pending_actions and sent in order; the screens change to confirmed by themselves. |
lib/Data/sync/pending_actions.dart, lib/Presentation/Screens/complete_exchange_screen.dart, lib/Presentation/Screens/meeting_point_screen.dart
|
<Author> |
| 11 | Rating draft | Stars, tags and review are saved while the user writes. |
lib/Presentation/Screens/complete_exchange_screen.dart, lib/Data/local/draft_store.dart
|
<Author> |
| 12 | Offline meeting points and free times | Saved copy, then bundled campus zones; free times without the slots already past. |
lib/Data/Repositories/meetup_repositories_impl.dart, assets/campus_zones.json
|
!!!!! |
| 13 | Sync service | Flushes the outbox when the connection returns, when the app resumes, on a backoff timer and with workmanager; one flush at a time. |
lib/Data/sync/outbox_sync_service.dart, lib/main.dart
|
!!!!! |
| 14 | Optional mobile data | Profile setting that lets the sync use mobile data; off by default, with a "Send now" option. |
lib/Presentation/Screens/profile_screen.dart, lib/Data/data_sources/connectivity_data_source.dart
|
Sabrina Noguera. |
| 15 | Photo cache | Memory and disk cache for listing and chat photos, with a placeholder when a photo was never downloaded. |
lib/Presentation/Widgets/cached_photo.dart (replaces the Image.network calls) |
!!!!! |
| 16 | Offline wishlist and notifications | Both lists are saved per user and shown after a restart without a connection. |
lib/Presentation/State Management/marketplace_state.dart, lib/Presentation/State Management/account_state.dart
|
!!!!! |
| 17 | Offline analytics queue | Events written to disk first, sent in batches, with the real event time and a UUID. |
lib/Data/Repositories/analytics_event_repository_impl.dart, lib/Domain/use_cases/track_event_use_case.dart
|
!!!!! |
| Author | Features owned |
|---|---|
| Daniel Diab | Account registration, input validation and limits, copy/paste blocking, filled bottom-bar icons, offline Home (Room cache, network monitor, banners), offline publishing (outbox and WorkManager), sell draft auto-save, offline chats and inbox, offline exchange steps (check-in, rating, proposal), two-level photo cache, server-side Smart Matching (app and backend), meeting confirmation event, offline wishlist and notifications |
| Carla González Mina | Real chat with the backend, conversations inbox, photos in the chat, campus map, shared meeting point, contact-seller analytics event |