Repository navigation
Home
https://youtube.com/playlist?list=PLIambY0uc7Io&si=XtWiF_KQspTCbIws
ParchApp is a native Android application that helps university students plan activities together. A user can share their live availability, see when friends and roommates are free, compare the schedules of a whole group, join or propose group plans, and confirm activities with a group in a few taps.
This page documents the current state of the codebase after Sprint 2: what it is built with, how the architecture is organized, how the main flows work, how analytics answers our Business Questions, how the app connects to the shared backend, and how to build, run and test everything.
- Language: Java. There is no Kotlin anywhere in the project.
- Platform: native Android SDK, no cross platform framework.
- UI toolkit: XML layouts with AndroidX AppCompat and Material Components.
- Minimum SDK: 24 (Android 7.0). Target SDK: 37.
- Build system: Gradle with a version catalog (
libs.versions.toml). - Backend services: Firebase Authentication, Cloud Firestore and Firebase Analytics, added through the Firebase BoM so the three libraries always use compatible versions.
- Shared backend: the Backend repository, used by this app and by the Flutter app. It holds the Firestore security rules, the Cloud Functions and the BigQuery queries.
The app runs in one of two modes, and it chooses on its own at startup.
-
Firebase mode. When the file
app/google-services.jsonexists, Gradle applies the Google Services plugin, and the app uses Firebase Authentication, Firestore and Firebase Analytics. This file comes from the team's Firebase console and is never committed. -
Demo mode. Without that file, the app still builds and runs. It uses in memory repositories filled with the same demo groups as the backend, accepts any sign in, and writes analytics events to Logcat under the tag
ParchAnalyticsinstead of sending them to Firebase.
This way anyone on the team can clone the project and run it right away, and the screens never need to know which mode is active.
- View: the XML layouts. They contain no logic.
- Controller: the Activities and bottom sheets. They react to taps, call the model and update the views.
- Model: the domain classes, domain services and repositories.
Controllers never call Firebase directly. They only talk to repositories and services, which keeps each subsystem easy to replace and lets the team work on different screens at the same time. At system level the app follows a client server style: the app is the client, and Firebase is the server side.
Each kind of data has one interface: GroupRepository, ActivityRepository, AuthRepository and InsightsRepository. Each interface has two implementations, one for Firestore and one in memory for demo mode. Repositories creates them once at startup and hands them to the controllers.
Every result comes back through a Callback on the main thread, so the interface never waits for the network. FirestoreGroupRepository reads the local cache first and then the server. Offline persistence is turned on, so screens keep working without signal, and changes made offline are sent when the connection returns.
AvailabilityEngine intersects the busy blocks of every member of a group and finds the windows where members are free. It does not decide the order of those windows itself. It delegates that to a SlotRanker, and there are three:
-
OverlapRankingputs first the slots where the most members are free, then the longest ones. -
PreferenceRankingputs first the slots that best match the preferred hours of the free members. -
SoonestRankingputs first the earliest slots.
The Compare Availability sheet lets the user switch between them, and the engine receives the chosen one through setRanker(). A new criterion, for example one trained on the BQ3 results, only needs a new class. The calculation runs on a background thread with an ExecutorService, so the screen never freezes.
DashboardActivity, GroupDetailActivity and CreateGroupActivity implement GroupListener. Each screen subscribes to a group in onStart() and unsubscribes in onStop(), which avoids memory leaks. When the group changes, the repository notifies every open screen, and each one updates its own part: the RSVP badge and progress bar, the common free time, or the list of pending invitations. With Firestore this happens through a snapshot listener, so a change made by one person appears on everyone's screen without reloading.
StatusService follows the same idea for the quick status on the dashboard (free until and next activity) and for the availability status chosen in the status sheet. It saves these values on the device, so they survive when the app is closed.
AnalyticsTracker has a single shared instance, so every event in the app uses the same format. Screens only call track() or trackFeature(), and the tracker hides everything else:
- Events wait in
EventQueue, which is saved on the device, so events logged without signal are not lost if the app closes. -
ConnectivityMonitortells the tracker when the phone is online. - Events are sent in batches: when 10 are waiting, when the connection comes back, or when the app goes to the background. This saves battery.
- User IDs are replaced with a SHA256 hash before being sent, and no event includes calendar titles, places or attendees.
All source code lives under app/src/main/java/com/example/parchapp.
Screens (package root)
-
MainActivity: the Sign In screen and entry point. It signs in or creates an account throughAuthRepository, validates the email and password, sends password reset emails, and shows "Continue as" when a previous session was restored. -
DashboardActivity: the Home Dashboard hub. -
GroupDetailActivity: the detail screen of a group, Roomies Main St by default. -
CreateGroupActivity: the Propose Plan form, including invitations by email. -
ActivityCreatedActivity: the confirmation screen after creating an activity. -
AvailabilityActivity: legacy screen that nothing opens anymore (see Known Limitations). -
ParchApplication: starts the repositories and the analytics tracker when the app launches, and restores the signed in user. -
UserProfileandUserSession: the profile of the signed in user as shown in the interface.
Bottom sheets (package root)
-
QuickStatusSheet: edits the free until and next activity values of the dashboard. -
AvailabilityStatusSheet: changes the availability status the user broadcasts. -
CalendarSheet: shows the weekly calendar and synced free blocks. -
PlanDetailsSheet: shows the details of a confirmed plan. -
GroupDetailsSheet: a reusable summary for any group. -
AllGroupsSheet: lists every group the user belongs to. -
CompareAvailabilitySheet: calculates and shows the best common free slots of a group. -
CreateActivitySheet: a short form to create an activity, opened from the dashboard, the group screen, or directly from a slot in Compare Availability.
domain
-
model:Group,Member,TimeBlock,TimeSlotandPlannedActivity. Times of day are stored as minutes since midnight. -
availability:AvailabilityEngine,SlotRankerand the three rankings. -
StatusService: the user's own availability status.
data
- The four repository interfaces, their Firestore and in memory implementations,
BaseGroupRepository(the shared observer bookkeeping),GroupListener,DemoDataandRepositories.
analytics
-
AnalyticsTracker,AnalyticsEvents(every event and parameter name),EventQueue,ConnectivityMonitor, and the two destinationsFirebaseAnalyticsSinkandLogcatSink.
util
-
CallbackandMainThread.
Resources live under app/src/main/res: layout for screen and sheet XML files, drawable for vector icons and shape backgrounds, values for colors.xml and strings.xml, and mipmap for the launcher icon.
Sign In leads to the Home Dashboard, the central hub, with a bottom navigation bar (Home, Groups, Schedule) and a floating button to create a new activity. From the dashboard, most actions open a bottom sheet instead of a new screen, and the Roomies card opens the Group Detail screen. From Group Detail, Propose Plan opens the Create Group form, which leads to the Activity Created screen when it is submitted. That screen offers two exits: continue forward, or go back to the dashboard.
For a breakdown of each screen and the UI and UX concepts applied on it, see the Implemented Views wiki page.
Sign in. MainActivity sends the email and password to AuthRepository. In Firebase mode, Firebase Authentication checks them, keeps the session token and restores it on the next launch, so returning users only tap "Continue as". The app never stores passwords.
Compare availability and create an activity.
- The user opens Compare Availability from the dashboard or from a group.
- The sheet gets the group from
GroupRepositoryand asksAvailabilityEnginefor the common slots, using the chosen ranking. - The engine runs on a background thread, measures how long the calculation took and logs it (BQ1).
- The sheet shows the best slot and up to two alternatives. The slot position that organisers accept most often is marked as Recommended; that position comes from
InsightsRepository(BQ3). - When the user schedules a slot,
CreateActivitySheetopens with the time already filled in. When the activity is created, the app logs whether the suggested slot was kept exactly as proposed (BQ3) and opens the Activity Created screen.
RSVP. Tapping Going or Maybe on the dashboard calls GroupRepository.setRsvp(). The button does not change the view itself. The view updates when the repository notifies the change, which is the same path a change from another person follows.
Each implemented Business Question has its own event. The names live in AnalyticsEvents.java, and the BigQuery queries depend on them, so they must not change without changing the queries.
-
BQ1, App Telemetry. Event
availability_calc, logged by the engine with the group size, the calculation time in milliseconds, the number of slots and the ranking used. It answers how fast the engine is for groups of different sizes. -
BQ3, Direct User Experience Improvement. Event
slot_accepted, logged when an activity is created from a suggested slot, with the slot position and whether the organiser changed it. A nightly Cloud Function writes the answer to the Firestore documentinsights/bq3_slot_acceptance, and the app reads it to mark the Recommended slot. This closes the Type 2 feedback loop. -
BQ8, Feature Analysis. Event
feature_used, logged every time someone opens Groups, Compare Availability, Schedule, Invitations or Alerts, together with the screen it was opened from. It shows which coordination features are used least.
The full pipeline is: every event goes through AnalyticsTracker, waits in the local queue, is sent in batches to Firebase Analytics, is exported every day to BigQuery, and is aggregated by one query per Business Question. BQ1 and BQ8 are meant for the team dashboard in Looker Studio, and BQ3 goes back into the app through Firestore.
The backend is shared with the Flutter app and lives in its own Backend repository. It contains:
- Firestore security rules. Only members of a group can read it. A member can only change their own RSVP and the pending invitations. Only the backend can create groups or change who belongs to them. A new activity must come from a member of its group, carry that person's own user ID and use the server time. The insights documents are read only for the apps.
-
Cloud Functions.
refreshSlotRecommendationruns the BQ3 query every night and writes the recommended slot position. It keeps position 0 until at least 20 accepted slots exist, so a few events cannot change the recommendation.joinDemoGroupsOnSignUpadds every new account to the demo groups, so a new user can RSVP and create activities right away. -
A seed script that writes the five demo groups (Roomies Main St, Engineering 2026, Campus Intramurals, Thesis Support Group and University Choir) with the same data as
DemoData.java. - The BigQuery queries for BQ1, BQ3 and BQ8.
- The data model document, which lists every Firestore field, who is allowed to write it, and examples in Java and Dart. Both apps must follow it.
Colors are centralized in colors.xml. The primary brand color is a deep blue (#003D9B), complemented by a success green, a purple, a rose and an amber accent used to color code group avatars, status badges and category icons.
Icons are custom vector drawables built for this project (roughly two dozen), since the original mockups referenced a web icon font that native Android cannot load directly. Reusable shape drawables cover the recurring visual patterns: rounded cards, selected and unselected chips, pill shaped buttons, circular avatars and progress bars.
Typography uses the system default sans serif font, with weight and size varied per component. No custom font family has been bundled yet.
While testing on an emulator, every button rendered in a generic purple instead of the brand blue, even though each one had its own background drawable in XML. The cause was that the app theme extends Theme.Material3, which makes Android replace every plain Button with a MaterialButton when the layout is inflated. MaterialButton ignores a manually set android:background and falls back to the Material 3 default color.
The fix, applied in every layout, was to declare buttons as androidx.appcompat.widget.AppCompatButton, which is not replaced and therefore respects the custom drawables. Toggle switches follow the same reasoning and are declared as androidx.appcompat.widget.SwitchCompat.
- Open the project root in Android Studio.
- Create a
local.propertiesfile whosesdk.dirpoints to your Android SDK. This file is excluded from version control. - Optional: to use Firebase instead of demo mode, download
google-services.jsonfrom the team's Firebase project and place it in theappfolder. Never commit it. - Build the debug APK with
./gradlew :app:assembleDebug, or press Run in Android Studio. - Install it on a device or emulator by pressing Run with a device selected, or install the APK found in
app/build/outputs/apk/debug. - The app needs Android 7.0 (API 24) or newer.
In demo mode, the analytics events can be checked in the Logcat window of Android Studio by filtering by the tag ParchAnalytics.
To set up the backend (deploying the rules, seeding the demo groups, linking BigQuery and deploying the functions), follow the README of the Backend repository.
Automated tests in this repository. Run them with ./gradlew :app:testDebugUnitTest.
-
AvailabilityEngineTestchecks the slot calculation and the three rankings. -
AnalyticsTrackerTestchecks the anonymisation of user IDs. -
UserProfileTestchecks how a profile is built from an account, for example turning maria.lopez@uni.edu into Maria Lopez.
Automated tests in the Backend repository. Unit tests for the Cloud Functions, and security rules tests that run the exact writes this app makes (RSVP, invitations, activity creation) against the Firestore emulator and check what outsiders and signed out users are denied.
Manual testing. The full flow was tested on an Android emulator: sign in, dashboard, RSVP, Compare Availability with each ranking, scheduling a slot, creating the activity and the confirmation screen. The analytics events were checked in Logcat.
- The app has not been connected to a real Firebase project yet, so the Firestore and Firebase Authentication code has only been tested through the emulators and demo mode.
- The Looker Studio dashboard for BQ1 and BQ8 does not exist yet. It needs the BigQuery export and real events first.
- Google sign in is not available yet; the button shows a message.
- Some tactics from the architecture design are still pending: the Distance Matrix client with its 8 second timeout and fallback message, and adaptive sensor sampling in
StatusService. - The app does not ask users for consent before collecting analytics, and there is no option to hide your availability from a group. Both should exist before a real launch.
- New accounts join the groups with empty schedules, because calendar sync is not implemented.
-
FirestoreGroupRepositorystill tries to create a demo group when it is missing. The security rules now reject that write because the backend seeds the groups, so this code can be removed. - The folder
analytics/sqlis a copy of the queries that now live in the Backend repository and should be removed to avoid two versions. -
AvailabilityActivityand its layoutactivity_availability.xmlare legacy code replaced byAvailabilityStatusSheet. Nothing opens them, and they remain only until the team decides to delete them.