[object Object]

← 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

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 →