Repository navigation
Firebase Setup
The app uses three Firebase products: Authentication (Google Sign-In via Credential Manager), Firestore (subscriptions & payments), and Analytics (flow audit). All against project helloworld-92567418 (display name Varisankya).
| Project ID | helloworld-92567418 |
| Project number | 663138385072 |
| Android app ID | 1:663138385072:android:dfcdd7f25c0171c56e0f75 |
| Package | com.hora.varisankya |
| Web OAuth Client (for Credential Manager) | 663138385072-bke7f5oflsl2cg0e5maks0ef3n6o113u.apps.googleusercontent.com |
| GA4 Property ID |
538794512 (linked May 2026) |
| GA4 Stream ID | 14938433408 |
The Web Client ID is currently hardcoded as WEB_CLIENT_ID in MainActivity.kt. The Google Services plugin also generates R.string.default_web_client_id from google-services.json; consider switching to that to avoid duplication (see Known Issues).
The file lives at app/google-services.json and is gitignored. The canonical copy lives in Bitwarden, split into two base64 chunks (google-services.json [Part 1] + [Part 2]); retrieve_secrets.sh reassembles them.
About the "API key" inside: Firebase Android API keys are identifiers, not secrets. Google explicitly documents that committing google-services.json is fine — access is gated by Android package name + SHA-1 fingerprint + Firestore rules, not by the key. We keep it out of git anyway to follow Play Console hygiene and to keep the keystore + Play service account next to it (those genuinely are secrets) without needing a partial gitignore policy.
If you ever change Firebase project state in the Console (add a new SHA-1, link analytics, etc.), the in-repo file goes stale. Re-pull via:
./retrieve_secrets.sh…or, for a manual fetch via the API (no Bitwarden involved):
# requires firebase CLI logged in (token at ~/.config/configstore/firebase-tools.json)
python -c "
import json, base64, urllib.request
d = json.load(open('<HOME>/.config/configstore/firebase-tools.json'))
tok = d['tokens']['access_token']
app_id = '1:663138385072:android:dfcdd7f25c0171c56e0f75'
url = f'https://firebase.googleapis.com/v1beta1/projects/helloworld-92567418/androidApps/{app_id}/config'
req = urllib.request.Request(url, headers={'Authorization': 'Bearer ' + tok})
resp = json.load(urllib.request.urlopen(req))
print(base64.b64decode(resp['configFileContents']).decode())
"Then re-encode and update the Bitwarden item — both halves — so the next ./retrieve_secrets.sh produces the same file.
Implemented via the modern Credential Manager API + GoogleIdTokenCredential — not the deprecated GoogleSignInClient. The flow in MainActivity.signInWithGoogle():
- Build
GetGoogleIdOptionwith the project's Web OAuth Client ID andsetFilterByAuthorizedAccounts(false). -
credentialManager.getCredential(activity, request)— Credential Manager shows the system picker. - Extract the ID token from the returned
GoogleIdTokenCredential. -
FirebaseAuth.signInWithCredential(GoogleAuthProvider.getCredential(idToken, null)). - On success, set up notifications, load subscriptions, observe ViewModel.
Sign-out: auth.signOut() and credentialManager.clearCredentialState(ClearCredentialStateRequest()) (the latter clears the cached account so the user can re-pick on next sign-in).
The Firebase Android app must know the SHA-1 of every signing key used to build APKs that try to sign in:
| Keystore | SHA-1 | Notes |
|---|---|---|
| Original debug keystore | 2b2a3169ef99488ef78ecb66a2f3aee5101bd92a |
Windows dev machine |
| Linux debug keystore | 4e2eb5c5911b5a2de2a696f78ee3f081d9243b8f |
Headless Ubuntu CLI |
| (additional debug keystore) | 1c9de1700cd98ef4dba8bdfff54e05ff04931f9d |
Auto-added Apr 2026 |
| Upload keystore | 5cb5895b7729a1d10b90cf0122727d2ef345b6f9 |
Play upload key |
| Play App Signing | d0fb3d47fa0aade1b062332864ab6d3837e1a00c |
Cert Play uses to re-sign |
To add a new fingerprint:
./gradlew :app:signingReport # grab SHA1 for the variant you care about
# Firebase Console → Project settings → Your apps → (Android) → Add fingerprint → paste
./retrieve_secrets.sh # refresh google-services.json + update Bitwarden afterwardsThe deployed rules live in firestore.rules and the Firebase project config in firebase.json + .firebaserc.
Current rules (verbatim):
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// A signed-in user has full read/write access to everything under their
// own /users/{userId} subtree.
match /users/{userId}/{document=**} {
allow read, write: if request.auth != null
&& request.auth.uid == userId;
}
// Collection-group fallback used by the All-Payments page when the flat
// collection hasn't been populated yet.
match /{path=**}/payments/{paymentId} {
allow read: if request.auth != null
&& resource.data.userId == request.auth.uid;
}
}
}
The {document=**} rule covers both the legacy nested users/{uid}/subscriptions/{sid}/payments/{pid} path and the flat users/{uid}/payments/{pid} path introduced by the migration. The collection-group rule is what unlocked the fast path for All Payments (without it, collectionGroup("payments") was denied for all users, silently degrading to the slow fan-out).
To redeploy after editing the file:
firebase deploy --only firestore:rules --project helloworld-92567418(Requires firebase login once.)
Linked May 2026. The Firebase Analytics SDK was always wired up (it's firebase-analytics in dependencies, and Analytics.init() is called from VarisankyaApplication.onCreate), but events had nowhere to land until the GA4 property was created and the stream mapping established.
To view events: see Analytics & Audit for the dashboard URL, event taxonomy, and DebugView setup.
To re-verify the link state programmatically:
python -c "
import json, urllib.request
d = json.load(open('<HOME>/.config/configstore/firebase-tools.json'))
tok = d['tokens']['access_token']
url = 'https://firebase.googleapis.com/v1beta1/projects/helloworld-92567418/analyticsDetails'
print(json.dumps(json.load(urllib.request.urlopen(
urllib.request.Request(url, headers={'Authorization': 'Bearer ' + tok}))), indent=2))
"A linked project returns a streamMappings array (the Android app's stream ID) and an analyticsProperty with the property+account IDs. An unlinked project returns an empty body.
| Symptom | Cause | Fix |
|---|---|---|
Sign-in fails with DEVELOPER_ERROR / ApiException: 10
|
SHA-1 of build keystore not in Firebase |
./gradlew :app:signingReport, add fingerprint, refresh google-services.json
|
NoCredentialException |
Device has no Google account; or hit the system-picker timeout | Add a Google account on the device; try again |
idToken null after Credential Manager returns |
Wrong Web Client ID passed to GetGoogleIdOption
|
Confirm WEB_CLIENT_ID matches the project's Web OAuth client (not Android) |
| Firestore writes succeed but reads return empty | Rules blocked the read; check Logcat for PERMISSION_DENIED
|
Re-deploy firestore.rules
|
| All-Payments page is slow | Collection-group rule missing (pre-rules_version=2) or no userId field on payments |
Confirm rule deployed; confirm new payments carry the userId field (they do — PaymentRepository enforces it) |
| Analytics events not appearing in console | First load after enable can take ~24h; or wrong Firebase project selected in console | Use DebugView for immediate confirmation; switch to project Varisankya |
- Data Model & Migrations — what we're actually storing in Firestore.
- Analytics & Audit — what events flow + how to read them.
- Troubleshooting — broader build & runtime issues.
Source: aarshps/varisankya-android · This wiki is human-facing; the repo itself targets AI coding agents (AGENTS.md).