Sprint 2 - Moviles-GN44/ISIS3510-GN44-wiki GitHub Wiki

Sprint 2 – App Implementation

flutter app

1. Business Questions

BQ Type Implemented by Platform Rationale
BQ5 – What is the completion rate of users scanning a QR code at a physical location to leave a review versus users searching manually to leave a review? Type 2 Felipe Boada Flutter Shows whether scanning the QR helps or hurts users finishing a review, since QR reviews are the most reliable data.
BQ3 – Which information do students use the most when comparing dining options: price, distance, estimated waiting time, menu availability, or dietary preferences? Type 2 Catalina Álvarez Flutter Helps identify which information students consult the most when comparing dining options, so the app can understand which criteria are more relevant during the decision process.
BQ6 – How much time on average does a student spend setting up filters such as price, distance, and dietary preferences before finally selecting a restaurant profile? Type 2 Juan José Ortiz Flutter Measures the friction students face when deciding where to eat with little time between classes. Comparing sessions with and without the "Suggested for you" filters shows whether the app makes that decision faster.
BQ9 – How often do students use the allergen and dietary restriction filters compared to the other filters in the app, and which restrictions are selected the most? Type 3 Juan José Ortiz Flutter Shows whether dietary filters are a feature students actually rely on when choosing where to eat, and which restrictions matter most, so the team can decide which dietary information to prioritize for each restaurant.

2. Analytics Pipeline

The analytics pipeline connects user interactions in the Flutter application with the Business Questions defined for Sprint 2.

The general process is:

User interaction → Event generation → Cloud Firestore → Processing → Aggregation → Business Question result

Analytics Pipeline Diagram

flowchart TD
    A[Student uses the Flutter app] --> B{User interaction}

    B --> C[BQ3 - Restaurant comparison]
    B --> D[BQ5 - Review flow]
    B --> T[BQ9 / BQ6 - Explore filters]

    C --> E[TelemetryService]
    E --> F[restaurant_comparison_criterion]
    F --> G[(Cloud Firestore - telemetry_events)]

    G --> H[BQ3AnalyticsService]
    H --> I[Filter BQ3 comparison events]
    I --> J[Group by criterion]

    J --> K1[Price]
    J --> K2[Distance]
    J --> K3[Waiting time]
    J --> K4[Menu]
    J --> K5[Dietary preferences]

    K1 --> L[Count interactions]
    K2 --> L
    K3 --> L
    K4 --> L
    K5 --> L

    L --> M[Calculate counts and percentages]
    M --> N[Identify most consulted criterion]
    N --> O[BQ3 result]

    D --> P[Review telemetry events]
    P --> G
    G --> Q[Review funnel analysis]
    Q --> R[Compare QR vs manual completion]
    R --> S[BQ5 result]

    T --> U[ExploreViewModel]
    U --> E
    E --> V[filter_applied]
    E --> W[filter_session_completed]
    V --> G
    W --> G

    G --> X[BQ9AnalyticsService]
    X --> X0[Filter filter_applied events]
    X0 --> X1[Count filter uses by type]
    X1 --> X2[Dietary share vs other filters]
    X1 --> X3[Most selected restriction]
    X2 --> Y[BQ9 result]
    X3 --> Y

    G --> Z[BQ6AnalyticsService]
    Z --> Z0[Filter filter_session_completed events]
    Z0 --> Z1[Average and median time to decide]
    Z0 --> Z2[Distribution by time range]
    Z0 --> Z3[With vs without suggestion]
    Z1 --> Z4[BQ6 result]
    Z2 --> Z4
    Z3 --> Z4
Loading

BQ5 events

Event When Params
review_flow_started Scanner opens –
review_method_selected Valid QR scanned or restaurant picked manually method: qr or manual
review_published Review saved method, verified

BQ5 uses events from the review flow to identify whether the user followed the QR or manual path and whether the review was eventually published.

These events can be used to compare completion between the two methods.

BQ3 events

BQ3 tracks interactions with information used by students while comparing restaurants.

These interactions are sent from the Flutter app to TelemetryService and stored in Cloud Firestore.

Collection flow:

User interaction → View / HomeViewModel → TelemetryService → Cloud Firestore (telemetry_events)

Event When Params
restaurant_comparison_criterion User checks the distance/location of a restaurant criterion: distance, restaurantId
restaurant_comparison_criterion User opens or consults the restaurant menu criterion: menu, restaurantId
restaurant_comparison_criterion User interacts with the alternative shown because of a high waiting-time context criterion: waiting_time, restaurantId
restaurant_comparison_criterion User consults restaurant menu information where prices are displayed criterion: price, restaurantId
restaurant_comparison_criterion User consults restaurant menu information where dietary tags are displayed criterion: dietary_preferences, restaurantId

The events are stored in the telemetry_events collection.

When an authenticated user is available, the user's UID can also be included in the telemetry event.

BQ3 Analytics Processing

The BQ3 pipeline does not stop after collecting the telemetry events.

BQ3AnalyticsService processes the stored information to obtain results for the Business Question.

Processing flow:

telemetry_events → Filter BQ3 events → Group by criterion → Count interactions → Calculate percentages → Identify most consulted criterion

The service considers the five BQ3 criteria:

  • Price
  • Distance
  • Waiting time
  • Menu availability
  • Dietary preferences

BQ3AnalyticsService reads events where:

eventName = restaurant_comparison_criterion

It then reads the criterion stored inside the event parameters and counts the interactions corresponding to each of the five supported criteria.

The service provides:

  • Interaction count for each criterion.
  • Total number of comparison interactions.
  • Percentage represented by each criterion.
  • Most consulted criterion.

Unknown criteria are ignored.

This transforms the collected telemetry into aggregated information that can be used to answer BQ3.

The interaction frequency is used as an indicator of which information students consult the most during the restaurant comparison process. It does not by itself prove that a criterion was the final reason for choosing a restaurant.

BQ9 events

BQ9 tracks which filters students apply on the Explore screen.

Collection flow:

Apply Filters → ExploreViewModel → TelemetryService → Cloud Firestore (telemetry_events)

Each tap on Apply Filters sends one filter_applied event per active filter. Events from the same search share an applyId. A search without filters is sent once with filterType: none.

Event When Params
filter_applied User applies a dietary filter filterType: dietary, value: vegan / gluten_free / lactose_free, applyId, filtersCount, userId
filter_applied User applies a walking time limit filterType: walking_time, value: 5 / 10 / 15, applyId, filtersCount, userId
filter_applied User applies a budget range filterType: price, value: min-max (e.g. 0-25000), applyId, filtersCount, userId
filter_applied User applies a payment method filterType: payment, value: Cash / Cards / Nequi, applyId, filtersCount, userId
filter_applied User applies a category filterType: category, value: Fast Food / Executive Lunch, applyId, filtersCount, userId
filter_applied User applies no filters filterType: none, value: none, applyId, filtersCount: 0

BQ9 Analytics Processing

BQ9AnalyticsService reads events where eventName = filter_applied and computes:

  • Number of uses of each filter type.
  • Share of all filter uses that are dietary filters.
  • Share of searches that used at least one dietary filter (grouping events by applyId).
  • Most used filter type.
  • Most selected dietary restriction.

BQ6 events

BQ6 measures how long a student spends setting filters before choosing a restaurant.

A session starts with the first interaction on the Explore screen (changing a filter, applying, or using the suggestion) and ends when the student opens a restaurant from the results.

Event When Params
filter_session_completed User opens a restaurant from the Explore results durationMs, filtersCount, filterChanges, searches, usedSuggestion, restaurantId, userId

BQ6 Analytics Processing

BQ6AnalyticsService reads events where eventName = filter_session_completed and computes:

  • Average and median time to decide.
  • Distribution of sessions: < 5 s, 5–10 s, 10–20 s, > 20 s.
  • Average time with and without the "Suggested for you" filters.
  • Average number of filter changes per session.

3. Architectural Design

3.1 Architecture

flowchart TB
    subgraph VIEW["VIEW"]
        direction LR
        v1[Home / Map]
        v2[Explore]
        v3[Restaurant detail]
        v4[QR scanner]
        v5[Write review]
        v6[Login / Register]
        v7[Profile]
        v7[Profile]
    end

    subgraph VIEWMODEL["VIEWMODEL"]
        direction LR
        vm1[HomeViewModel]
        vm2[CampusMapViewModel]
        vm3[ExploreViewModel]
        vm4[ScanQrViewModel]
        vm5[WriteReviewViewModel]
        vm6[AuthViewModel]
        vm7[ProfileViewModel]
    end

    subgraph MODEL["MODEL"]
        direction TB
        subgraph REPO["Repositories"]
            direction LR
            r1[RestaurantRepository]
            r2[CampusRestaurantsRepository]
            r3[ReviewRepository]
            r4[AuthRepository]
            r5[UserProfileRepository]
        end
        subgraph SERV["Services"]
            direction LR
            s1[UserLocationTracker]
            s2[TelemetryService]
            s3[Analytics services]
            s4[Recommendation strategies]
        end
        subgraph ENT["Entities"]
            direction LR
            m1[Restaurant]
            m2[Review]
            m3[AppUser]
            m4[RestaurantFilters]
        end
    end

    subgraph EXT["BACKEND AND DEVICE"]
        direction LR
        e1[(Firebase Auth)]
        e2[(Cloud Firestore)]
        e3[OpenStreetMap]
        e4[GPS · Camera · Light sensor]
    end

    VIEW -- "user actions" --> VIEWMODEL
    VIEWMODEL -- "notifyListeners()" --> VIEW
    VIEWMODEL --> MODEL
    MODEL --> EXT
Loading

Rationale

The Flutter app follows MVVM with a Repository layer, and uses Firebase as its backend.

  • View: the screens (Home/Map, Explore, Restaurant detail, QR scanner, Write review, Login/Register, Profile). They only draw the current state and send the user's actions to their ViewModel. They never access Firebase directly.
  • ViewModel: one per screen. It holds the screen state and its logic (selected restaurant, filters, location, loading and errors) and notifies the View when something changes through ChangeNotifier. Because it does not depend on widgets, it can be tested without the UI.
  • Model: the data and logic shared by the app.
    • Repositories hide where the data comes from (Firestore, Firebase Auth) and return the app's own types, so moving from mock data to Firestore did not change any screen.
    • Services hold logic used by several screens: location (UserLocationTracker), telemetry, analytics and recommendation strategies. This avoids duplicating it in each ViewModel.
    • Entities (Restaurant, Review, AppUser, RestaurantFilters) are the data types every layer shares.
  • Backend and device: Firebase is shared with the Kotlin app (Auth for @uniandes.edu.co accounts; Firestore for restaurants, reviews, user profiles and analytics events). OpenStreetMap provides the campus map, and the phone sensors provide location, photos and light.

Why this structure: each layer only talks to the next one. Members could work on different screens at the same time without conflicts, a screen can change without touching the data, and the data source can change without touching the screens.

Catalina – MVVM Contribution

Catalina contributed to the MVVM architecture through the restaurant comparison and recommendation flow.

Flow:

HomeScreen → HomeViewModel → RestaurantRepository / TelemetryService → Data and external services

HomeViewModel manages the selected restaurant, waiting-time context, alternative recommendation and BQ3 telemetry.

Restaurant data and recommendations are delegated to RestaurantRepository, while analytics events are delegated to TelemetryService.

This keeps UI responsibilities separated from state management, recommendation logic and external data access.

3.2 Architecture Design

sequenceDiagram
    actor S as Student
    participant V as View
    participant VM as ViewModel
    participant LT as UserLocationTracker
    participant R as Repository
    participant T as TelemetryService
    participant F as Cloud Firestore

    Note over V,F: Opening the app
    VM->>R: watchRestaurants()
    R->>F: restaurants.snapshots()
    LT->>LT: GPS strategy (fallback: building)
    F-->>R: restaurant documents
    R-->>VM: List<Restaurant>
    LT-->>VM: location changed
    VM->>VM: measure distances from the student
    VM-->>V: notifyListeners()
    V->>V: redraw map and "Near you"

    Note over S,F: Filtering on Explore
    S->>V: Apply Filters
    V->>VM: applyFilters()
    VM->>VM: filter and sort results
    VM-->>V: notifyListeners()
    VM-)T: filter_applied (BQ9)
    T-)F: telemetry_events
    S->>V: open a restaurant
    V->>VM: openRestaurant()
    VM-)T: filter_session_completed (BQ6)
    T-)F: telemetry_events

    Note over S,F: Publishing a review
    S->>V: Publish
    V->>VM: publish()
    VM->>R: addReview()
    R->>F: reviews.add() + update restaurant rating
    F-->>R: saved (or queued offline)
    VM-)T: review_published (BQ5)
    VM-->>V: notifyListeners()
    F-->>R: new rating via snapshots()
    R-->>VM: updated restaurants
Loading
flowchart LR
    MAPV["<b>Map / Home screen</b>"]
    EXV["<b>Explore screen</b>"]
    REVV["<b>QR + Review screens</b>"]
    AUV["<b>Login + Profile screens</b>"]

    HVM["<b>HomeViewModel</b><br/>CampusMapViewModel"]
    EVM["<b>ExploreViewModel</b>"]
    RVM["<b>ScanQrViewModel</b><br/>WriteReviewViewModel"]
    AVM["<b>AuthViewModel</b><br/>ProfileViewModel"]

    LOC["<b>UserLocationTracker</b><br/>GPS / Building strategy"]
    TEL["<b>TelemetryService</b>"]
    REPO["<b>Repositories</b><br/>Restaurant · Review · Auth · UserProfile"]

    FS[("<b>Cloud Firestore</b>")]
    FA[("<b>Firebase Auth</b>")]
    GPS["<b>GPS sensor</b>"]

    MAPV -- "method call" --> HVM
    EXV -- "method call" --> EVM
    REVV -- "method call" --> RVM
    AUV -- "method call" --> AVM

    HVM -. "notifyListeners()" .-> MAPV
    EVM -. "notifyListeners()" .-> EXV
    RVM -. "notifyListeners()" .-> REVV
    AVM -. "notifyListeners()" .-> AUV

    HVM -- "Future / Stream" --> REPO
    EVM -- "Stream" --> REPO
    RVM -- "Future" --> REPO
    AVM -- "Future / Stream" --> REPO

    LOC -. "location changed" .-> HVM
    LOC -. "location changed" .-> EVM
    GPS -- "position Stream" --> LOC

    HVM -- "fire-and-forget" --> TEL
    EVM -- "fire-and-forget" --> TEL
    RVM -- "fire-and-forget" --> TEL

    REPO -- "snapshots() / get / set" --> FS
    REPO -- "authStateChanges()" --> FA
    TEL -- "telemetry_events" --> FS
Loading

Rationale

Solid arrows are requests (one component asks another to do something); dotted arrows are notifications (one component tells another that something changed).

  • View → ViewModel (method call): each screen calls its ViewModel when the user acts, for example selectRestaurant, applyFilters, publish or signIn. Keeping the call in one direction means the screen has no business logic.
  • ViewModel → View (notifyListeners): ViewModels are ChangeNotifiers and the screens redraw through ListenableBuilder (Observer pattern). A ViewModel does not know which widgets use it, so several views can follow the same state; for example, the map and the restaurant preview both follow HomeViewModel.
  • ViewModel → Repository (Future / Stream): one-time operations return a Future (sign in, publish a review, save the preferred building). Data that changes over time arrives as a Stream: restaurants through Firestore snapshots() and the session through authStateChanges(). This makes the app reactive: a new rating or a sign-in reaches every open screen without reloading.
  • Shared location: UserLocationTracker reads the GPS position stream, or a campus building when the GPS is not available, and notifies both the map and Explore. Both screens measure distances from the same place without duplicating location logic.
  • Telemetry (fire-and-forget): ViewModels send BQ events to TelemetryService without waiting for a response, so analytics never slows down or blocks the user.
  • Firebase access in one layer: only the repositories and TelemetryService talk to Firestore and Firebase Auth. Firestore's local cache answers reads and queues writes when there is no connection, and syncs them when it returns, so the app keeps working offline.

3.3 Design Patterns

Category Pattern Where Responsible Why
Creational Singleton AuthRepository.instance Felipe The app has exactly one user session and one instance with a global access point.
Structural Adapter AuthRepository and ReviewRepository Felipe Firebase returns its own types and the repositories convert them into the app's types, so ViewModels can work with the data.
Behavioral Observer ChangeNotifier + ListenableBuilder and authStateChanges() stream Felipe Views subscribe to ViewModels and redraw when notified and AuthViewModel observes the session to react to sign-in and sign-out.
Behavioral Strategy RestaurantRecommendationStrategy + FasterRestaurantStrategy + SmartRestaurantStrategy Catalina The recommendation algorithm is encapsulated behind a common strategy interface. The repository can use different recommendation algorithms without changing the UI or repository flow. SmartRestaurantStrategy ranks alternatives using waiting time, distance, rating and average menu price.
Behavioral Strategy LocationStrategy + GpsLocationStrategy + BuildingLocationStrategy, used by UserLocationTracker Juan José Knowing where the student is can be done in different ways. The GPS strategy reads the phone sensor; the building strategy places the student at a campus building. The tracker starts with the GPS and switches at runtime to a building when the GPS fails (no permission, location off, low accuracy or a position outside campus), or when the student picks one. The map, the Explore screen and the distance calculation only receive coordinates and never depend on how they were obtained.

3.4 Architectural Tactics

Quality attribute Tactic Where Responsible
Availability Failover to local storage Reviews published without connection are stored by Firestore on the device and synced when the connection returns Felipe
Performance Caching The local cache answers review reads when offline Felipe
Performance Asynchronous processing Firebase calls and telemetry run asynchronously Felipe
Security Authentication Only @uniandes.edu.co accounts can publish reviews Felipe
Security Input validation QR payload must be an existing restaurant Felipe
Maintainability Modularization Features split into View / ViewModel / Repository layers Felipe
Maintainability Separation of concerns BQ3 interaction handling is separated between the View, HomeViewModel and TelemetryService instead of placing analytics logic directly in the UI Catalina
Performance Asynchronous processing BQ3 telemetry events are sent asynchronously to Firestore without placing the database operation directly in the UI Catalina
Maintainability Strategy-based recommendation Restaurant recommendation algorithms are separated from the repository through RestaurantRecommendationStrategy Catalina
Maintainability Analytics separation BQ3 aggregation is handled by BQ3AnalyticsService instead of mixing analytics processing with telemetry collection or UI logic Catalina
Availability Graceful degradation When the GPS is unavailable, the location falls back to the student's preferred building (or ML) instead of failing Juan José
Reliability Input filtering GPS readings with more than 50 m of error and positions outside campus are discarded so the location does not jump Juan José
Performance Caching Restaurants come from a real-time Firestore listener with local cache, so the map and Explore keep working offline Juan José
Usability Scope limitation The map camera is constrained to the campus bounds, keeping the app focused on nearby options Juan José
Maintainability Shared location service UserLocationTracker is shared by the map and Explore instead of duplicating location and distance logic Juan José
Maintainability Analytics separation BQ9 and BQ6 aggregation live in BQ9AnalyticsService and BQ6AnalyticsService, separate from collection and UI Juan José

4. Implemented Features

4.1 Felipe

Item Feature
a. Sensor QR scanner with real camera and flashlight to verify the visit
b. Type 2 BQ QR vs manual review funnel (BQ5)
c. Context aware Ambient light sensor suggests the flashlight under 15 lux
d. Smart Wait time estimated from student reviews
e. Authentication Firebase email login/sign-up (@uniandes.edu.co), guest mode
f. External service Reviews saved to and read from Cloud Firestore

4.2 Catalina

Item Feature
a. Sensor Camera access for adding a dish photo to a restaurant review, including photo selection, preview and removal before publishing
b. Type 2 BQ BQ3 comparison analytics to collect and aggregate the information students consult when comparing dining options
c. Context aware Detects a high waiting-time context using current/reported waiting information and offers the user a restaurant alternative
d. Smart Ranks restaurant alternatives using waiting time, walking distance, rating and average menu price
e. Authentication Associates BQ3 comparison telemetry with the currently authenticated user's UID
f. External service Sends BQ3 comparison telemetry to the telemetry_events collection in Cloud Firestore

Catalina – BQ3

BQ3: Which information do students use the most when comparing dining options: price, distance, estimated waiting time, menu availability, or dietary preferences?

The implementation covers both the collection and processing sides of the BQ3 analytics pipeline.

The five comparison criteria covered are:

  • Price
  • Distance
  • Estimated waiting time
  • Menu availability
  • Dietary preferences

When a comparison interaction occurs, the app records a restaurant_comparison_criterion event through TelemetryService.

The events are stored in the telemetry_events collection in Firestore.

BQ3AnalyticsService then processes these events by grouping them according to their comparison criterion.

For every criterion, the service can obtain:

  • Number of interactions.
  • Percentage of total comparison interactions.

It also calculates:

  • Total comparison interactions.
  • Most consulted criterion.

When an authenticated user is available, the user's UID can also be associated with the telemetry event.

This provides the processing required to transform the BQ3 telemetry into aggregated results.

Catalina – Sensor

The review flow includes access to the device camera so the user can take a photo of a dish when creating a restaurant review.

The user can:

  • Take a photo using the device camera.
  • Select a photo from the gallery.
  • Preview the selected photo before publishing.
  • Remove or replace the selected photo.
  • Continue publishing the review without a photo if the selection is cancelled.

This functionality was integrated into the existing WriteReviewScreen without changing the general review flow.

Catalina – Context-Aware Feature

The app uses the current waiting-time context of the selected restaurant to decide whether an alternative should be shown.

Recent student-reported waiting information is used when available. If there is no recent reported information, the restaurant's default waiting information is used as a fallback.

When the current waiting-time context is high (WaitTime.over15), the app makes an alternative restaurant available to the user.

If the waiting-time context is not high, the alternative is not shown.

This makes the recommendation dependent on the user's current restaurant context instead of displaying the same recommendation at all times.

Catalina – Smart Feature

The alternative restaurant is selected using multiple criteria instead of only one.

The recommendation considers:

  • Waiting time: 40%
  • Walking distance: 25%
  • Rating: 20%
  • Average menu price: 15%

The currently selected restaurant and closed restaurants are excluded from the alternatives.

The smart recommendation is implemented through SmartRestaurantStrategy, which implements the common RestaurantRecommendationStrategy interface.

RestaurantRepository uses the recommendation strategy instead of implementing the ranking algorithm directly.

This connects the smart feature with the Strategy design pattern while keeping the recommendation algorithm separated from the UI and repository flow.

Catalina – Authentication

BQ3 telemetry can be associated with the currently authenticated user.

The existing authentication flow provides access to the current user's UID. This value is injected into HomeViewModel through authenticatedUserId.

When a BQ3 interaction is recorded, HomeViewModel passes the current UID to TelemetryService, which can include it in the telemetry event.

No new authentication flow was created. The implementation uses the existing authentication system and integrates it with BQ3 analytics.

Catalina – External Service

BQ3 uses Cloud Firestore as an external backend service.

Comparison interactions are sent through TelemetryService and stored as documents in the telemetry_events collection.

Each comparison event contains information such as:

  • Event ID
  • Event name
  • Timestamp
  • Platform
  • Comparison criterion
  • Restaurant ID
  • Authenticated user ID when available

The collection flow is:

User interaction → View / HomeViewModel → TelemetryService → Cloud Firestore

The processing flow is:

Cloud Firestore → BQ3AnalyticsService → Aggregation → BQ3 result

Catalina – Analytics Contribution

Catalina implemented the collection and processing flow for BQ3.

TelemetryService is responsible for recording the comparison interactions in Firestore.

BQ3AnalyticsService is responsible for reading and aggregating the comparison events.

The analytics flow is:

Interaction → TelemetryService → telemetry_events → BQ3AnalyticsService → counts and percentages → BQ3 result

The analytics service recognizes:

  • price
  • distance
  • waiting_time
  • menu
  • dietary_preferences

It calculates the interaction count and percentage for each criterion and identifies the most consulted criterion.

This keeps telemetry collection and analytics processing as separate responsibilities.

Catalina – Architectural Pattern Contribution

The application follows an MVVM-based separation between the UI, application state and data/services.

Catalina's implementation contributes to this architecture through the restaurant comparison and recommendation flow:

HomeScreen → HomeViewModel → RestaurantRepository / TelemetryService

HomeViewModel manages:

  • The currently selected restaurant.
  • Current/reported waiting-time context.
  • Whether a high-wait context exists.
  • Access to the alternative restaurant recommendation.
  • BQ3 telemetry interactions.
  • The authenticated user ID used by BQ3 telemetry.

The ViewModel delegates restaurant access and recommendations to RestaurantRepository and analytics event recording to TelemetryService.

BQ3 result processing is kept separately in BQ3AnalyticsService.

This avoids placing recommendation, state-management, Firestore telemetry and analytics-processing logic directly inside the UI.

Catalina – Design Pattern Contribution

Catalina implemented the Strategy behavioral design pattern for restaurant recommendations.

The common interface is:

RestaurantRecommendationStrategy

The available implementations are:

  • FasterRestaurantStrategy
  • SmartRestaurantStrategy

RestaurantRepository depends on the strategy interface instead of containing a fixed recommendation algorithm.

The default implementation is SmartRestaurantStrategy, which ranks alternatives using waiting time, distance, rating and average menu price.

This allows the recommendation algorithm to be changed without modifying the UI flow or the repository's public recommendation method.

Catalina – View Contribution

Catalina contributed to WriteReviewScreen by integrating the dish-photo flow into the existing review view.

The view supports camera/gallery selection, photo preview, removal or replacement of the selected image, cancellation and publishing a review without requiring a photo.

This contribution preserves the existing review functionality while adding the sensor interaction required for Sprint 2.

4.3 Juan José

Item Feature
a. Sensor GPS shows the student's real position on the map and measures walking distance and time to each restaurant
b. Type 2 BQ BQ6: time students spend setting filters before choosing a restaurant, with and without the filter suggestion
c. Context aware "Near you": detects the campus building the student is in and highlights the restaurants reachable in 3 minutes
d. Smart Suggests the filters the student usually applies, learned from their own past searches
e. Authentication Preferred building saved in the student's account (users/{uid}) and a Profile that shows the signed-in student's real data
f. External service Real campus map with OpenStreetMap tiles and restaurants read in real time from Cloud Firestore

Juan José – BQ6 (Type 2)

BQ6: How much time on average does a student spend setting up filters such as price, distance, and dietary preferences before finally selecting a restaurant profile?

A filter session starts with the first interaction on the Explore screen and ends when the student opens a restaurant from the results. The app sends a filter_session_completed event with the duration, number of filters, filter changes, searches, and whether the suggestion was used.

BQ6AnalyticsService calculates the average and median time to decide, the distribution by time range, and the comparison with and without the suggestion. This shows how much friction students face and whether the suggestion reduces it.

Juan José – BQ9 (Type 3)

BQ9: How often do students use the allergen and dietary restriction filters compared to the other filters in the app, and which restrictions are selected the most?

Every tap on Apply Filters in the Explore screen sends one filter_applied event per active filter to telemetry_events. Events from the same search share an applyId.

BQ9AnalyticsService groups the events by filter type and by dietary restriction to obtain the share of dietary filters against the others, the share of searches that use a dietary filter, the most used filter type and the most selected restriction.

Juan José – Sensor

The app uses the phone's GPS through geolocator. Before reading the position it checks that location is enabled and asks for permission. Readings with more than 50 m of error and positions outside campus are ignored.

The position updates the student's marker on the map and the walking distance and time to every restaurant (straight-line distance with the haversine formula, multiplied by 1.3 for detours and divided by 80 m/min).

If the GPS cannot be used, the app falls back to the student's preferred building, and the student can also pick a building from the "Where are you?" sheet.

Juan José – Context-Aware Feature

The map adapts to the student's location without being asked. Using the current position (GPS or chosen building), the app finds the closest campus building within 120 m of its entrance and the restaurants reachable in 3 minutes on foot.

A "Near you" banner shows, for example, "Near RGD · 5 restaurants under 3 min · closest: Burger Play - RGD". Restaurants farther away are dimmed. The banner updates as the student walks, and tapping it selects the closest restaurant.

Juan José – Smart Feature

The Explore screen suggests the filters the student usually applies.

FilterSuggestionService reads the student's own filter_applied events, groups them by search, keeps the latest 10 searches, and suggests every filter that appears in at least half of them (and in at least two). With fewer than two searches there is no suggestion yet.

The suggestion appears as "Suggested for you · Vegan · < 10 min · Based on your last 4 searches" and is applied with one tap. It is hidden when those filters are already selected.

Juan José – Authentication

The student's preferred building is stored in their account in users/{uid}, using the same preferredBuilding field and codes (ML, SD, RGD…) as the Kotlin app. It is changed from the Profile screen and written with merge, so no other field of the document is removed.

After signing in, the preferred building becomes the location used when the GPS is not available. When the student signs out it goes back to ML.

The Profile screen shows the signed-in student's name, initials, email, number of published reviews (from the reviews collection) and preferred building, instead of the previous mock data.

Juan José – External Service

The map uses OpenStreetMap tiles through flutter_map, with the camera limited to the campus.

Restaurants are read from the Cloud Firestore restaurants collection in real time and mapped with Restaurant.fromFirestore (location, price, category, dietary options, payment methods and menu). Changes in Firestore, such as a new rating, reach the app without reloading.

Juan José – Architectural Pattern Contribution

Juan José built the campus map, the Explore screen and the Profile data flow as MVVM components: CampusMapViewModel, ExploreViewModel and ProfileViewModel hold the state, and CampusRestaurantsRepository and UserProfileRepository handle Firestore. Location logic lives in UserLocationTracker, shared by the map and Explore.

Juan José – Design Pattern Contribution

Juan José implemented the Strategy behavioral design pattern for the student's location.

The common interface is LocationStrategy, with the implementations GpsLocationStrategy and BuildingLocationStrategy.

UserLocationTracker is the context: it starts with the GPS strategy and switches to the building strategy when the GPS fails or the student picks a building. Consumers only receive coordinates, so a new way of locating the student can be added without changing the map or the distance calculation.

Juan José – View Contribution

Juan José implemented the Explore screen: filters by category, walking time, budget, dietary restriction and payment method, a list of matching restaurants ordered by distance with an empty state, and the "Suggested for you" card. He also built the real campus map (markers, "Where are you?" sheet, location controls and "Near you" banner) and the Profile screen with the signed-in student's data and the preferred building selector.

Kotlin app

1. Business Questions

BQ Type Implemented by Platform Rationale
BQ4 - How often do students open dish photos when they inspect a restaurant menu? Type 2 Karin Kotlin Shows whether dish photos get attention. The result is the share of menu inspection events where a student opened a photo
BQ6 - How much time on average do students spend configuring price, distance, and dietary filters before selecting a restaurant? Type 2 Samuel Kotlin Analyzes decision friction in restaurant discovery based on campus building origin, budget, and dietary preferences.
  • BQ4 was changed to make it more specific and measurable. The new version focuses on how often students open dish photos during menu inspection, which can be directly measured through telemetry events.

2. Analytics Pipeline

BQ4

aaaaaa drawio

When a student opens a dish photo, the app saves an event. The event goes through RestaurantViewModel and AnalyticsRepository, which adds a session ID. The app saves it in the background in Cloud Firestore (telemetry_events). A Python service reads the events from Firestore and keeps only the menu inspection events. Then it counts the events where a photo was opened and calculates the percentage: photo events divided by all menu inspection events. This percentage is the BQ4 result. The QR review events are also saved, but BQ4 does not use them.

BQ4 events

Event When Params
karin_bq_menu_inspection The student opens a dish photo in a restaurant menu "restaurant_id", "dish_id", "dish_count", "checked_photos", session_id
qr_review_submission The student scans the QR and sends or cancels the review "restaurant_id", "rating", "is_completed", "session_id"

BQ6

Analytics pipeline BQ6

When a student configures filters in ExploreScreen and clicks "Apply Filters", the app records the duration of the configuration session (in seconds) along with the selected origin building, budget range, and dietary restrictions (vegan, gluten-free). The event is dispatched via AnalyticsRepository with an active session ID and persisted to Cloud Firestore (telemetry_events). The Python analytics microservice (analytics_pipeline/app.py) queries Firestore, extracts all samuel_bq_filter_session events, computes the average duration in seconds, categorizes times into distribution buckets (<5s, 5-10s, 10-20s, >20s), and identifies the most consulted campus reference buildings (ML, RGD, Franco, SD, C, W).

BQ6 events

Event When Params
samuel_bq_filter_session The student adjusts dining filters and clicks "Apply Filters" "duration_sec", "building", "budget_range", "is_vegan", "is_gluten_free", session_id

3. Architectural Design

a. Architecture:

Overall System Structure

mermaid-diagram

Justification The application is organized into User Interface (UI), ViewModel, and repository layers, primarily following the MVVM pattern. Jetpack Compose screens do not access Firestore directly; instead, they use ViewModels that delegate access to the repositories. Firebase serves as the primary backend: authentication handles user management and password recovery, while Cloud Firestore stores data regarding restaurants, users, reviews, and telemetry events. The Python pipeline is decoupled from the Android application; it consumes events from "telemetry_events" and calculates BQ4/BQ6 metrics. This separation ensures that analytical processing does not depend on mobile presentation logic.

b. Architecture design:

Communication Between Main Components

mermaid-diagram (1)

Justification: Communication in the app goes in one direction: the screen talks to the ViewModel, the ViewModel talks to the repository, and the repository talks to the external service (Firebase). This way the screens do not know anything about Firebase or how the data is saved. For example, ReviewScreen does not write to Firestore: it sends the action to ReviewViewModel, which uses RestaurantRepository and AnalyticsRepository. The state comes back the other way with StateFlow: the repository or ViewModel updates it and the Compose screen redraws by itself. Because of this, changes like a new restaurant rating or a login state show up in the UI without extra code.

c. Design patterns and tactics:

Desing patterns

Category Pattern Where Responsible Why
Architectural MVVM ReviewScreen, ReviewViewModel, RestaurantRepository Karin The screen and the logic are separate, so we can test the logic.
Data access Repository PopularDishesRepository, RestaurantRepository.saveReview Karin The ViewModel does not know about Firestore
Creational Dependency Injection ReviewViewModel(analyticsRepository, restaurantRepository) Karin We can use mocks in the unit tests.
Behavioral Observer StateFlow + collectAsState in RestaurantDetailScreen Karin The screen updates when the data changes.
Structural Facade AnalyticsRepository.logEvent Karin One call hides the local list, the Firestore write and the HTTP send.
Architectural MVVM ExploreScreen, MapScreen, RestaurantViewModel Samuel The UI passively observes reactive ViewModels via StateFlow, separating GIS rendering and filter logic from data streams.
Data access Repository RestaurantRepository, AnalyticsRepository Samuel Acts as Single Source of Truth, handling in-memory caching and real-time synchronization with Cloud Firestore.
Behavioral Observer StateFlow + collectAsStateWithLifecycle in ExploreScreen & MapScreen Samuel The screen updates reactively when filters, distance thresholds, or reference buildings change without tight coupling.
Behavioral Strategy Dynamic filtering predicates & campus walking distance calculation in RestaurantRepository Samuel Interchangeable filtering strategies based on user criteria (origin building, max walking time, budget, dietary restrictions).
Structural Adapter AndroidBridge interface in MapScreen Samuel Bridges external Leaflet JavaScript map click events with native Jetpack Compose callback methods.

Architectural Tactics

Quality attribute Tactic Where Responsible
Security Authentication Password recovery through Firebase Auth reset email Karin
Security Input validation "validateEmail" is applied before "resetPassword" calls Firebase Karin
Security Limit exposure of information Generic reset message, so account existence is not revealed Karin
Security Least privilege Camera permission is requested at runtime only when the QR scanner opens Karin
Performance Asynchronous processing AnalyticsRepository.logEvent sends events to Firestore in the background, so the screen never waits for the network Karin
Performance Caching The BQ4 analytics reader caches the Firestore pages for 10 seconds Karin
Maintainability Separation of concerns Telemetry collection ("AnalyticsRepository") is separated from BQ4 processing ("bq4_analysis.py") Karin
Testability Dependency injection ViewModels receive repositories through the constructor, so JUnit tests use Mockito mocks Karin
Performance Caching RestaurantRepository maintains an in-memory cache of restaurants and menu items to prevent redundant Firestore network reads Samuel
Performance Pre-computation Pre-computed walking distance matrix per campus building (ML, C, RGD, Franco, SD, W) for instant client-side calculation Samuel
Security Data Minimization Campus location is determined through user-selected origin building without harvesting background GPS traces Samuel
Maintainability Layered Separation Cartographic Leaflet web engine is decoupled from native Compose navigation via strongly typed bridge interfaces Samuel

4. Implemented Features

We chose a over c

4.1 Karin

Item Feature
a. Sensor QR scanner with the real camera and a runtime camera permission request
b. Type 2 BQ Telemetry for dish photo inspection and QR visits, and the analytics that calculates the percentage
d. Smart "Popular with students" label: it recommends the most viewed dishes of a restaurant, using the real behavior of other users (telemetry events).
e. Authentication Password recovery by Firebase reset email, from the login screen
f. External service QR reviews saved in the backend (Cloud Firestore): rating, comment and user id go to the reviews collection, and the restaurant rating and review count are updated.

4.2 Samuel

Item Feature
c. Context-Aware Campus-aware dining discovery: The app dynamically recalculates walking times, contextual distance badges ("X min walk from [Building]"), and reachable eateries based on the student's selected campus reference building (ML, C, RGD, Franco, SD, W).
b. Type 2 BQ BQ6: Filter session duration tracking (samuel_bq_filter_session) measuring time spent configuring price, distance, and dietary criteria, processed by the Python Flask microservice to compute averages and distribution histograms.
d. Smart Interactive Campus Map with spatial building routing and multi-criteria constraint solver: cross-references geographic coordinates of campus buildings and restaurants with budget, dietary, and walking-time constraints.
e. Authentication Firebase Authentication: user registration, login, institutional email domain validation (@uniandes.edu.co), session persistence, and secure logout in ProfileScreen.
f. External service Google Cloud Firestore & GIS Web Tile Services: bidirectional synchronization of restaurant catalog and telemetry events to Cloud Firestore, along with real-time satellite tile streaming from Esri and OpenStreetMap.

5. Ethics Video

https://youtu.be/MWfdNHcyS5U

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