Cheevo is a cross-platform location-based social app — native iOS in SwiftUI and native Android in Jetpack Compose, backed by a shared Firebase backend.
Two Native Clients, One Backend Contract
Building two native apps instead of a cross-platform framework like Flutter was deliberate. For a location-aware, social-interaction-heavy product, platform idioms matter: iOS uses MVVM with ObservableObjects and SwiftUI’s native navigation stack; Android uses MVVM + Hilt with Jetpack Compose. Both consume the same Firestore collections, the same Cloud Functions, and the same auth tokens.
The hard part isn’t writing the same screen twice — it’s keeping the data model honest across both clients. When one client makes a type assumption the other doesn’t, Codable fails silently on iOS or a cast exception fires on Android. Getting this right meant defining explicit schema contracts before implementation, using the typed Firestore SDKs consistently on both platforms, and validating both clients against real data rather than mocked fixtures. The Android client reached ~95% feature parity across 22+ screens without a dedicated catch-up phase. The data model held.
Authorization in the Data Layer
The most security-critical decision is where location verification happens. A user’s GPS coordinate comes from a device the user controls — client-side verification is easy to spoof. All location-gated writes go through Cloud Functions that re-validate the claimed position against each achievement’s geofence rules independently. The client provides coordinates; the backend decides whether an unlock is valid.
Firestore Rules handle the rest of the authorization model. Subcollection reads — feeds, achievements, follower graphs — are scoped to the owner or their follower list, not open reads filtered on the client. Authorization bugs fail at the data layer during development, not at the API layer in production. The rules were tightened in a single audit pass alongside adding a typed CheevoAPIError enum, Task cancellation wiring in Swift structured concurrency, and listener type-safety fixes across both clients.
From Feature-Complete to Launch-Ready
There’s a gap between “the feature works” and “this can ship to the App Store,” and closing it is real engineering work. The submission-readiness pass tightened admin guards around manual Cloud Functions, avatar/banner Storage read access, safer fallback handling, ATT/Facebook advertiser ID cleanup, and server-side note validation.
An accessibility pass added 89 VoiceOver labels across 27 iOS views — up from 7. The product has moved from feature build-out into submission readiness: accessibility coverage, rules hardening, server-side validation, and automated tests around repositories and ViewModels.

