Skip to content

User Testing — TestFlight & Android

This guide covers getting the RL Padel Academy app onto real devices for testing, on both iOS (via TestFlight) and Android (via Google Play internal testing). We use EAS Build to compile signed binaries in the cloud and EAS Submit to upload them to the stores.

The app runs Expo SDK 56 with the new architecture enabled, bundle ID nl.rlpadelacademy.app. Because parts are native, distributing to testers requires a real signed build — you can’t just send a JS bundle.

Overview

The flow is the same on both platforms:

  1. eas build produces a signed binary in Expo’s cloud (no local Xcode/Android Studio toolchain needed).
  2. eas submit uploads that binary to App Store Connect or Google Play.
  3. You add testers to a track/group and they install.
  4. Between native builds, ship JS-only fixes instantly with eas update.

Install the CLI and log in once:

Terminal window
npm install -g eas-cli
eas login
cd /Users/stijn/Developer/wva-shb-partnership/padel/apps/frontend

eas.json

Build profiles live in apps/frontend/eas.json. This defines three environments, each pointed at the appropriate PocketBase backend:

{
"cli": {
"version": ">= 12.0.0",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": { "simulator": true },
"env": {
"EXPO_PUBLIC_PB_URL": "http://localhost:8090"
}
},
"preview": {
"distribution": "internal",
"channel": "preview",
"ios": { "resourceClass": "m-medium" },
"android": { "buildType": "apk" },
"env": {
"EXPO_PUBLIC_PB_URL": "https://staging-pb.rlpadelacademy.nl"
}
},
"production": {
"channel": "production",
"autoIncrement": true,
"android": { "buildType": "app-bundle" },
"env": {
"EXPO_PUBLIC_PB_URL": "https://rl-padel-pb.fly.dev"
}
}
},
"submit": {
"production": {
"ios": {
"appleId": "stijn@stijnbakker.com",
"ascAppId": "0000000000",
"appleTeamId": "XXXXXXXXXX"
},
"android": {
"track": "internal",
"serviceAccountKeyPath": "./google-service-account.json"
}
}
}
}
  • development builds a dev client for the iOS simulator and points at a local PocketBase.
  • preview builds an internally-distributable binary (APK on Android, ad-hoc IPA / TestFlight on iOS) against a staging backend.
  • production builds a store-ready AAB / IPA against the live Fly.io backend and auto-increments the build number.

iOS / TestFlight

Requirements

  • An Apple Developer Program membership ($99/year). This is non-negotiable for any device distribution beyond the simulator.
  • The app registered in App Store Connect with bundle ID nl.rlpadelacademy.app.

Build and submit

Terminal window
# Build a signed IPA in the cloud (EAS manages certificates & provisioning)
eas build --platform ios --profile production
# Upload the resulting build to App Store Connect / TestFlight
eas submit --platform ios --profile production --latest

On first run, EAS offers to create the App Store Connect app record and manage your signing credentials — accept this unless you have an existing setup.

TestFlight groups

In App Store Connect, open TestFlight. There are two kinds of testers:

  • Internal testing — up to 100 users who are members of your App Store Connect team. Builds are available almost immediately with no App Review. Use this for yourself and close collaborators.
  • External testing — up to 10,000 testers invited by email or public link. The first build submitted to an external group requires a short Beta App Review by Apple (usually a few hours to a day). Subsequent builds of the same version are auto-approved.

Create a group, add tester emails (or generate a public link), and assign the build to the group.

How testers install

  1. Tester installs TestFlight from the App Store.
  2. They open the email invite (or public link) and tap View in TestFlight / Accept.
  3. They tap Install, then launch the app from TestFlight.

Testers can leave feedback and attach screenshots directly from TestFlight (take a screenshot while testing, then Share Beta Feedback) — this lands in App Store Connect with device and OS metadata attached.

Android

Android has a lower barrier: a $25 one-time Google Play registration fee, and you can also just share an APK directly for a quick test.

Build

Terminal window
# Quick share: produces an installable APK (sideload / direct download)
eas build --platform android --profile preview
# Store-ready bundle for Google Play
eas build --platform android --profile production

The preview profile sets "buildType": "apk", giving you a single file you can send to a tester via the EAS build page link. They enable “install from unknown sources” and install it — great for fast iteration without the Play Console.

Google Play Internal Testing

For a more realistic path (and to test Play-managed signing and updates):

  1. Create the app in the Google Play Console under bundle ID nl.rlpadelacademy.app.
  2. Create a service account key and save it as apps/frontend/google-service-account.json (referenced in eas.json).
  3. Submit to the Internal testing track:
Terminal window
eas submit --platform android --profile production --latest
  1. In the Play Console, open Testing → Internal testing, create a tester list (email addresses), and share the opt-in link. The internal track has no review delay and supports up to 100 testers.

Pointing builds at the right backend

The app reads EXPO_PUBLIC_PB_URL at build time. Each eas.json profile sets it explicitly:

  • developmenthttp://localhost:8090 (your machine).
  • preview → a staging PocketBase instance.
  • production → the live Fly.io backend.

Use a separate staging PocketBase for testers so they can create accounts, bookings, and other data without polluting production — and so a buggy test build can’t corrupt real members’ data. Spin one up exactly like production (see the Fly.io deployment guide) under a different app name and volume.

OTA updates between builds

For JS / asset-only changes (UI tweaks, copy fixes, logic that doesn’t touch native code), you don’t need a new store build. Push an over-the-air update to the matching channel:

Terminal window
# Ship a JS fix to everyone on the preview build
eas update --branch preview --message "Fix booking confirmation copy"
# ...or to production testers
eas update --branch production --message "Adjust schedule layout"

Testers pick up the update the next time they open the app. Note: anything that changes native code, the SDK version, or app.json native config still requires a fresh eas build.

A pragmatic test plan

Recruit roughly 8 club members spread across the app’s roles so every permission path gets exercised:

  • 4–5 lid (regular members) — booking, schedule, profile.
  • 2 trainer — managing/viewing their sessions.
  • 1–2 eigenaar (owner/admin) — club management, member overview.

Give each tester a short feedback checklist:

  • Onboarding: can you register and log in?
  • Booking: reserve a slot, see it on the schedule, cancel it.
  • Notifications/reminders behave as expected.
  • Anything slow, confusing, visually broken, or crashing?
  • Does the app survive backgrounding and a cold restart?

Ask iOS testers to use TestFlight’s built-in screenshot + feedback flow so reports arrive with device/OS context. Collect Android feedback in a shared doc or your issue tracker.

Versioning reminder

The app is on SDK 56 with the new architecture. Bump the build number on every submissioniOS buildNumber and Android versionCode must be strictly higher than any previously uploaded build or the store will reject it. The production profile above sets "autoIncrement": true with "appVersionSource": "remote", so EAS manages these for you automatically. If you ever build manually, increment them yourself in app.json.