← back to Nineoh Guide
90210: gate Firebase Analytics/Crashlytics behind ATT (fix 5.1.2 pre-consent screen_view)
3b8d02198a817be2b47aaca238baadea36e90936 · 2026-09-03 13:28:02 -0700 · Steve
Was enabling collection at module-import time, so a GA4 screen_view fired before the
ATT prompt resolved (App Store 5.1.2 flag). Now: analytics.ts stays at Firebase's
default (OFF) at import and exposes enableCollectionAfterATT(); App.tsx calls it in the
ATT effect after requestTrackingPermissionsAsync() resolves, before mobileAds().initialize().
AdMob ordering was already correct. Restored from Henry nineoh-guide.bundle (source-only).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YNivxV68DdxP1rvhViBfcN
Files touched
M apps/mobile/App.tsxM apps/mobile/analytics.ts
Diff
commit 3b8d02198a817be2b47aaca238baadea36e90936
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Sep 3 13:28:02 2026 -0700
90210: gate Firebase Analytics/Crashlytics behind ATT (fix 5.1.2 pre-consent screen_view)
Was enabling collection at module-import time, so a GA4 screen_view fired before the
ATT prompt resolved (App Store 5.1.2 flag). Now: analytics.ts stays at Firebase's
default (OFF) at import and exposes enableCollectionAfterATT(); App.tsx calls it in the
ATT effect after requestTrackingPermissionsAsync() resolves, before mobileAds().initialize().
AdMob ordering was already correct. Restored from Henry nineoh-guide.bundle (source-only).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YNivxV68DdxP1rvhViBfcN
---
apps/mobile/App.tsx | 6 +++++-
apps/mobile/analytics.ts | 27 ++++++++++++++++++++++-----
2 files changed, 27 insertions(+), 6 deletions(-)
diff --git a/apps/mobile/App.tsx b/apps/mobile/App.tsx
index 0b8f6fe..aadf20f 100644
--- a/apps/mobile/App.tsx
+++ b/apps/mobile/App.tsx
@@ -23,7 +23,7 @@ import mobileAds, {
} from "react-native-google-mobile-ads";
import { requestTrackingPermissionsAsync } from "expo-tracking-transparency";
import { APP, EpisodeSchema, type Episode } from "@nineoh/core";
-import { logScreen } from "./analytics";
+import { logScreen, enableCollectionAfterATT } from "./analytics";
// Real AdMob banner unit in production; Google's test unit in dev (so we never
// serve/click live ads while developing, per AdMob policy).
@@ -301,6 +301,10 @@ export default function App() {
} catch {
/* user can decline; ads still serve non-personalized */
}
+ // ATT has now resolved (allowed or denied). ONLY now enable Firebase
+ // Analytics + Crashlytics collection, so no event transmits pre-consent
+ // (fixes the App Store 5.1.2 flag: a screen_view firing before ATT).
+ await enableCollectionAfterATT();
try {
await mobileAds().initialize();
} catch {
diff --git a/apps/mobile/analytics.ts b/apps/mobile/analytics.ts
index 714c2c3..3b7ec70 100644
--- a/apps/mobile/analytics.ts
+++ b/apps/mobile/analytics.ts
@@ -11,15 +11,32 @@ import crashlytics from "@react-native-firebase/crashlytics";
let ready = true;
try {
analytics();
- // GoogleService-Info.plist ships with IS_ANALYTICS_ENABLED=false (Firebase's
- // conservative iOS default), so turn collection on explicitly — otherwise no
- // events reach GA4. Crashlytics likewise.
- analytics().setAnalyticsCollectionEnabled(true);
- crashlytics().setCrashlyticsCollectionEnabled(true);
+ // Firebase's iOS default is collection OFF (GoogleService-Info.plist ships
+ // IS_ANALYTICS_ENABLED=false). We intentionally DO NOT enable it here at
+ // module-import time: that let a screen_view transmit before the App Tracking
+ // Transparency prompt was answered (an App Store 5.1.2 flag). Collection is
+ // now enabled only AFTER ATT resolves, via enableCollectionAfterATT() below,
+ // called from App.tsx. Until then Firebase stays at its default (OFF), so the
+ // early logScreen effect transmits nothing pre-consent.
} catch {
ready = false;
}
+/**
+ * Enable Firebase Analytics + Crashlytics collection. MUST be called ONLY after
+ * the ATT prompt has resolved (see App.tsx), so nothing is transmitted
+ * pre-consent. Safe no-op if the native Firebase module isn't present.
+ */
+export async function enableCollectionAfterATT(): Promise<void> {
+ if (!ready) return;
+ try {
+ await analytics().setAnalyticsCollectionEnabled(true);
+ await crashlytics().setCrashlyticsCollectionEnabled(true);
+ } catch {
+ /* non-fatal */
+ }
+}
+
/** Log a screen view (call whenever the visible tab/screen changes). */
export async function logScreen(screen: string): Promise<void> {
if (!ready) return;
← ca26827 Refresh ASC blocker board to live truth (5 in review, 2 subm
·
back to Nineoh Guide
·
eas: pin production ios.image=latest (ITMS-90725 guard) befo 8d5fd18 →