One codebase, two platforms, real differences
Whistler runs on iOS and Android from one React Native + Expo codebase. The shared code is the UI and logic; the differences sit in native integrations: Firebase Cloud Messaging and Notifee for notifications, deep links that route to specific screens, and Google, Apple and Facebook sign-in. Apple’s rule that apps offering social login must also offer Sign in with Apple is an example of a platform constraint that shapes the product, not just the code.
Know which side of the OTA boundary a change is on
EAS Update can ship JavaScript and assets without store review. Anything touching native code needs a new binary — a Gradle app bundle for Google Play. Knowing which side a change falls on decides how fast it can reach users, so it is a design question, not just a release question.
Keeping the classifier in pure JavaScript was partly a release decision: a retrained model is an over-the-air update, not a store submission.
Stage every release
Whistler has a development channel and a production channel. Updates reach development-client builds first and production only after they have been exercised. Native builds use EAS-managed version auto-increment so versioning isn’t a manual step to forget.
Observe what shipped
Sentry reports crashes and errors from production builds. Mixpanel and Firebase Analytics show how features are used. Without both, the only feedback loop is app-store reviews.
Test the backend paths the app depends on
Onboarding is the first impression, and it is a burst of API calls. I built a load-test harness that replays the full onboarding sequence — registration, username checks, profile updates, image uploads, interest selection — for up to 1,000 accounts at once, with k6 scenarios for feed and moments traffic.