Sprint 4 - MOVILES-G22-2025/Wiki GitHub Wiki

List of features

Sprint 2

  • User authentication (sign in and sign up) requesting Uniandes email and additional students data.
  • Add a product for sale with its respective information using the phone's camera sensor and accessing the photo gallery.
  • List of available products with their respective details (name, description, images and seller).
  • Personalized filter recommendations by category based on user searches and clicks.
  • Efficient product search based on product name and description.
  • Personalized notifications when a user's favorite product drops in price.

Sprint 3

  • Product recommendations during key periods of university.
  • Profile page with routes to favorite products and products published by the user with the option to edit or delete that product.
  • List of the user's favorite products.
  • Chat page for communication between seller and buyer of a product.
  • Personalized notifications to inform the user about updates on active listings.

Sprint 4

  • View the seller's profile and rate them after a completed transaction.
  • Complete the purchase of a product.
  • Adds a second authentication step by sending a time-limited code to the user’s Uniandes email (OTP) for verify login.
  • Personalized notifications to inform the user about new messages on a chat.
  • Return process for sold products that do not match the specifications listed in the app.

Value proposition of Senemarket

SeneMarket is a university exclusive marketplace designed to support both students and certified campus vendors by providing a structured and reliable platform for buying and selling a wide variety of products, from academic materials to snacks, desserts, phone cases, and more. It promotes a circular economy and waste reduction. Leveraging its context-aware smart features, the platform adapts to the university calendar, prioritizing exam materials during exam weeks and academic supplies. Additionally, it optimizes product visibility based on time-of-day patterns, promoting food vendors during peak meal times and class transitions to maximize convenience for buyers and sellers. By ensuring a secure and trusted environment with verified university-affiliated sellers, SeneMarket enhances accessibility, boosts small student-led businesses, and fosters a dynamic, campus-focused economy tailored to the real-time needs of the community—offering a trusted, cost-effective, sustainable, and user-friendly solution tailored to the academic community’s needs.

Moreover, SeneMarket suggests personalized filters for each user based on past searches and surfaces trending products by analyzing click activity, in-app chats, and academic-week sales data (for example, identifying which semester week sees the highest demand for supplies in a specific program). It also tracks the influence of chat interactions on purchase decisions to continuously refine the platform. Users can create new product listings, add items to favorites, and edit their products or profile. Favorite items are accessible offline and users receive notifications when a favorite product’s price drops. To minimize the impact of connectivity issues on the user experience, core functions—such as browsing saved listings, drafting new offers, and accessing favorites—remain fully operational even without internet. Our revenue model combines non-intrusive in-app advertising, voluntary donations from supportive students, and optional premium listings that boost sellers’ visibility.

Business questions (BQs)

Sprint 2 and 3

No. Type Question Implemented in
1 1 How many users create an account and log in per week? Sprint 2
2 1 What are the busy hours for the marketplace usage? Sprint 2
3 2 Which filters should be suggested to the user based on their past searches? Sprint 2
4 2 Has a saved item’s price dropped or has a new listing appeared for a saved search? Sprint 2
5 2 Are there updates on an active listing? Sprint 3
6 3 Which features are used less than twice a month on average? Sprint 3
7 3 Which product categories receive the highest user engagement in terms of searches, listings, and transactions? Sprint 2
8 3 What percentage of users enable push notifications, and do they interact with them? Sprint 2
9 3 How frequently do users use the in-app messaging feature, and does it impact transaction completion rates? Sprint 3
10 4 What types of products are most frequently favorited by students from specific academic programs? Sprint 3
11 4 At what times during the semester do students from a specific program create the most academic products Sprint 3
12 5 Which features are used less than twice a month on average? Sprint 3

No. 11 Type 4 business question was changed since the one defined in Sprint 1 was not convenient since we have only one category per product.

Sprint 4

No. Type Question Implemented by
1 2 Are there any unread messages for the user? Sara Benavides
2 2 Has the user finished completing their profile information? Luisa Hernández
3 3 How did the authentication success rate and weekly user retention change after implementing the verification code? Daniela Castrillón
4 2 Has the seller received an in-app notification when a buyer marks an item as paid? Juan Jose Cediel
5 3 What is the total number of searches performed in the search bar per day? and what is their effect on product purchases? Mateo Calderón

Micro-optimization strategies

Flutter

This section documents key micro-optimization strategies applied to improve performance in the Senemarket Flutter app. These include UI rebuild reduction, resource-efficient widget reuse and smarter data handling.

Strategy Objective Affected component or class Justification
Memoization / caching for heavy logic Reduce recomputation of expensive operations HomePage, Firestore filters Caching the results of expensive filters or queries when the inputs don't change with each render. This saves CPU and bandwidth.
Debounce search inputs Prevent excessive API/filter calls SearchBar, ProductSearchViewModel Reducing the number of searches triggered while the user types. Reduce unnecessary calls.
Minimal and grouped setState calls Reduce unnecessary UI rebuilds EditProfilePage, HomePage Grouped related state changes inside a single setState block. Avoid massive rebuilds. This has a direct impact on UI fluidity and frame rates.
Use of const for static widgets Avoid unnecessary rebuilds SearchBar, ProfilePage, icons Reusing widget instances instead of recreating them. This improves FPS and memory usage.
Provider.of(context, listen: false) Avoid widget rebuilds when not needed NavigationBarApp, ChatPage Preventing rebuilds of widgets that do not depend on state changes. Improves widget tree efficiency.
Replace var with late final Reduce runtime memory allocation ProfilePage, EditProfilePage Preventing unnecessary lifecycles and reducing pressure on the garbage collector.
Conditional loading in _loadUserData Avoid redundant reads ProfilePage, EditProfilePage Fetching user data only when necessary to improve load performance. Reduces duplicate reads from Firestore or disk. Saves battery life and improves startup.

Before / after code examples

Each example below shows how the code was improved through a specific micro-optimization strategy.

  1. Memoization / caching for heavy logic

Before

final filtered = products.where((p) => matches(p, selectedFilters)).toList();

After

List<Product>? _cachedFiltered;
List<Product> getFiltered(List<Product> products) {
  if (_cachedFiltered != null) return _cachedFiltered!;
  _cachedFiltered = products.where((p) => matches(p, selectedFilters)).toList();
  return _cachedFiltered!;
}
  1. Debounce search inputs

Before

onChanged: (text) => viewModel.updateSearchQuery(text),

After

Timer? _debounce;
onChanged: (text) {
  if (_debounce?.isActive ?? false) _debounce!.cancel();
  _debounce = Timer(const Duration(milliseconds: 400), () {
    viewModel.updateSearchQuery(text);
  });
},
  1. Minimal and grouped setState calls

Before

setState(() => _name = value);
setState(() => _email = value);

After

setState(() {
  _name = value;
  _email = value;
});
  1. Use of const for static widgets

Before

Text('Hello'),
Icon(Icons.person),

After

const Text('Hello'),
const Icon(Icons.person),
  1. Provider.of(context, listen: false)

Before

final user = Provider.of<UserProvider>(context);

After

final user = Provider.of<UserProvider>(context, listen: false);
  1. Replace var with late final

Before

var userId = FirebaseAuth.instance.currentUser!.uid;

After

late final String userId = FirebaseAuth.instance.currentUser!.uid;

Conditional loading in _loadUserData

Before

final data = await repo.getUserData();
setState(() {
  name = data['name'];
  // ...
});

After

if (shouldReloadUserData) {
  final data = await repo.getUserData();
  setState(() {
    name = data['name'];
    // ...
  });
}

Performance before and after implementing micro-optimization strategies

This section visualizes the measurable impact of the applied micro-optimizations. All tests were run on a emulator and Flutter DevTools performance tab.

image

Metric Before optimizations After optimizations Change (%)
App launch time (ms) 2800 1950 ⬇️ 30.35
Average FPS 60 50 ⬆️ 16.66
Average memory usage (MB) 140 110 ⬇️ 21.42
Frame build time (ms) 22 14 ⬇️ 36.36
API calls per search 5 1 ⬇️ 80

Kotlin

A system trace profiling test was made. This was the results:

image

Based on these results we conclude:

Asynchronous Thread Management

By leveraging I/O dispatchers, the application effectively distributes computational tasks across multiple threads. This approach prevents blocking the main thread, thereby mitigating the risk of Application Not Responding (ANR) errors. The strategic use of threading ensures responsive user interactions and smooth background processing.

Intelligent Caching Mechanisms

The integration of Firebase and Glide caching libraries plays a crucial role in optimizing memory consumption. These technologies intelligently cache data and resources, significantly reducing redundant network requests and minimizing unnecessary object creation. This approach not only improves performance but also enhances the application's memory efficiency.

Potential Areas for Future Optimization

While the current implementation demonstrates strong performance characteristics, continuous monitoring and incremental improvements can further enhance the application's efficiency.

Eventual connectivity strategy(ies)

Caching and retrieving strategy options

No. Strategy
1 Cache, falling back to network
2 Network only
3 Network falling back to cache
4 Cached on network response
5 Cache then network
6 Generic fallback

Eventual connectivity scenarios

ID eventual connectivity scenario 1
Event description The user successfully logs in or registers with their email and password, but loses internet connection before the OTP email can be sent.
System response The app generates and stores the OTP code and timestamp locally. A flag is also saved to indicate that the email has not been sent. A listener from connectivity_plus monitors the network status. Once internet is restored, the app automatically attempts to resend the OTP email via the Firebase Cloud Function, without requiring any user intervention.
Possible antipatterns 2 (forcing the user to manually retry), 5 (not verifying email status before resending)
Caching and retrieving strategy 1, 5
Storage type Local NoSQL (Hive)
Stored data type Key-value pair with fields: otpCode, timestamp, pendingSend: true
ID eventual connectivity scenario 2
Event description The user wants to see a seller information (career or semester), but is not connected to the internet before clicking on the view seller option
System response The application consults the local database with the information of the users, there it brings the fields to load the information on the screen of the seller, so that the user is not affected at any time by his purchase or the fact that he wants information from the seller.
Possible antipatterns 4.3 (Lost Functionality), 4.5 (Lost text)
Caching and retrieving strategy 1, 5
Storage type Local SQLite
Stored data type Table in the database with user data
ID eventual connectivity scenario 3
Event description The user wants to know if there are new messages but the device was disconnected when a message was sent.
System response When a message is recieved it is cached locally with metadata (sender id, timestamp, read status). The app displays a local notification to indicate that there is a new/unread message. When the app reconnects, it syncs with firebase to retrieve any newer messages not yet cached and marked messages as "read" if the user has seen them. So, all unread messages are shown without requiring internet, and then updated/synced when online.
Possible antipatterns Lost content
Caching and retrieving strategy 1, 5
Storage type Room Database
Stored data type Table in the database with messages information 
ID eventual connectivity scenario 4
Event description A user marks a payment as completed while offline, and the app needs to sync this status with Firebase once internet connection is restored.
System response The app stores the payment status locally in Hive with a pending sync flag. A connectivity listener monitors network status. When internet is restored, the app automatically syncs the payment status to Firebase, updating the payment document with the new status and timestamp. The seller's app receives a real-time update through Firestore listeners.
Possible antipatterns 1 (requiring manual sync), 3 (not handling conflicts), 4 (losing payment data during sync)
Caching and retrieving strategy 1, 3, 5
Storage type Local NoSQL (Hive)
Stored data type Document with fields: paymentId, status: "completed", timestamp, buyerId, sellerId, pendingSync: true, syncAttempts: 0

Local storage strategy(ies)

Flutter

Considering the essential role of local storage in managing on-device data, its implementation is key to ensuring that, in scenarios of network unavailability, the application can continue functioning without disrupting the user experience. By maintaining access to critical data offline, the app guarantees continuity of its core functionalities under varying connectivity conditions.

The selection of local storage strategies was guided by the specific characteristics and persistence requirements of each data type. This alignment allowed for the adoption of the most suitable data structures (ranging from key-value stores to relational databases and secure storage) optimizing performance, reliability and scalability.

This deliberate approach not only simplified CRUD operations, but also ensured that stored data remained easily accessible and seamlessly integrated within the app’s runtime logic. As a result, performance was enhanced, data integrity was preserved and the overall responsiveness of the application was maintained.

The following table presents the local storage strategies implemented across different features, outlining what is stored, the rationale behind its storage and the corresponding storage mechanism used.

Feature What is saved? Why is it saved? Where is it saved?
Verify login with email-based OTP code OTP code (6 digits) The code is stored locally to allow the app to validate the user's input without needing to make another network request. This ensures that the verification step can be completed even when the device temporarily loses connectivity after the code is sent. It also supports retry mechanisms, avoids unnecessary code regenerations and allows the system to provide user feedback (for example, incorrect or expired code). local: Hive (Flutter key-value DB)
Verify login with email-based OTP code Timestamp of generation The timestamp is stored to control the validity window of the OTP (for example, 5 minutes). This ensures that the code cannot be reused indefinitely and prevents security risks such as code leakage or brute-force guessing. It also enables the app to provide a live countdown timer and automatically invalidate the OTP after expiration, enhancing both user experience and security. local: Hive
Verify login with email-based OTP code Email sent flag This flag is used to indicate whether the email was successfully sent. It prevents duplicate sends and allows the system to reattempt email delivery only when necessary (for example, when connectivity is restored). This reduces unnecessary API calls to the email service and ensures system resilience in cases of intermittent internet connectivity. local: Hive
View the seller's profile and rate them after a completed transaction Seller information such as first name, last name, career, semester, and previous grade In order to have the information stored on the device and if the user loses the connection, they can continue to see this information to help them orient themselves in the purchase process local: SQLite
Payment Status - Payment ID
- Status ("completed")
- Timestamp
- Buyer ID
- Seller ID
- Pending sync flag
- Sync attempts counter
Enable offline payment confirmation, Ensure payment status is not lost, Track sync status for eventual consistency, Handle retry attempts for failed syncs Hive Box: 'pending_payments'

Kotlin

In the Kotlin implementation of SeneMarket, Room serves as the primary local storage solution for structured and queryable data such as chat messages, return requests, and user-related information. Room abstracts the complexities of SQLite, providing type safety and compile-time query verification, which enhances both reliability and developer productivity.

For lightweight and key-value storage, SharedPreferences and EncryptedSharedPreferences are used. SharedPreferences stores non-sensitive flags and timestamps (e.g., last sync time or submission status), while EncryptedSharedPreferences securely handles sensitive user data such as credentials or personal preferences.

This hybrid local storage strategy enables the app to deliver a responsive, offline-first experience. Data like unread messages and product return requests can be stored and accessed locally, allowing users to continue interacting with key features even when internet connectivity is limited. Upon reconnection, the app synchronizes with Firebase, ensuring consistency and real-time updates across devices.

The following table summarizes how local storage is leveraged to support critical features in the app:

Feature What is saved? Why is it saved? Where is it saved?
Personalized notifications for new chat messages Message metadata (ID, sender ID, timestamp, read status) To allow the app to notify the user of new/unread messages even when offline. This ensures the user stays informed, supports quick local UI updates, and reduces unnecessary server calls. local: Room (SQLite)
Personalized notifications for new chat messages Last sync timestamp Used to determine when the last successful sync occurred. Helps avoid fetching redundant data and enables background syncing only when needed. local: SharedPreferences
Return process for sold products that do not match specifications Product return request (product ID, reason, timestamp, status) Enables users to submit and view return requests even when offline. Ensures the request is queued and automatically submitted when connectivity is restored. local: Room (SQLite)
Return process for sold products that do not match specifications Offline submission flag Indicates whether the return request has been successfully sent to the server. Prevents duplicate submissions and allows automatic retry upon reconnection. local: SharedPreferences

Multi-threading/concurrency strategy(ies)

Flutter

In Flutter, concurrency plays a fundamental role in maintaining a responsive and performant user interface by allowing asynchronous operations to execute without blocking the main UI thread. Although Flutter apps run in a single-threaded environment by default, Dart provides several mechanisms for managing concurrent tasks effectively.

The multi-threading/concurrency strategy chosen for each feature was based on:

  • The nature and duration of the task.
  • The need for real-time updates.
  • The potential impact on performance or responsiveness.
  • The ability to recover in the presence of delays, retries or background processing.

Below are the main concurrency mechanisms used in the app.

Feature Task Strategy used Why it was used?
Verify login with email-based OTP code Code generation and delivery Future with handle + Future with async/await Used to execute asynchronous tasks such as generating the OTP, storing it in local storage and sending it via HTTP to a Firebase Cloud Function. The handler structure is used in conjunction with a connectivity listener to defer the email-sending task when the device is offline. This ensures that the code delivery is retried automatically once connectivity is restored, without blocking the main thread.
Verify login with email-based OTP code Countdown timer Stream A stream is created using Timer.periodic to track the remaining validity time of the OTP in real time. This enables the UI to display a live countdown and automatically invalidate the code after 5 minutes. This approach avoids polling or blocking the main thread and provides reactive updates to the verification screen.

Kotlin

In Kotlin, concurrency is essential for delivering a responsive user experience, especially in mobile environments where tasks like network requests, local storage operations, and UI updates must coexist efficiently. Kotlin provides several tools for managing concurrency, primarily through coroutines, which allow asynchronous tasks to run without blocking the main UI thread.

The strategy for each feature was chosen based on:

  • The nature and expected duration of the task.
  • The need for responsiveness or real-time updates.
  • Whether the operation could continue or resume under intermittent connectivity.
  • The ability to handle background processing or retries safely.

Below is a breakdown of the concurrency strategies used in the Kotlin implementation for key features:

Feature Task Strategy used Why it was used?
Personalized notifications for new messages Fetching new messages, storing read status, scheduling notifications Kotlin coroutines with Dispatchers.IO and ViewModelScope Coroutines are used to handle background fetching of messages and storage operations without blocking the UI. Dispatchers.IO ensures that database or network operations run on a thread pool optimized for I/O. This allows the app to display new message alerts efficiently, even during background syncs.
Return process for sold products Submitting return requests and queuing them offline Coroutine + WorkManager WorkManager is used for persistent background tasks. If a return request is submitted offline, it’s queued and retried automatically once the device regains connectivity. This ensures reliability and persistence even if the app is closed or the device is rebooted.
Return process for sold products Reading/writing request data locally Kotlin coroutines with Dispatchers.IO Database operations such as saving the request locally or updating its sync status are handled using Dispatchers.IO to ensure the main thread remains free for UI updates.

Caching strategy(ies)

Flutter

Seller Profile Image Caching with cached_network_image

A caching approach that fetches the seller’s avatar from the network and stores it locally on the device to prevent redundant downloads and accelerate load times.

To optimize performance and minimize data usage, SeneMarket applies this mechanism to seller profile pictures using the cached_network_image plugin backed by flutter_cache_manager. Once an avatar is downloaded, it’s instantly available on subsequent views.

Libraries used

  • cached_network_image: Loads and displays images from the network with automatic caching.
  • flutter_cache_manager: Provides advanced file caching management; used internally by cached_network_image and extended here for profile images.

Modified and added files for profile image caching

File Type Description
custom_cache_manager.dart Edited Extended to include a separate cache key and a 30-day stale period for profile images.
seller_profile_image.dart Added New widget that wraps CachedNetworkImage to display seller avatars using the custom cache manager.
seller_profile_page.dart Edited Replaced Image.network with SellerProfileImage to enable caching on the profile screen.
edit_profile_viewmodel.dart Edited Calls cacheManager.removeFile(...) when the user uploads a new profile picture.
edit_profile_page.dart Edited Clears outdated cache and displays the updated avatar via CachedNetworkImage.

With this setup, seller profile images load faster and consume less data, delivering a consistently improved user experience across the app.

Kotlin

Efficient caching is vital for optimizing performance and ensuring a smooth user experience, especially in apps with intermittent connectivity or frequent content updates. In SeneMarket’s Kotlin implementation, we leverage Room for structured local data caching and Glide for image caching. These tools reduce redundant network requests, accelerate UI load times, and support offline access to essential data.

Personalized Notifications for New Messages

What is cached:

  • Last read message ID
  • List of recent messages from active chats

Why it's cached: To avoid re-fetching entire message histories when checking for new messages. Only unread messages are fetched from the server, reducing network load and speeding up notifications.

Caching strategy:

  • Room Database stores chat metadata and message IDs locally.
  • Repository pattern checks local cache before querying the network.
  • Cache is updated when connectivity is available, with fallback to local data if offline.

Libraries used:

  • Room: For structured caching of message and notification data.
  • Coroutines + ViewModelScope: To asynchronously manage cache refresh without blocking the UI.

Return Process for Sold Products

What is cached:

  • Return request data (reason, product ID, timestamp, image proof)
  • Submission status (pending/synced)

Why it's cached: Allows users to initiate return requests while offline. The data is stored locally and synced when the network is available, ensuring reliability and user trust.

Caching strategy:

  • Room Database stores return requests locally.
  • WorkManager retries syncing in the background.
  • The app shows locally cached request status to inform the user of progress.

Libraries used:

  • Room: For persistent caching of structured return data.
  • WorkManager: For background syncing of cached requests.
  • EncryptedSharedPreferences: Used to store secure flags (e.g., user authentication tokens for syncing).

With these caching strategies, SeneMarket ensures real-time responsiveness and seamless functionality even in low connectivity scenarios while minimizing data usage and supporting a more sustainable mobile experience.

Ethics video

https://drive.google.com/file/d/1fCfm15gpXR5k38QtBjpMTcGicm2s-jQi/view?usp=sharing

⚠️ **GitHub.com Fallback** ⚠️