Introduction

There's something about pickup sports that feels increasingly rare in our digital age: real, spontaneous community. No algorithms deciding who you meet, no endless scrolling through feeds. Just you, a basketball, and whoever shows up at the park.

That's the vision behind Pickup. Not another app where you're the product, but a tool to make real-world connection accessible. A way to find that Tuesday night volleyball game at the park down the street, or discover which courts have the best basketball runs on weekends.

What I didn't expect was just how complex building a "simple" sports coordination app would turn out to be.

Pickup map view showing nearby games

The Unexpected Complexity of Social Apps

When I started Pickup, I imagined it as straightforward: users, games, parks. Maybe some chat functionality. How hard could it be?

Turns out, social apps are deceptively complex. The moment you introduce relationships between entities, the complexity multiplies exponentially. Here's what I mean:

  • Users have relationships with other users - friendships, blocked users, pending requests
  • Users have relationships with games - joining, leaving, hosting, getting kicked
  • Users have relationships with parks - favorites, preferences, arrival notifications
  • Games have relationships with parks - location data, availability, capacity
  • Chats have relationships with everything - game chats, park chats, direct messages, series chats
  • Series tie multiple games together - recurring events with their own context and continuity

This was one of those humbling moments in software development where you realize the iceberg is much bigger than you thought. Each relationship required careful consideration of state management, permissions, notifications, and edge cases. The data model ended up being the most critical architectural decision of the entire project. Get it wrong early, and you're stuck refactoring hundreds of files later. Trust me, I know.

Home screen showing upcoming games and recommendations

Core Features: Building the Social Layer

At the heart of Pickup is communication. Finding a game is one thing, but coordinating with people is what makes it work. The chat system became surprisingly sophisticated:

Park Chats

Every park has a community chat. Think of it as a digital bulletin board for that location. Someone asks "anyone up for tennis at 6?", another person responds, and suddenly you've got a game. These chats are persistent and location-based, creating micro-communities around physical spaces.

Park chat interface with game creation

Game Chats

Once you've joined a game, you get access to the game-specific chat. This is where the coordination happens: "Running 10 mins late", "Bringing an extra ball", "Should we switch to the covered courts?" These chats are ephemeral by design - they exist for the lifetime of the game and then archive.

Messages view showing game and park chats

Series Chats

For recurring games - like your weekly Thursday night basketball - series chats provide continuity. The same group, the same banter, week after week. This ended up being crucial for building actual community rather than just one-off interactions.

Group Chats

Beyond location and game-based chats, you can create private group chats with your friends. Planning a multi-park basketball run across the city? Group chats give you a dedicated space for your regular playing partners, separate from the public park chats and temporary game chats.

Reviews and Reputation

After playing together, users can leave reviews for each other. It's not about ratings or scores - it's about building trust in the community. "Great teammate, always on time" or "Friendly player, good energy" help people know who they're playing with. The system is opt-in and lightweight, designed to highlight good community members without creating toxicity around arbitrary numbers.

Real-Time Coordination

All of this chat infrastructure had to be real-time. Nobody wants to refresh to see if the game is still on. Firebase's Firestore handled this beautifully with its snapshot listeners, though managing subscription lifecycles across platform boundaries proved tricky.

Maps: Different Platforms, Different Worlds

One of the more interesting technical challenges was map integration. On the surface: just show pins on a map. In practice: two completely different implementations.

Android: Google Maps

On Android, we use Google Maps with clustering for performance. When you have dozens of parks in view, individual markers get overwhelming fast. The clustering algorithm groups nearby parks, expanding as you zoom in. The implementation lives in MapTabContent.android.kt, using the Maps Compose library which gives us that nice declarative API.

iOS: MapKit

iOS gets the native MapKit treatment (MapTabContent.ios.kt). Apple's maps look and feel different - more refined in some ways, though the annotation customization APIs are more restrictive. The expect/actual pattern in Kotlin Multiplatform made this split implementation surprisingly clean.

Both implementations share the same business logic for filtering parks, calculating distances, and managing selected locations. Only the actual rendering diverges. This is Kotlin Multiplatform at its best: write the complex stuff once, customize the UI per platform.

Privacy and Data Ownership

Building a social sports app means handling sensitive data - location, contacts, real-time whereabouts. Privacy isn't an afterthought; it's fundamental to the platform.

Location Data

Pickup only uses location data when you explicitly choose to share it. Arrival notifications are opt-in, and your precise location is never shared with other users - just the fact that you've arrived at the park. Location data is processed on-device first, and only the minimum necessary information is sent to the server.

Chat and Communication

All chat messages are encrypted in transit using TLS. While messages aren't end-to-end encrypted (we need to support features like offline message delivery and cross-device sync), they're stored securely and never sold or shared with third parties. You control who can message you, and blocking/reporting tools are built in from day one.

Privacy Controls

You control your visibility on the platform. Want to use Pickup but not be searchable by strangers? Toggle off discoverability. Don't want random friend requests? Turn that off too. Every game can be set to private - invite only, no public visibility. Games can also require approval to join, giving hosts control over who participates. You can participate in pickup sports without broadcasting your presence to everyone.

Privacy and visibility settings screen

Content Moderation

Every piece of user-generated content - messages, game descriptions, user profiles - runs through OpenAI's moderation model before it's saved. This catches most problematic content automatically. Severe violations get flagged immediately and ping my Slack instance directly, so I can respond fast. The built-in reporting system also notifies me instantly when users flag content or behavior. It's a small app right now, so this manual oversight works. As it scales, the automated moderation provides a first line of defense.

No General Ads, No Data Selling

Pickup's business model is simple: optional paid features like leagues and tournaments. Your data is never sold to advertisers. Your game history, chat logs, and preferences exist to make your experience better - not to build an advertising profile. While we might explore sport-specific ads in the future (think local sports leagues, equipment deals), there will never be generic banner ads or tracking pixels from third-party ad networks.

User profile with sports and park preferences

Features I'm Proud Of

Some features came together better than expected:

Arrival Notifications

The ParkArrivalManager monitors your location and notifies your game chat when you arrive at the park. Small feature, but it feels magical when you pull into the parking lot and your phone buzzes with "Brandon has arrived!"

Smart Game Recommendations

The recommendation system considers your sport preferences, favorite parks, skill level, and typical playing times. It's not ML-powered or anything fancy - just good old-fashioned filtering and scoring - but it works surprisingly well.

Live Game Scoring

Every sport has its own scoring system, and Pickup supports them all. Track sets and points in real-time for roundnet, volleyball, pickleball, or any other game. The scoring interface handles team assignments, supports multiple concurrent games, and automatically ends games after a timeout period. It keeps a full match history, making it easy to review past games and track your progress. Way more fun than trying to remember the score mid-game.

Live game scoring interface

Achievements System

Gamification done tastefully. "First Game", "Regular Player" (10 games), "Legend" (100 games). Purely cosmetic, no pay-to-win nonsense, just fun milestones to mark your journey.

Achievements screen showing game milestones

Deep Linking and Deferred Deep Linking

Sharing a game works seamlessly across the entire user journey. When someone shares a game, the link takes you directly into that game - even if you don't have the app installed yet. The deferred deep linking flow handles everything: install from the app store, complete onboarding, and land right in the shared game. No copying links, no "now go find the game manually" friction. Just tap, install, onboard, and you're in.

Join Game from PDF

Every game can be exported as a shareable PDF with all the details: time, location, participants, QR code for quick access. Perfect for posting on bulletin boards at gyms or community centers. Scan the code, and you're in the game. To make this work, I built kmpdf, a Kotlin Multiplatform PDF creation library. Because sometimes you need to take digital coordination into the physical world.

Marketing: The Final Boss

Here's the uncomfortable truth: building the app was the easy part. Getting people to actually download and use it? That's the real challenge. And honestly, the marketing strategy is still very much TBD.

I can architect a complex social platform, wrangle real-time data sync across platforms, and build custom PDF libraries - but getting strangers to trust a new social app from an unknown developer? That's a whole different game. One where "just build it and they'll come" is about as effective as showing up to an empty basketball court and hoping for a 5v5.

The app store is a graveyard of beautifully engineered apps that nobody uses. Pickup could have the cleanest architecture and the smoothest UX, but if nobody knows it exists, it's just an expensive side project. The real question isn't "can we build cool features?" - it's "how do we convince the pickup basketball crowd at the local park to switch from their group chat to our app?"

So yes, marketing strategy is TBD. And it's probably going to be harder than everything else combined. If you have ideas - or you're reading this and you actually organize pickup games - please reach out. I can debug a race condition in distributed state management, but viral growth strategies? That's magic I haven't figured out yet.

Lessons Learned

Building a social sports app taught me more than I expected:

Social Features Are a Multiplier

Every social feature you add multiplies complexity. Chat, friends, blocking, reporting - each one introduces edge cases and state management challenges. Don't underestimate this. What seems like "just add chat" is actually "add chat infrastructure, message state, read receipts, typing indicators, push notifications, moderation tools, and 47 edge cases."

Data Models Matter More Than You Think

Get your data model right early or suffer later. Seriously. I refactored the game-park-user relationship three times before finding something that scaled. Each refactor meant touching dozens of files. Design your models with relationships in mind from day one.

Real-Time Adds Delight

The difference between polling for updates and real-time listeners is night and day for user experience. Seeing messages appear instantly, watching game rosters update live - this makes the app feel alive. Worth the added complexity.

Community Is the Product

At the end of the day, Pickup isn't really about the features or the tech stack. It's about getting people to the park. The best technical decision I made was to keep the focus on real-world connection rather than in-app engagement metrics. If someone uses Pickup for 2 minutes and then plays basketball for 2 hours, that's a win.

Future Monetization: City Leagues and Beyond

Here's the thing about sports apps: the users are already spending money on sports. Balls, shoes, court rentals, leagues. The question isn't whether there's money in the market, but how to provide real value worth paying for.

City Leagues

The main monetization plan is organized leagues. Think of it as pickup sports with structure: regular schedules, standings, playoffs, championships. Cities could host basketball leagues, volleyball tournaments, tennis ladders. We provide the platform, handle registration and payment, coordinate schedules, and track standings. Cities get a cut, we get a cut, players get organized competition.

Clubs and Tournaments

Beyond leagues, there's appetite for private clubs and one-off tournaments. A group of pickleball players wants to run a weekend tournament? We can handle the bracket, scheduling, and coordination. A tennis club wants to manage their membership and court bookings? Built-in functionality.

Affiliate Marketing

Sports equipment is a natural fit. When you're in a roundnet chat and someone asks "what ball do you use?", imagine being able to share affiliate links right in the app. No intrusive ads, just helpful recommendations that support the platform. Same for shoes, nets, bags - anything players actually want.

The key is that none of this compromises the core experience. Finding and joining pickup games will always be free. The monetization comes from enhanced features that make sense for people already invested in the community.