Lemma 2
CampusGuide
An Android app for finding your way around Concordia’s two campuses: outdoor and indoor directions, the shuttle, and a route to your next class.
Screens
- MapBuildings, routes, shuttle
- Indoor mapFloors and rooms
- CalendarSchedule, next class
- Points of interest
- Accessibility
State
- ViewModelsSearch, location, indoor navigation, calendar
Domain and data
- Directions repositories
- Indoor pathfindingDijkstra
- ShuttleTracker
- GeoJSON parser
- CalendarRepository
External services
- Google Maps SDK
- Google Routes API
- Concordia Open Data API
Overview
CampusGuide is a navigation app for Concordia University’s Sir George Williams and Loyola campuses, built over one term by an eleven-person team, The Null Pointers. It draws the campus buildings on a map, gives outdoor directions in several travel modes, routes indoors between rooms and floors, knows the shuttle schedule, and can take a student from where they are to their next class.
I was one of five developers on the front-end side of the team. This page describes the app as a whole, then separates out the parts I built.
Problem
A timetable gives a student a building code and a room number, on one of two campuses several kilometres apart. A general-purpose map app stops at the front door: it does not know which floor the room is on, when the next shuttle between campuses leaves, or which class comes next.
Goals
- Show both campuses with their buildings clearly distinguished from the surrounding city, and switch between them in one tap.
- Give outdoor directions on foot, by car, by public transit and by the Concordia shuttle, including between the two campuses.
- Route indoors between rooms on different floors, with accessibility-aware options.
- Read a student’s course schedule and offer directions to the next class.
Technologies
| Application | Kotlin, Jetpack Compose, Material 3, Google Maps SDK for Android, Maps Compose, Play Services Location, Google Routes API, Concordia Open Data API, Retrofit, OkHttp, kotlinx.serialization, DataStore |
|---|---|
| Testing | JUnit 4, Robolectric, Mockito, MockWebServer, kotlinx-coroutines-test, Compose UI Test, Espresso, UI Automator |
| Build and CI | Gradle (Kotlin DSL), GitHub Actions, JaCoCo, SonarQube Cloud, Android Lint |
Architecture
A single-activity Jetpack Compose app. Screens and reusable composables sit on top of ViewModels that hold the UI state. Repositories wrap the outside services: Google’s Routes API for outdoor directions and Concordia’s Open Data API for course schedules.
Campus buildings are GeoJSON polygons, parsed and drawn as map overlays. Indoor navigation is its own engine, built by teammates: floor graphs loaded from assets, Dijkstra’s algorithm for the shortest path on a floor, and a router that joins floors together.
The shuttle is modelled as a domain class behind a data-source interface, so its schedule logic can be tested without a map on screen.
My responsibilities
A team project with eleven members. I worked on the front-end side, and set up much of the build, test and coverage tooling. What I built, in the order it landed:
- Foundations: the first Google Maps integration, the GitHub Actions workflows, and JaCoCo coverage reporting into SonarQube Cloud.
- Campus switching: the toggle between the two campuses, the saved campus preference, the camera animation and the swap of building polygons.
- Cross-campus routing between Sir George Williams and Loyola, with end-to-end tests that do not depend on the device’s GPS.
- The step-by-step directions panel, with expand and collapse.
- Shuttle stops: the ShuttleTracker domain class, the stop markers on the map and the stop information sheet.
- Directions to the next class from the daily schedule, and the acceptance tests for the calendar features.
100 of the 507 non-merge commits on the main branch are mine. Indoor navigation, points of interest and the accessibility screens were built by teammates.
Engineering decisions
Parse off the main thread, attach on it
Building polygons come from GeoJSON files large enough to freeze the interface when parsed during start-up. I moved coordinate parsing to a background dispatcher, kept the map calls on the main thread where the Maps SDK requires them, and loaded each campus’s overlay only when it is first shown.
Fast unit tests over heavyweight ones
Early coverage came from Robolectric tests that started whole activities. They were slow and ran the suite out of memory. I deleted 203 of them and replaced the approach: logic extracted into methods that plain unit tests can reach, and instrumented tests for what genuinely needs the Compose runtime.
A domain class before any markers
For the shuttle I first extracted a ShuttleTracker class and a stop model behind a data-source interface, and only then drew markers and the information sheet on top. The schedule logic is covered by unit tests that never touch a map.
Measure the code that can be measured
Composable layout code and map rendering cannot be exercised by unit tests. I excluded those layers from the coverage calculation, so the quality gate reports on logic that unit tests can actually reach, and documented the reasoning in the repository’s testing guide.
Testing and quality
On the main branch there are 831 unit-test methods in 63 files and 72 instrumented test methods in 27 files. Unit tests use JUnit 4, Robolectric and Mockito; instrumented tests use Compose UI Test and Espresso, and most are written as numbered acceptance tests.
Four GitHub Actions workflows build the app, run the unit tests and Android Lint, produce the JaCoCo report for SonarQube Cloud, and build a release APK.
Challenges
The app froze on map load
Adding the second campus made the map screen stop responding at start-up. The cause was GeoJSON parsing on the main thread. Moving it off fixed the freeze and exposed the next problem: attaching overlays from a background thread crashed, because the Maps SDK is main-thread only. The final shape parses in the background and attaches on the main thread.
A test suite that ran out of memory
As coverage tests accumulated, the suite began failing with out-of-memory errors and tests interfered with each other through static mocks and shared coroutine scopes. Raising the heap bought time; the real fix was removing the heavyweight tests, replacing static mocks with injectable hooks, and stopping coroutine scopes from leaking between tests.
Coverage reporting on a new build toolchain
The project used version 9 of the Android Gradle Plugin, and the usual JaCoCo and SonarQube configuration did not work with it. Getting coverage to appear in the quality gate took several iterations on class paths, task wiring and double-indexed sources.
Results
| Measure | Result | Source |
|---|---|---|
| Delivered | Campus maps, outdoor and indoor directions, shuttle, schedule and points of interest | Source tree on the main branch |
| Unit tests | 831 test methods in 63 files | Counted on the main branch, April 2026 |
| Instrumented tests | 72 test methods in 27 files | Counted on the main branch, April 2026 |
| My share of the history | 100 of 507 non-merge commits | Git history of the main branch |
Table 1. Test figures are counted from test annotations in the source, not from a test report. No coverage percentage is quoted because the repository does not hold a final measured report.
Repository and links
ShadiiAM/ConcordiaCampusGuide(opens in a new tab)
The team repository, hosted on a teammate’s account. My commits there are under my own GitHub identity.
There is no hosted demo for this project. The repository explains how to run it.
End of case study