Skip to content

Kwilo Learn app reference

Operational reference for the consumer Android app. For what the app does, see Kwilo Learn mobile app. The institutional app, Kwilo Campus, is apps/mobile-b2b (ai.kwilo.campus) in the webapp repo.

No secrets on this page. Token values live in the team secret manager, GitHub environments and EAS secrets.

Identifiers

Item Value
Source MySetu-AI/kwiloai-b2c, apps/mobile
Display name Kwilo Learn
Android package / iOS bundle id ai.kwilo.learn (iOS declared, nothing ships)
EAS project kwiloai/kwilo-b2c
Signing EAS-managed Android key
Runtime version policy fingerprint
Stack React Native + Expo (new architecture), expo-router, NativeWind, @kwilo/ui-native, Zustand, TanStack Query
Staging API The B2C staging Container App origin, called directly
Production API https://b2c-api.kwilo.ai/api/v1, In build

The app talks only to the consumer API. It never calls api.kwilo.ai or api-staging.kwilo.ai.

Environment variables

Expo inlines every EXPO_PUBLIC_* value into the JS bundle at build time. Defaults sit in apps/mobile/eas.json per profile; override locally in .env.local.

Var Purpose
EXPO_PUBLIC_API_URL Consumer backend base URL
EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID Google OAuth Web client id, the only one the native SDK reads
EXPO_PUBLIC_POSTHOG_KEY PostHog token. Production profile only; blank disables analytics
EXPO_PUBLIC_POSTHOG_HOST https://t.kwilo.ai
EXPO_PUBLIC_ENVIRONMENT The analytics environment super property

These values ship in the binary

Anything EXPO_PUBLIC_* is extractable from a distributed APK or AAB. Never put a credential there. The staging build workflow fails if an Access token ends up in the bundle.

Google Sign-In

The native SDK authenticates against the Web OAuth client, which the backend trusts. An Android OAuth client (package + signing SHA-1) must still exist in vidyanet-staging so Google recognises the app.

Every keystore has its own SHA-1: the EAS upload key, Play App Signing, and each developer's debug keystore. A build whose SHA-1 isn't registered fails with DEVELOPER_ERROR.

eas credentials -p android          # EAS keystore SHA-1
keytool -keystore ~/.android/debug.keystore -list -v \
  -alias androiddebugkey -storepass android

Builds and releases

Workflow Does
deploy-mobile-staging.yml Manual, from staging. Local EAS build of the preview APK against staging, checked for the right API origin and no embedded credentials, uploaded as a run artifact.
mobile-release-label.yml Every PR touching mobile needs exactly one label: major, minor, patch or skip-release.
mobile-play.yml On merge to main: computes the version from labels, tags learn-vX.Y.Z, builds an AAB and submits to Play internal testing. Gated on B2C_PLAY_RELEASE_ENABLED.

Version code is major * 10000 + minor * 100 + patch. Promotion past internal testing is a manual step in Play Console.

Native builds run locally on the runner (eas build --local), so they don't spend the Expo cloud build quota and still use the EAS-managed keystore. Generating a fresh keystore instead breaks Google Sign-In.

Fingerprint rule

runtimeVersion.policy: "fingerprint". A JS-only change keeps the fingerprint, so an EAS Update reaches installed builds. A native change (new module, SDK bump, config plugin) changes it; an OTA would be invisible, so a new build is required.

Notifications

Local reminders and streak nudges need no credentials. Remote push needs FCM, which Expo's relay wants as a service-account key, and the org blocks key creation (constraints/iam.disableServiceAccountKeyCreation). Until that's solved, devices register a push token and nothing is delivered.

Troubleshooting

Symptom Cause / fix
DEVELOPER_ERROR on Google Sign-In Signing SHA-1 not registered on the Android OAuth client
Merged JS change never arrives Fingerprint changed; cut a native build
Cold launch lands on /login instead of /welcome The 401 interceptor must reject the boot /auth/me probe, not redirect
Gesture handlers never fire react-native-gesture-handler doesn't receive touches under the new architecture here. Use PanResponder + Animated
Animation at the router root doesn't run Reanimated doesn't run at the expo-router root. Use core Animated
CI build dies with kill ESRCH at expo doctor Race in the local-build plugin. Re-run; the staging workflow skips the doctor step

Open risk: keystore in failed-build logs

When eas build --local fails, the plugin prints its job payload, which contains the signing keystore and its passwords in base64. Anyone who can read the Actions logs can sign an APK as Kwilo. Mitigations, cheapest first: filter the payload out of the log, restrict log access, rotate the keystore (breaks Sign-In until the new SHA-1 is registered and forces reinstalls).