It can be difficult for people to organize future pick-up games or find live pick-up games. Organizers must communicate and coordinate with players through traditional methods such as texting, while players must have prior connections or friends or physically see the pick-up game in order to know it is happening.
Our app allows users to organize, find, and join pick-up games in a convenient and streamlined manner. Users have access to nearby games that fit their skill level and match the desired sport, while organizers can share and coordinate games with their friends or anyone nearby. Organizers can control game visibility (public vs. private), accept or reject user requests to join a game, and invite specific users to join, allowing full control over who has access to the game location and details. Organizers can coordinate user responsibilities for bringing gear, equipment, and more. Users can add friends and communicate easily with each other through game-specific chatrooms.
The scope of our app encompasses a social network for users to find and engage with their local community, make friends, find nearby games, and communicate with other players. Our app also functions as a tool for organizers to schedule and manage pickup games and their players.
Our potential users include individuals who organize or want to organize pick-up games with their friends, as well as individuals looking to find and join pick-up games easily.
Our app utilizes the client-server architecture, where the client (or frontend) provides the UI and allows the user to interact with the app, and the server (or backend) receives and processes requests from the client. We use Expo with React Native for the frontend, and Supabase for the backend.
Frontend:
- Framework: Expo with React Native
- Navigation: React Navigation
- UI library: Tamagui with NativeWind CSS
Backend:
- Database and Authentication: Supabase Database
- Storage: Supabase Storage
- Cloud Functions: Supabase Cloud Functions
Deployment:
- Distribution: Expo's tools to build and publish updates to the Apple App Store and Google Play Store
- CI/CD: GitHub Actions
Additional technologies:
- Version Control: Git and GitHub
- Testing Frameworks: Jest and Detox
- Caching: Upstash Redis
- Email Notifications: Resend
- @babel/runtime: ^7.24.4
- @react-native-async-storage/async-storage: 1.21.0
- @react-native-community/datetimepicker: ^7.6.4
- @react-navigation/bottom-tabs: ^6.5.20
- @react-navigation/native: ^6.1.17
- @react-navigation/native-stack: ^6.9.26
- @supabase/supabase-js: ^2.42.1
- @tamagui/alert-dialog: ^1.94.4
- @tamagui/card: ^1.94.4
- @tamagui/checkbox: ^1.94.4
- @tamagui/config: ^1.94.4
- @tamagui/font-inter: ^1.94.4
- @tamagui/lucide-icons: ^1.94.4
- @tamagui/slider: ^1.94.4
- @tamagui/toast: ^1.94.4
- @testing-library/jest-dom: ^6.4.2
- @upstash/redis: ^1.29.0
- burnt: ^0.12.2
- expo: ^50.0.15
- expo-app-loading: ^2.1.1
- expo-dev-client: ~3.3.11
- expo-font: ~11.10.3
- expo-image-picker: ~14.7.1
- expo-location: ^16.5.5
- expo-status-bar: ~1.11.1
- expo-updates: ^0.24.12
- immer: ^10.0.4
- jest-expo: ~50.0.4
- nativewind: ^4.0.36
- react: 18.2.0
- react-native: 0.73.6
- react-native-css-interop: ^0.0.34
- react-native-elements: ^3.4.3
- react-native-reanimated: ~3.6.3
- react-native-safe-area-context: 4.8.2
- react-native-screens: ~3.29.0
- react-native-svg: ^14.1.0
- react-native-url-polyfill: ^2.0.0
- resend: ^3.2.0
- tamagui: ^1.94.4
- zustand: ^4.5.2
Make sure you have these prerequisites installed: npm and Expo CLI.
For testing the app using Expo Go, you will need a compatible mobile device (iOS or Android) with the Expo Go app installed. For iOS development, Xcode must be installed to run the iOS simulator. For Android development, Android Studio or an Android device/emulator must be set up.
Follow the below steps to run the app locally on Expo Go.
-
Clone the repository and navigate to the root folder in the terminal.
-
Add a
.envfile in the root folder of this repository containing the fields in the.env.examplefile, and fill in the required environment variables. -
Run
npm installto install dependencies. -
To use the Expo Go app:
a. Download the Expo Go app on your phone.
b. Run
npx expo start --go.c. Scan the QR code with the Expo Go app (Android) or the Camera app (iOS) and open the link.
-
To locally build the project:
a. On iOS, to run the simulator on Xcode:
expo prebuild --platform ios npx expo run:ios
b. On Android (not tested yet):
expo prebuild --platform android npx expo run:android
-
To login as a test user to the app, use the following credentials:
email: admin@example.com password: password
Follow the below steps to download the build from the Expo Dashboard and run it on the Xcode simulator.
-
Navigate to our OOSE group's Expo dashboard.
-
Navigate to the Pickup project.
-
Click on the most recent build.
-
Click "Download" and wait for the build to download.
-
Unzip the downloaded build.
-
Open Xcode simulator and drag the unzipped build onto the simulator.
app.json: contains app information such as name, slug, icon, splash icon, etc.babel.config.js: configure presets and plugins such as NativeWindeas.json: configure build profiles, distribution settings, and submission configurations for EASjest.setup.js: set up the Jest testing environmentmetro.config.js: customize the Metro bundler configuration and integrate additional plugins like NativeWindpackage.json: define and manage dependenciestailwind.config.js: configure Tailwind CSStamagui.config.js: configure Tamagui, specify theme configuration, etc.tsconfig.js: configure TypeScript settings like compiler options, etc.
Refer to the Installation Guide's Prerequisites and System Requirements.
Run npm install to install all dependencies.
To set up the environment variables, add a .env file to the root of the project directory in the following format, as in .env.example, and fill it out with the correct keys:
ANON_KEY=
AUTOCOMPLETE_API_KEY=
UPSTASH_TOKEN=
RESEND_API_KEY=
In order to use Pickup!, the following external keys/tokens are required in .env:
ANON_KEY: The Supabase anon key, which will allow queries to the Pickup! production database.AUTOCOMPLETE_API_KEY: Thehttps://locationiq.comAPI key necessary for the address autocomplete feature.UPSTASH_TOKEN: The token for Upstash Redis necessary to handle the cache.RESEND_API_KEY: The API key for Resend necessary to handle the email notifications.
For our user interface, our app is divided into four main views: Feed, My Games, Profile, and Friends.
The Feed view contains a feed of nearby public games and a feed of friends-only games, along with an option to filter the feed.
The My Games view contains a list of the user's published games and a list of the user's joined games, along with an option to create a new game. Published and joined games are managed by the user through this view.
The Profile view contains the user's profile information and options to edit their profile or logout.
The Friends view contains three tabs, one for viewing the friends list, one for viewing friend requests, and one for searching for other users.
In addition, users will be greated by a page for logging in or registering when they open the app for the first time.
Our app uses Supabase to authenticate and authorize our users. Users may register by entering an email, password, and unique username. They must click on the confirmation link sent to their email address before they can login using their email and password.
-
User profile
- [must-have] As a user, I want to register for the app and login using my credentials, so that I can store and access my data.
- [must-have] As a user, I want to view and edit my profile, including my username, display name, avatar, bio, sports that I’m interested in, and my skill level in each sport.
-
CRUD operations on pickup games
- [must-have] As an organizer, I want to create a pick-up game so that others can join it.
- [must-have] As an organizer, I want to create a public game, so that any other users in my area can request to join the game.
- [must-have] As an organizer, I want to create a friends-only game, so that my friends can request to join the game.
- [must-have] As an organizer, I want to change my game’s visibility to public or friends-only, so that I can control who can view and request to join the game.
- [must-have] As an organizer, I want to edit a game’s title, description, location, date, time, maximum number of players, and sport, so that players can see the game's most up-to-date details.
- [must-have] As an organizer, I want to see a list of the games that I’ve organized, so that I can keep track of my upcoming games.
- [must-have] As a player, I want to see a list of my accepted games, so that I can quickly access game details.
- [must-have] As a player, I want games I’ve previously attended to no longer show up on my accepted games feed, so that I can clearly keep track of my current events.
- [must-have] As an organizer, I want to be warned if there is another event going on in the same location and time that I am setting up my event.
- [must-have] As an organizer, I want the addresses for my games to be autocompleted so I know I am submitting a valid address.
-
Join requests, joining, and leaving pickup games
- [must-have] As an organizer, I want accepted users to see my game’s location and non-accepted users to see the distance of my game’s location to their location.
- [must-have] As an organizer, I want to accept or reject user requests to join, so that I can control who can join the game.
- [must-have] As an organizer, I want to remove an accepted user from my game, so that they no longer have access to the game location or details.
- [must-have] As a player, I want to request to join a game so that I can see the game details.
- [must-have] As a player, I want to leave a game that I have already joined, so that I no longer receive game details or notifications.
- [nice-to-have] As a player, I want to bring a +1 to a game, so that they can come too while keeping the number of players below maximum capacity.
-
Feed of nearby pickup games
- [must-have] As a player, I want to see a feed of public games in my area so that I know which games I could join.
- [must-have] As a player, I want my feed to also display games organized by my friends, so that I can request to join them.
- [must-have] As a player, I want to change the limit on game distance from me so that I can view games in my nearby area.
- [must-have] As a player, I want to be able to filter through games in my feed by skill level and sport type, so that I can join games relevant to me.
-
Friends
- [nice-to-have] As a player or organizer, I want to be able to click on another player in the accepted players list, join requests list, avatar in chatroom, or organizer in game view so I can view their profile.
- [must-have] As a user, I want to search for another user by username, so that I can view their profile.
- [must-have] As a user, I want to request to add a friend, so that we can view each other’s friends-only games in our feeds.
- [must-have] As a user, I want to accept a friend request, so that we can view each other’s friends-only games in our feeds.
- [must-have] As a user, I want to reject a friend request, so that they can’t see my friends-only games in their feed.
- [must-have] As a user, I want to be able to view my requests, so I can see who wants to become my friend.
- [must-have] As a user, I want to be able to view my friends list, so I can see all the users who I am currently friends with.
- [nice-to-have] As an organizer, I want to send notifications to my friends when I create an event, so that they are aware and can join.
-
Email notifications
- [nice-to-have] As an organizer, I want to send notifications to my friends when I create an event, so that they are aware and can join.
- The following user stories were not originally in the roadmap but are integrated features in the app:
- [nice-to-have] As a user, I want a notification to be sent to another user when I send them a friend request.
- [nice-to-have] As a player, I want a notification to be sent to the organizer of a game when I request to join.
-
Chatroom
- [nice-to-have] As a user, I want to be able to communicate with other accepted users through a game-specific chatroom, so that we can coordinate or discuss the game.
- [nice-to-have] As a player, I want to be able to quickly load and view chats from a chatroom so I don't have to wait for them to load.
Since our app architecture uses Supabase as a BaaS, we do not have a publicly available API.
However, we do utilize several third-party APIs, listed below, in order to handle caching and get location information for games.
https://locationiq.com: this API is necessary for our address autocomplete feature, which repeatedly searches for and presents matching addresses as the user types in an address.https://geocode.maps.co: we query this API in order to get latitude and longitude coordinates for a given address on the backend.https://us1-pleased-kangaroo-41719.upstash.io: we use Upstash Redis to handle caching for chatroom messages and other players' profile information.https://api.resend.com/emails: we use Resend to implement email notifications on friends-only game creation, friend requests, and join requests. In order to make this work, we registered thepickup-app-notifications.comdomain and built an Edge Function on Supabase that posts the body of the desired email to Resend.
None
None
None
Entity-Relationship Diagram:
UML Diagram:
-
profiles: contains publicly accessible profile information for each user. -
sports: contains publicly accessible sports information for each user. Users may add multiple sports to their profile. -
games: contains game information (except location and address) and visibility status for each created game. -
game_locations: contains game location and address location for each created game. -
game_requests: contains player join requests for each game. -
joined_game: contains joined players for each game. -
friends: contains all friend relationships between users. -
friend_requests: contains friend requests between players. -
messages: contains chatroom messages for each game chatroom.
profiles
iduuid, cannot be nullupdated_attimestamp with time zone, cannot be nulldisplay_nametextavatar_urltextbiotextlength(bio) < 500usernametext, must be unique, cannot be null,length(username) < 20, can contain letters, numbers, and underscoresemailtext, cannot be null
sports
iduuid, cannot be nulluser_iduuid, referencesprofiles.id, cannot be nullnametext, cannot be nullskill_levelint4, cannot be null,skill_level >= 0 and skill_level <= 2
games
iduuid, cannot be nullorganizer_iduuid, referencesprofiles.id, cannot be nulltitletext, cannot be nulldescriptiontext,length(description) < 500created_attimestamp with time zone, cannot be nulldatetimetimestamp with time zone, cannot be null,sporttext, cannot be nullskill_levelint4, cannot be null,skill_level >= 0 and skill_level <= 2max_playersbigint, cannot be null,max_players >= 1current_playersbigint, cannot be nullis_publicboolean, cannot be null
game_locations
iduuid, cannot be nullgame_iduuid, referencesgames.id, cannot be nullstreettext, cannot be nullcitytext, cannot be nullstatetext, cannot be nullziptext, cannot be nullloc"extensions"."geography"(Point,4326)
joined_game
iduuid, cannot be nullplayer_iduuid, referencesprofiles.id, cannot be nullgame_iduuid, referencesgames.id, cannot be nullplus_oneboolean, cannot be null
game_requests
iduuid, cannot be nullcreated_attimestamp with time zone, cannot be nullplayer_iduuid, referencesprofiles.id, cannot be nullgame_iduuid, referencesgames.id, cannot be nullplus_oneboolean, cannot be null
friend_requests
iduuid, cannot be nullcreated_attimestamp with time zone, cannot be nullrequest_sent_byuuid, referencesprofiles.id, cannot be nullrequest_sent_touuid, referencesprofiles.id, cannot be null
friends
iduuid, cannot be nullcreated_attimestamp with time zone, cannot be nullplayer1_iduuid, referencesprofiles.id, cannot be nullplayer2_iduuid, referencesprofiles.id, cannot be null
messages
iduuid, cannot be nullsent_attimestamp with time zone, cannot be nullplayer_iduuid, referencesprofiles.id, cannot be nullgame_iduuid, referencesgames.id, cannot be nullcontenttext, cannot be null
We planned two approaches to testing our app: (a) unit tests and (b) frontend tests. All tests are run automatically on pushes or pull requests to main or dev via GitHub Actions.
The first approach, unit tests, targets the backend tables and custom postgreSQL functions written for our use case. The tests were written using pgTAP and are applied to a test database schema built by a migration file which contains all of our tables and functions. We first seed our database with example records of profiles, games, and more. Then, we test that all database tables are built with the correct columns, foreign key relationships, and data types. Lastly, we test that our custom postgreSQL functions behave as expected by manipulating the seed data, calling the function, and verifying the results.
Our second approach, frontend tests, targets our frontend components and tests whether they render correctly and handle data in the expected way. We used Jest to write our frontend tests. We focused on determining whether components render as expected, mocking the store and hooks, and verifying that button presses or other user interations were handled as expected.
We encountered a lot of difficulty with our frontend test suite. We ended with a total of 39.43% coverage in the front end with many tests failing, often due to continued issues with jest mocks. These are our front end test results:
Test Suites: 22 failed, 6 passed, 28 total Tests: 56 failed, 29 passed, 85 total Snapshots: 0 total Time: 47.924 s, estimated 66 s
For the unit tests, we were not able to get all tests to run/pass, but the output is as follows:
Test Summary Report
-------------------
./unitTests.sql (Wstat: 0 Tests: 107 Failed: 0)
Parse errors: Tests out of sequence. Found (1) but expected (104)
Tests out of sequence. Found (2) but expected (105)
Tests out of sequence. Found (3) but expected (106)
Tests out of sequence. Found (3) but expected (107)
Bad plan. You planned 1 tests but ran 107.
Files=1, Tests=107, 2 wallclock secs ( 0.04 usr 0.01 sys + 0.02 cusr 0.02 csys = 0.09 CPU)
Result: FAIL
For our frontend testing, one of the biggest difficulties we encountered was mocking our zustand store (useStore), which is manipulated in virtually every component. However, many components import and use different functions and variables from the store, making each store mock unique for each component. Testing the store itself proved to be very difficult as well. As such, many tests did not properly render because of issues with the mocked store, which caused cascading error with the other tests that relied on a properly rendered component. By the same logic, an improperly mocked store also ended up hurting our overall testing coverage, as the tests treated the non-rendered components and functions as non-existent.
We build and deploy our app automatically on pushes to main via GitHub Actions using Expo EAS. .yml scripts written to automate this process are available at ./.github/workflows.
None

