Architecture - benjaminsunliu/ConUMap GitHub Wiki
Most mobile applications use a multi-layered approach to their software architecture. More coarsely defined as a "View", "Logic" and "Data" layers. Nevertheless, there are many versions of this multi-layered approach choosing the right one for our needs is dependent on our development needs and what is best for our application.
- Suited for React/React Native
- Communicating with 3rd party HTTP API'S
- Reading large amounts of information to a database
- Suited for small applications
The most popular three layered architectures are
This one is recommended by android and really has the separation of concerns between components built into the architecture. However, it doesn't suit well with react because the "model" in react, which is the state, is directly embedded with the "view" component. We could separate everything into other hooks but react was built to have the logic of state right with the rendering of the state to have better understanding of how things are rendered.
Similar to the model-view-viewmodel, it really separates the concerns between the components. Still, the separation between the model and the view using the presenter as a mediator is against the philosophy of react which wouldn't fit our needs.
This architecture design really blends will with the philosophy of react because having the controller be all handled by hook functions would significantly separate the concerns of the rendering and the how a component behaves based on action. However, we still have a better option with the 3 layered approach.
We will use the three-layered approach (View, Logic, Data) for our mobile application and map it specifically to our needs. We will be using this article and this article as our main sources of inspiration for adapting this architecture to our app.
Our application will still be divided into 3 layers of view, logic and data, but it will be done with React-specific concepts.
block-beta
columns 1
db(("User"))
blockArrowId6<[" "]>(down)
block:Components
A["Components"]
end
space
block:Hooks
H["Hooks"]
end
space
block:Services
S["Services"]
end
space
Components --> Hooks
Hooks --> Services
style A fill:#3dda85,stroke:#333,stroke-width:4px,color:#000
style H fill:#4282f2,stroke:#333,stroke-width:4px
style S fill:#092f42,stroke:#333,stroke-width:4px
Our React components and screens will be the "View" layer of our architecture. They will hold nothing much besides state that is relevant to UI and they will use the next layer to get most of the domain specific logic and data that they need.
Custom react hooks will function as our "Logic" layer for more complicated domain logic that isn't relevant to the UI. Per our source article, we can see that we can extract even more heavy buisiness logic into specific domain models. We will apply this as needed within our project.
Service objects and data stores will serve as our "Data" layer. These objects and data stores will be communicating with the logic layer to connect our application to the outside world (via API'S) and have persistance (via data stores).
To visualise this layered architecture better, here is the folder structure that we will be using for our project that clearly separates each of the layers.
/client
/datastore # Data layer
/services # Data layer
/hooks # Logical Connectors between UI and data
/components # Reusable UI components
/screens # Screen components
/utils # Shared utilities
/constants # App constants
based on this article
We will follow some guidlines to help us develop this application faster and more flexible to changes

The catalogs blocks found in the domain model are a way to represent a faster indexing method where the system can search the related catalog directly from the Map instead of needing to go through the various layers of nesting.


As can be seen in the class diagram,the Transportation interface shows an example of abstraction and polymorphism that allows various different transport types while allowing the sharing of common behaviour. Components like the LoginManager and TransportManager serve the role of coordinators for application state. Most state updates between components are handled through React's state management and hooks, which follows similar principles to the Observer pattern by allowing components to respond automatically to changes in application state.
The Singleton pattern is used so that only one instance of the authentication store exists across the entire app. Rather than instantiating AuthenticationStore every time it's needed, a single instance is created and exported as AuthStore from AuthenticationStore.ts, and every file that needs it imports that same object.
Singletons work slightly differently in JavaScript/TypeScript than traditional OOP. The ES6 (a Javascript specification that introduced modules) module system caches exports the first time they're imported, meaning that any future import of AuthStore gets the exact same instance back. Unlike traditional Java applications, there's no private constructor or getInstance() method required since the module cache handles it.
An example would be in use-login.ts and AuthContextProvider.tsx, which both imports AuthStore from AuthenticationStore.ts. When useIsLoggedIn is called, it calls AuthStore.getLoggedInData(). Because AuthStore is a Singleton, this is guaranteed to be the same instance that AuthContextProvider uses to save and clear login data.
Links
-
AuthenticationStore.ts (Lines 6-23)
-
Example Uses:
- Import in use-login.ts (Lines 2-8)
- Import in AuthContextProvider.tsx (Line 1)
- Usage in use-login.ts (Line 16)
- Usage in AuthContextProvider.tsx (Line 22)
UML

The Strategy pattern is used in directions.ts to encapsulate the route-fetching logic for each transportation mode into separate strategy classes. Instead of having one large function with conditionals for walking, transit, driving, bicycling, and shuttle, each mode is represented by its own class implementing the same RouteStrategy interface.
The shared behavior for Google Routes API requests is placed in the abstract GoogleRoutesStrategy class. This base class handles the common logic such as building the request, calling the API, applying the field mask, handling timeouts, and normalizing the response. Concrete strategies like WalkingRouteStrategy, TransitRouteStrategy, DrivingRouteStrategy, and BicyclingRouteStrategy only define their specific travelMode. ShuttleRouteStrategy is separate because shuttle directions are not fetched from Google Routes API and are instead handled through the Concordia shuttle schedule.
This makes the code easier to extend and maintain. If a new transportation mode is added later, a new strategy can be created without changing the main direction fetching logic. It also avoids large conditional blocks and keeps each transportation-specific behavior isolated.
We are also using a simple factory pattern with RouteStrategyFactory, which acts as the selector for the correct strategy. When fetchDirections is called, it asks the factory for the strategy matching the requested TransportationMode, then delegates the work to that strategy’s fetch() method. This means the rest of the app does not need to know the implementation details of how each mode retrieves its routes.
Links
- directions.ts Strategy Pattern Commit (Lines 291, 377-425)
UML

The Facade pattern was implemented to streamline how the CalendarScreen handles authentication. Instead of having the component directly control the AuthContextProvider or AuthenticationStore components, that logic is hidden behind a module interface exposed through use-login.ts modules. This file acts as the Facade, providing three focused methods: useIsLoggedIn, useLogin, and useLogout.
AuthContextProvider.tsx manages the authentication state using React context, showing either a LoggedInContext or LoggedOutContext depending on session state. AuthenticationStore.ts handles persistence with the use of Expo's SecureStore with getLoggedInData, setLoggedInData, and clearLogin. The Facade uses these two components internally and exposes the results across the three methods, each extracting only its relevant information. From the client's point of view, authentication is a simple module interface of three clean methods with no awareness of context providers, secure storage, or the types the subsystem uses internally.
Links
- AuthContextProvider.tsx (Lines 18-50)
- AuthenticationStore.ts (Lines 6-23)
- Login Module with 3 hooks (Lines 7-53)
- Implementation of Facade hooks in Calendar (Lines 11-13)
UML

The Factory Method pattern wasn't implemented exactly as the GoF expects as with its textbook definition. This is due to some of the limitations of our design and React itself. So, we took inspiration from this article and after a short discussion decided on our implementation.
First, the limitations set by React is that since we want to create a factory for a specific component, there is no way to extend components via functional React, so we had to suffice with the generic React.ReactElement as our abstract "product".
Second, our created react elements need some state from the parent component that they are rendered within so we used a parametrized createMarker function which passes the state to whichever marker it creates.
Third, since we only have one concrete factory (for now, allows us to easily extend the code later), and we didn't recognize any shared generic functionality of a MarkerFactory, we decided to make it an interface instead of an abstract class (also JavaScript doesn't have abstract classes)
Links
- default-marker-factory.tsx (lines 14-16) as our interface factory
- default-marker-factory.tsx (the rest of the lines) as our concrete factory
- default-poi-marker.tsx as our concrete "product"
- building-floor.tsx (line 49) is where the factory is used
UML

The Command pattern was implemented to decouple the indoor navigation UI from the logic executed when the Next and Previous buttons are pressed. Instead of embedding conditional logic inside the IndoorNavigationControls component, each button action is encapsulated as a command object containing a label, an executability flag, and an execute() function.
This design allows the same UI controls to support different behaviors depending on the navigation mode. In floor mode, commands switch floors, while in step mode, they advance navigation steps. The component simply renders the commands and invokes execute() when pressed.
Since this is a React/TypeScript application, we used lightweight typed objects instead of a full class hierarchy. This keeps the implementation simple while still preserving the intent of the Command pattern: encapsulating a request as an object and separating the UI from the action logic.
The pattern is adapted to React native and differs slightly from the textbook GoF. However, it still does come with many of the benefits:
- The control component no longer decides behavior. It just invokes commands in components/map/indoor-navigation-controls.tsx (line 31) and components/map/indoor-navigation-controls.tsx (line 49).
- The screen creates the mode-specific behavior in one place at app/(tabs)/(map)/[buildingCode].tsx:268 (line 268).
- The command objects encapsulate execute, canExecute, and label in utils/indoorNavigationCommands.ts (line 3), which is enough to decouple the buttons from floor-vs-step logic.
Links
- Refactoring PR: https://github.com/benjaminsunliu/ConUMap/pull/216
UML

- Storage:
- react-native-mmkv (Great and fast)
- react-native-storage (Good, but not fast)
- Gestures:
- Accesibility:
- Animations:
- Geolocation Service:
Pros:
- Default and bundled with Expo
- Most popular with react native
- Custom markers on map Cons:
- Doesn't allow for offline maps navitation
Pros:
-
Technically allows for offline map support Cons:
-
Pricing above 1000 requests a month
-
The react native part of the library isn't maintained by the company
-
The offline map support doesn't have good documentation
More accurate than the default RN location API; essential for pinpointing which campus the student is currently on. It interfaces with the Fused Location Provider on Android and CoreLocation on iOS. It is the most reliable way to get a user’s coordinates. Seems crucial for the shuttle feature because it tells the app exactly how far the student is from the bus stop.
Pros:
- Offers High-fidelity Polling, giving you very accurate latitude and longitude data.
- Includes a Distance Filter, so the app only re-calculates the route when the student has moved a certain number of meters.
- Better Permission Lifecycle handling than the default React Native location tool.
Cons:
- Significant battery usage when set to the highest accuracy mode.
- Can have a slow Time to First Fix, meaning the "blue dot" might take a few seconds to appear.
- Requires strict Privacy Policy disclosures for the App Store and Play Store.
Either unpopular, unmaintained, or is not great for maps in North America.
Provides "declarative" touch logic, making it easier to create large, forgiving tap targets. Standard React Native touch handling can be a bit slow because it has to communicate back and forth with the js/ts code. This library moves that logic to the Native side. It lets you define Hit Slops (making the clickable area bigger than the actual button) to help students with physical impairments hit their targets.
Pros:
- Operates on the Native thread, ensuring that touch responses feel "snappy" even when the app is loading a map.
- Allows for Simultaneous Recognition, meaning a student can pan the map and zoom at the same time without the UI locking up.
- Uses State-based logic, making it easy to program different behaviors for a "Long Press" versus a "Force Touch."
Cons:
- Setup is a bit complex because it requires Native Linking in the Android and iOS project files.
- Can interfere with the Navigation Stack if you don't configure the gesture "bubbles" correctly.
- Harder to simulate and test in a web-based emulator.
Converts navigation text into spoken turn-by-turn directions. The library goes into the phone’s Native Speech Synthesis API & turns text strings into spoken audio. For students who struggle with literacy, this acts as an Accessibility Layer that interprets the screen data and reads it out through the device.
Pros:
- Utilizes Native Engine Integration, meaning it doesn't need the cloud and works with zero latency.
- Supports Audio Session Management, which allows for ducking (eg. lowering the volume of other apps like Spotify) during navigation.
- Offers granularity in speech control, letting you adjust the pitch, rate, and voice quality.
Cons:
- Voice availability is device-dependent, so a budget Android might sound different than a new iPhone.
- Requires Lifecycle Management to make sure the app doesn't keep talking after the student closes the screen.
Enables voice-to-text building selection (eg. "take me to Hall Building"). This is a wrapper for Automatic Speech Recognition (ASR). It lets students talk to the app instead of typing. It converts voice waves into text data that the app can use to search for buildings or shuttle times. Useful for input accessibility, especially for users with motor impairments.
Pros:
- Provides Real-time Transcription, so the user sees partial results immediately while they speak.
- Supports Event-driven programming, letting the app trigger actions automatically as soon as it recognizes a keyword.
- Uses On-device Recognition when available, which is better for privacy and speed.
Cons:
- Very sensitive to ambient noise, which can cause a high Word Error Rate (WER) on a busy campus.
- Requires manifest permissions (eg. mic access), which can be a privacy block for some users.
Includes useReducedMotion to automatically disable animations if the user's system settings require it. Reanimated uses something called worklets to run animations separately, which is a lifesaver for students with epilepsy because it ensures movement is consistent and never "stutters" or flashes.
Pros:
- Offloads execution to the UI thread, which prevents frame drops and such that can trigger sensory issues.
- Supports declarative animations, making it easier to maintain a reduceMotion state across the whole app.
- Uses synchronous updates, so the UI stays perfectly in sync with user touches.
Cons:
- The Worklet syntax is tricky and has a steep learning curve
- Debugging can be a pain because the code runs in a different JavaScript runtime than the rest of the app.
The primary library for Google Maps integration. It supports Polylines (drawing the walking route) and Markers (building photos for landmarks). The library provides the MapView component, which acts as a window into Google Maps. We'll use Polylines to draw the route between SGW and Loyola. You can also use custom overlays to highlight building footprints, which helps neurodivergent users understand the physical boundaries of the campus.
Pros:
- Supports Native Rendering, so zooming and panning are handled by the phone's GPU.
- Allows for Marker Clustering, which keeps the map clean by grouping icons together until you zoom in.
- Easily handles GeoJSON data (if needed), making it simple to import campus-specific map layers.
Cons:
- Rendering too many layers can cause the phone to heat up and/or lag.
- Google might charge fees if the app hits a very high number of map loads.
- Styling the map (like using "Dark Mode") requires a JSON style object that can be annoying to maintain.