Skip to content

Sprint 3

Angie Gutiérrez Trujillo edited this page Oct 28, 2026 · 21 revisions

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 Scenarios

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).

Kotlin App

EC1. Browsing listings without a connection

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

EC2. Publishing a listing without a connection

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

EC3. Leaving the sell form unfinished (signal lost, app closed or killed)

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

EC4. Sending a chat message without a connection

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)

EC5. Opening a conversation without a connection

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)

EC6. Meeting points and free times without a connection

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)

EC7. Analytics events while offline

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)

EC8. Logging in or creating an account without a connection

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

EC9. Confirming the exchange at the meeting point without a connection

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

EC10. Rating the other student without a connection

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

EC11. Proposing a meeting point without a connection

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

EC12. Seeing photos without a connection

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

EC13. Wishlist, notifications and smart alerts without a connection

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

Flutter App

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).

EC1. Browsing listings without a connection

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

EC2. Searching without a connection

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 !!!!!

EC3. Publishing a listing without a connection

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 Angie Gutiérrez

EC4. Leaving the new-listing form unfinished (when signal gets lost, or app closed)

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 Angie Gutiérrez

EC5. Creating an account or logging in without a connection

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.

EC6. Opening the app without a connection when a session is stored

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 opens with the profile saved at the last sign-in instead of the login screen, and keeps the token so requests work as soon as the connection is back. When the connection returns or the app comes back to the foreground, it validates the token (GET /auth/me), replaces the profile with the fresh one and loads again what could not load offline. If the server answers 401, the session ends and the login screen asks for the password again; queued items stay saved under their owner. Fingerprint Quick Unlock also works offline for the enrolled account. Without a saved profile (never signed in on this phone), the login screen is shown.
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 signed_in_profile)
Stored data type The JWT, and the last user profile as JSON
Author Andrés Botero

EC7. Sending a chat message without a connection

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.

EC8. Opening a conversation or the inbox without a connection

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.

EC9. Meeting points and free times without a connection

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 Angie Gutiérrez

EC10. Confirming the exchange at the meeting point without a connection

Event description The buyer is at the meeting point, checks the item, picks the condition received, rates the seller and taps Complete exchange, but the phone has no signal, which is common inside buildings.
System response The confirmation (with the received condition) and the rating are saved on the phone, in that order, and the screen says "You're offline. It's saved on your phone and will be sent when you're back online." Reopening the exchange shows that it is saved and waiting. When there is a connection, the sync service calls POST /exchanges/{id}/complete and then sends the rating, and the orders are read again so the exchange shows as completed by itself, even if the user left the screen. If the server refuses the confirmation (for example because the exchange was cancelled), the rating is held back and the screen shows the reason with a Discard button.
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 step: kind completeExchange, exchange id as its group, owner, JSON payload (received condition), attempts and failure reason
Author Andrés Botero

EC11. Writing and sending a rating without a connection

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 checklist, received condition, stars, tags and review are saved as a draft 0.4 s after the last change (or when leaving the screen), and restored when the screen reopens ("Your unfinished rating was restored."). If the user taps Send offline, the rating is saved on the phone and the screen says "You're offline. It's saved on your phone and will be sent when you're back online." It is sent after the confirmation of the same exchange, never before, because the server only accepts ratings for completed exchanges. The draft is deleted once the rating is sent or queued.
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:<userId>:<exchangeId>). Queued rating: Local > Private > DB (pending_actions)
Stored data type Draft JSON (checked items, received condition, stars, tags, review). One row with kind rate and a JSON payload (rated user, stars, tags, review)
Author Andrés Botero

EC12. Proposing a meeting point without a connection

Event description The user has the meeting planner open (zones and free times already loaded), picks a meeting point and a free time, and taps Propose while offline.
System response The proposal is saved on the phone and the user goes back to the chat with the message "You're offline. The proposal is saved and will be sent to when you're back online." When there is a connection, the sync service sends it and the proposal card appears in the chat. Before sending, it checks that the time has not passed; if it has, the proposal is refused on the phone with "That time already passed. Propose another one." instead of being sent.
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 proposeMeeting, the chat as its group and a JSON payload (meeting point, start and end time in ISO format)
Author Andrés Botero

EC13. Only mobile data is available (optional data use)

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.

EC14. Seeing photos without a connection

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 Sabrina Noguera

EC15. Wishlist and notifications without a connection

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, with a banner "You're offline. Showing the copy saved 2h ago." and a Retry button. The lists load on their own, so they show even when the listings or the orders cannot load. Tapping the heart already updates instantly; if the server cannot save the change, it is undone and the screen says "Can't reach the server. Check your connection and try again." Opening a notification offline is also undone, so the unread dot never lies. After a change, the saved copy is refreshed so a restart does not bring back what was undone.
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 Andrés Botero

EC16. Analytics events while offline

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 Angie Gutiérrez

Eventual Connectivity Strategies

Kotlin App

  • Network monitoring. NetworkConnectivityObserver listens to Android's ConnectivityManager default network and exposes isOnline as a StateFlow. 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. OutboxWorker runs only with a CONNECTED network 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_MATCH notification 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.

Flutter App

  • Network monitoring. A ConnectivityService wraps connectivity_plus and exposes one state (online, offline, waitingForWifi) as a ValueNotifier. The interface type alone does not prove that the server is reachable, so a NetworkException from ApiClient also sets the state to offline, 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. OutboxSyncService flushes 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 a workmanager task 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 MarketplaceViewModel.toggleFavorite works.

  • 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_data option 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.

Caching Strategies

Kotlin App

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

Flutter

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. Sabrina Noguera
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. Sabrina Noguera
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, Angie Gutiérrez
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. Angie Gutiérrez
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. Andrés Botero
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. Andrés Botero

Multi-Threading / Concurrency Strategies

Kotlin App

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.Default for 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.IO for blocking I/O. Image downloads, disk reads and decoding (ImageCache) and session persistence (SessionManager scope) run on IO.
  • One download per image, shared by everyone who asks. When several cards ask for the same photo at once, ImageCache keeps the download in progress in a ConcurrentHashMap<String, Deferred> and every caller awaits the same job. The job runs in the cache's own SupervisorJob scope, so a card that scrolls away doesn't cancel the download for the others.
  • Thread-safe caches. LruCache is safe across threads. The disk cache uses @Synchronized methods, 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 suspend functions or Flows, 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 in viewModelScope, 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 ConnectivityManager callback runs on a system thread and only writes to a StateFlow, which is safe to read from the main thread.

Flutter App

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/await for 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 (MarketplaceViewModel.load, AccountViewModel.load). Sprint 3 does the same for the photos of one listing (up to 3 uploads at once; Future.wait keeps 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 in ChatViewModel).
  • Streams and listenables. The connection state is a stream (onConnectivityChanged) that feeds a ValueNotifier. The outbox and the states are ChangeNotifiers, and the screens rebuild only what changed with ListenableBuilder.
  • Dropping stale answers. MarketplaceViewModel and AccountViewModel already use a _generation counter, 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, ApiClient and 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. didHaveMemoryPressure clears the in-memory image cache; the disk copy stays.
  • Concurrency locks for file I/O (AnalyticsQueueLock). The analytics pipeline uses an isolate to write events to pending.jsonl (track()) and another isolate to sync and delete the file (syncNow()). To prevent race conditions where an event is lost if written during a sync deletion, a mutex lock ensures only one process accesses the file at a time, without blocking the asynchronous network upload.

Local Storage Strategies

Kotlin App

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.

Flutter App

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 (messages), Angie Gutiérrez (listings)
Local > Private > DB Queued exchange confirmations, ratings and meeting proposals (pending_actions, added in database version 2, payload as JSON) sqflite Offline exchange steps Andrés Botero
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 Angie Gutiérrez
Local > Private > Preferences Registration draft without passwords (register_draft) shared_preferences Account creation offline Sabrina Noguera.
Local > Private > Preferences Rating draft (rating_draft:<userId>:<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 Angie Gutiérrez
Local > Private > Files Pending analytics events (pending.jsonl) path_provider Analytics pipeline. Angie Gutiérrez
Local > Private > Cache Downloaded photos, one file per URL cached_network_image (cache folder) Photos offline Sabrina Noguera

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.

Features implemented this sprint, by author

Kotlin App

# 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

Flutter App

# Feature What it does Files Author Status
1 Local database and response cache sqflite database with pending_messages, pending_listings and cached_responses. Every successful server answer is saved per user with the time it was saved, so screens can show it later with its age. Includes an onUpgrade hook for future schema changes. lib/model/data_sources/local/app_database.dart, lib/model/data_sources/local/response_cache.dart Sabrina Noguera ✅ Implemented
2 LRU memory cache LruCache<K,V> built on a LinkedHashMap with a fixed capacity: a read moves the entry to the end, and when the capacity is exceeded the first (least recently used) entry is removed. It sits in front of the sqflite cache, so reopening Home or a chat is instant. Only the memory copy is evicted. lib/model/data_sources/local/lru_cache.dart, lib/model/data_sources/local/response_cache.dart Sabrina Noguera ✅ Implemented
3 Offline Home Home saves each successful listings response. Without a connection it shows the saved listings, an offline banner with the age of the copy, and refreshes by itself when the connection returns. On a first launch offline it shows "Connect once to load listings" with a Retry button. lib/view/screens/home_screen.dart, lib/view_model/marketplace_view_model.dart, lib/model/repositories/impl/marketplace_repositories_impl.dart, lib/view/widgets/offline_banner.dart Sabrina Noguera ✅ Implemented
4 Outbox and sync service Messages and listings are written to the local database first and sent later. The sync service flushes when the connection returns, when the app resumes and on a retry timer with backoff, one flush at a time. Each item ends as sent, retry later or refused with the server's reason. Rows belong to one user. The flush is launched as a Future with .then().catchError() handlers. lib/model/data_sources/local/outbox_store.dart, lib/model/services/outbox_sync_service.dart, lib/model/data_sources/remote_outbox_sender.dart, lib/main.dart Sabrina Noguera ✅ Implemented
5 Offline chat A message appears at once with a clock icon, waits in the outbox and is sent in order; it then shows a tick. A Stream<int> of pending messages feeds the chat. Polling is paused while offline. lib/view_model/chat_view_model.dart, lib/view/screens/chat_view.dart, lib/model/use_cases/pending_message_use_cases.dart Sabrina Noguera ✅ Implemented
6 Saved chats and inbox Each thread and the inbox are saved per user and shown offline with their age, followed by the messages still waiting to be sent. A saved copy never marks messages as read. lib/view/screens/messages_screen.dart, lib/view_model/chat_view_model.dart Sabrina Noguera ✅ Implemented
7 Sell draft auto-save The new-listing form is saved to preferences 0.4 s after the user stops typing, and restored (with its photos) when the form is opened again. It is deleted once the listing is published or queued. lib/model/repositories/impl/draft_repositories_impl.dart, lib/model/data_sources/local/draft_photo_store.dart, lib/view_model/smart_listing_view_model.dart, lib/view/screens/new_listing_smart_screen.dart Angie Gutiérrez ✅ Implemented
8 Offline publishing If publishing fails without a connection, the listing is saved in the local database and its photos in a local folder. The photos are compressed in an isolate (Isolate.run) and written to a temporary file that is then renamed. The listing shows as "Waiting to publish" in My Listings, and "Not published" with Discard if the server refuses it. lib/model/repositories/impl/pending_listing_repository_impl.dart, lib/core/isolates/photo_compressor.dart, lib/view_model/smart_listing_view_model.dart, lib/view/screens/seller_hub_screen.dart Angie Gutiérrez ✅ Implemented
9 Registration draft and offline messages Name, major and institutional email are saved while typing, so they survive a closed app. Passwords are never saved. Login and registration show "Waiting for connection" and keep everything typed. lib/view_model/register_view_model.dart, lib/model/repositories/impl/draft_repositories_impl.dart, lib/view/screens/register_screen.dart, lib/view/screens/login_screen.dart Sabrina Noguera ✅ Implemented
10 Photo cache Listing and chat photos are cached in memory and on disk (200 files, 30 days) with cached_network_image, with a category placeholder when a photo was never downloaded. This replaces the Image.network calls. lib/view/widgets/cached_photo.dart Sabrina Noguera ✅ Implemented
11 BQ4 conversation insight on real events The back-end creates a MEETING_CONFIRMED event when a meeting proposal is accepted. Messages keep the time they were written (clientCreatedAt). The analytics service computes messages and time to agreement on demand, shown on the BQ4 dashboard page and as the insight card in the chat. lib/view/widgets/conversation_insight_card.dart, lib/view_model/chat_view_model.dart, lib/model/repositories/impl/conversation_insight_repository_impl.dart Angie Gutiérrez, Sabrina Noguera ✅ Implemented
12 Optional mobile data The connection state also reports Wi-Fi or mobile data. A switch in Profile (off by default) lets the sync use mobile data; with it off and no Wi-Fi, the items wait and a bar says why, with a "Send now" option. lib/Data/data_sources/connectivity_data_source.dart, lib/Presentation/State Management/connectivity_state.dart, lib/Presentation/Screens/profile_screen.dart, lib/Data/sync/outbox_sync_service.dart Sabrina Noguera ❌ Not implemented
13 Rating draft The complete-exchange screen saves the checklist, received condition, stars, tags and review 0.4 s after the last change (Timer debounce) and when the screen closes, and restores them when it reopens. Per user and exchange; deleted once the rating is sent or queued. The screen logic moved to CompleteExchangeViewModel. lib/model/data_sources/local/draft_store.dart, lib/model/repositories/impl/draft_repositories_impl.dart, lib/view_model/complete_exchange_view_model.dart, lib/view/screens/complete_exchange_screen.dart Andrés Botero ✅ Implemented
14 Offline session start The profile is saved at every sign-in. Offline at launch, the app opens with it instead of the login screen and keeps the token; the token is checked when the connection returns or the app resumes, and a 401 signs out. Quick Unlock also works offline for the fingerprint account. lib/model/repositories/impl/auth_repository_impl.dart, lib/model/use_cases/restore_session_use_case.dart, lib/view_model/app_view_model.dart Andrés Botero ✅ Implemented
15 Offline exchange steps Confirming the exchange, rating and proposing a meeting are queued in pending_actions (database version 2, through the onUpgrade hook) when there is no connection, and sent by the sync service in order per exchange. A refused step holds back the rest and shows its reason with Discard; a proposal whose time passed is refused on the phone. Screens follow the outbox and change to confirmed by themselves. lib/model/repositories/impl/pending_action_repository_impl.dart, lib/model/use_cases/pending_action_use_cases.dart, lib/model/services/outbox_sync_service.dart, lib/view_model/complete_exchange_view_model.dart, lib/view_model/meeting_planner_view_model.dart, lib/view_model/account_view_model.dart Andrés Botero ✅ Implemented
16 Offline wishlist and notifications Both lists use network falling back to cache, saved per user in cached_responses, and show a "copy saved 2h ago" banner offline. They load apart from the listings and orders, so they show even when those fail. The copy is refreshed after a change. lib/model/repositories/impl/marketplace_repositories_impl.dart, lib/view_model/marketplace_view_model.dart, lib/view_model/account_view_model.dart, lib/view/widgets/saved_copy_banner.dart Andrés Botero ✅ Implemented
17 Average time to mark an item as sold (BQ 11) What is the average time (in days) it takes for a newly listed academic item to be marked as sold? files Sabrina Noguera ❌ Not implemented
18 Offline analytics queue Events are appended to pending.jsonl first and uploaded in batches, with their real time and a client UUID; the file being sent is swapped atomically, so new events keep being written. lib/model/services/analytics_sync_service.dart, lib/model/repositories/impl/analytics_event_repository_impl.dart, lib/model/use_cases/track_event_use_case.dart Angie Gutiérrez ✅ Implemented
19 Offline meeting points and free times Saved copy, then bundled campus zones; free times without the slots already past. lib/model/repositories/impl/meetup_repositories_impl.dart, assets/campus_zones.json, lib/model/rules/free_slot_expiry.dart Angie Gutiérrez ✅ Implemented
20 Average ticket by condition (BQ13) Endpoint joining Exchange and Material to calculate the global average ticket and grouped by condition. Displayed as a financial insight card in the Seller Hub. analytics/views.py, lib/view/widgets/average_ticket_card.dart, lib/view_model/average_ticket_view_model.dart Angie Gutiérrez ✅ Implemented

Summary by person Kotlin

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

Summary by person Flutter

# Feature Files Author Status
1 Local database and response cache lib/model/data_sources/local/app_database.dart, lib/model/data_sources/local/response_cache.dart Sabrina Noguera ✅ Implemented
2 LRU memory cache lib/model/data_sources/local/lru_cache.dart Sabrina Noguera ✅ Implemented
3 Offline Home lib/view/screens/home_screen.dart, lib/view_model/marketplace_view_model.dart, lib/model/repositories/impl/marketplace_repositories_impl.dart, lib/view/widgets/offline_banner.dart Sabrina Noguera ✅ Implemented
4 Outbox and sync service lib/model/data_sources/local/outbox_store.dart, lib/model/services/outbox_sync_service.dart Sabrina Noguera ✅ Implemented
5 Offline chat lib/view_model/chat_view_model.dart, lib/view/screens/chat_view.dart, lib/model/use_cases/pending_message_use_cases.dart Sabrina Noguera ✅ Implemented
6 Saved chats and inbox lib/view/screens/messages_screen.dart, lib/view_model/chat_view_model.dart Sabrina Noguera ✅ Implemented
7 Sell draft and offline publishing lib/model/repositories/impl/draft_repositories_impl.dart, lib/model/data_sources/local/draft_photo_store.dart, lib/view_model/smart_listing_view_model.dart, lib/view/screens/new_listing_smart_screen.dart, lib/model/repositories/impl/pending_listing_repository_impl.dart, lib/core/isolates/photo_compressor.dart Angie Gutiérrez ✅ Implemented
8 Registration draft lib/view_model/register_view_model.dart, lib/model/repositories/impl/draft_repositories_impl.dart, lib/view/screens/register_screen.dart, lib/view/screens/login_screen.dart Sabrina Noguera ✅ Implemented
9 Photo cache lib/view/widgets/cached_photo.dart Sabrina Noguera ✅ Implemented
10 BQ4 insight and MEETING_CONFIRMED lib/view/widgets/conversation_insight_card.dart, lib/view_model/chat_view_model.dart, lib/model/repositories/impl/conversation_insight_repository_impl.dart Angie Gutiérrez, Sabrina Noguera ✅ Implemented
11 Rating draft lib/view_model/complete_exchange_view_model.dart, lib/model/data_sources/local/draft_store.dart Andrés Botero ✅ Implemented
12 Offline session start lib/model/repositories/impl/auth_repository_impl.dart Andrés Botero ✅ Implemented
13 Offline exchange steps lib/model/repositories/impl/pending_action_repository_impl.dart, lib/model/services/outbox_sync_service.dart Andrés Botero ✅ Implemented
14 Offline wishlist and notifications lib/model/repositories/impl/marketplace_repositories_impl.dart, lib/view/widgets/saved_copy_banner.dart Andrés Botero ✅ Implemented
15 Offline analytics queue lib/model/services/analytics_sync_service.dart, lib/model/repositories/impl/analytics_event_repository_impl.dart, lib/model/use_cases/track_event_use_case.dart Angie Gutiérrez ✅ Implemented
16 Average time to mark an item as sold (BQ 11) What is the average time (in days) it takes for a newly listed academic item to be marked as sold? - files Sabrina Noguera ❌ Not implemented
17 Average ticket by condition (BQ 14) analytics/views.py, lib/view/widgets/average_ticket_card.dart, lib/view_model/average_ticket_view_model.dart Angie Gutiérrez ✅ Implemented
18 Offline meeting points and free times lib/model/repositories/impl/meetup_repositories_impl.dart, assets/campus_zones.json, lib/model/rules/free_slot_expiry.dart Angie Gutiérrez ✅ Implemented

PARA QUE ELIJAN SI QUIEREN FLUTTER

Flutter App

# 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 !!!!!

Clone this wiki locally