From 8b4554075754e18eeae69d4271a6e8f657864661 Mon Sep 17 00:00:00 2001 From: sokoloff06 Date: Sat, 9 May 2026 14:48:03 -0400 Subject: [PATCH 1/4] feat(analytics): add support for googleAppMeasurementOnDeviceConversion in iOS Expo plugin --- .../__snapshots__/iosPlugin.test.ts.snap | 13 ++++++++ .../plugin/__tests__/iosPlugin.test.ts | 21 ++++++++++++- packages/analytics/plugin/src/index.ts | 3 +- packages/analytics/plugin/src/ios/index.ts | 4 +-- packages/analytics/plugin/src/ios/podfile.ts | 31 +++++++++++++++++++ packages/analytics/plugin/src/pluginConfig.ts | 4 +++ 6 files changed, 72 insertions(+), 4 deletions(-) diff --git a/packages/analytics/plugin/__tests__/__snapshots__/iosPlugin.test.ts.snap b/packages/analytics/plugin/__tests__/__snapshots__/iosPlugin.test.ts.snap index 8e0ea488f8..be03ea66f9 100644 --- a/packages/analytics/plugin/__tests__/__snapshots__/iosPlugin.test.ts.snap +++ b/packages/analytics/plugin/__tests__/__snapshots__/iosPlugin.test.ts.snap @@ -1,5 +1,18 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +exports[`Analytics Config Plugin iOS Tests adds the Podfile flag when googleAppMeasurementOnDeviceConversion is enabled 1`] = ` +"platform :ios, '15.0' + +prepare_react_native_project! +# @generated begin @react-native-firebase/analytics-googleAppMeasurementOnDeviceConversion - expo prebuild (DO NOT MODIFY) sync-6d1952a1f7b9ccb2d313cbd66659db4cdeae591f +$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true +# @generated end @react-native-firebase/analytics-googleAppMeasurementOnDeviceConversion + +target 'ReactNativeFirebaseDemo' do +end +" +`; + exports[`Analytics Config Plugin iOS Tests adds the Podfile flag when withoutAdIdSupport is enabled 1`] = ` "platform :ios, '15.0' diff --git a/packages/analytics/plugin/__tests__/iosPlugin.test.ts b/packages/analytics/plugin/__tests__/iosPlugin.test.ts index a2014345dc..c7940a54c7 100644 --- a/packages/analytics/plugin/__tests__/iosPlugin.test.ts +++ b/packages/analytics/plugin/__tests__/iosPlugin.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from '@jest/globals'; -import { setAnalyticsPodfileWithoutAdIdSupport } from '../src/ios/podfile'; +import { setAnalyticsPodfileWithoutAdIdSupport, setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion } from '../src/ios/podfile'; const podfileFixture = `platform :ios, '15.0' @@ -29,4 +29,23 @@ describe('Analytics Config Plugin iOS Tests', function () { expect(restored).toEqual(podfileFixture); }); + + it('adds the Podfile flag when googleAppMeasurementOnDeviceConversion is enabled', function () { + const result = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion(podfileFixture, true); + expect(result).toMatchSnapshot(); + }); + + it('is idempotent when the ODM Podfile flag is already present', function () { + const onceModified = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion(podfileFixture, true); + const twiceModified = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion(onceModified, true); + + expect(twiceModified).toEqual(onceModified); + }); + + it('removes the generated Podfile flag when googleAppMeasurementOnDeviceConversion is disabled', function () { + const onceModified = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion(podfileFixture, true); + const restored = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion(onceModified, false); + + expect(restored).toEqual(podfileFixture); + }); }); diff --git a/packages/analytics/plugin/src/index.ts b/packages/analytics/plugin/src/index.ts index fb2cd5e10a..66ae941ee4 100644 --- a/packages/analytics/plugin/src/index.ts +++ b/packages/analytics/plugin/src/index.ts @@ -1,6 +1,6 @@ import { ConfigPlugin, withPlugins, createRunOncePlugin } from '@expo/config-plugins'; -import { withIosWithoutAdIdSupport } from './ios'; +import { withIosWithoutAdIdSupport, withIosGoogleAppMeasurementOnDeviceConversion } from './ios'; import { PluginConfigType } from './pluginConfig'; /** @@ -10,6 +10,7 @@ const withRnFirebaseAnalytics: ConfigPlugin = (config, props) return withPlugins(config, [ // iOS [withIosWithoutAdIdSupport, props], + [withIosGoogleAppMeasurementOnDeviceConversion, props], ]); }; diff --git a/packages/analytics/plugin/src/ios/index.ts b/packages/analytics/plugin/src/ios/index.ts index b63d644c3b..e642d277ac 100644 --- a/packages/analytics/plugin/src/ios/index.ts +++ b/packages/analytics/plugin/src/ios/index.ts @@ -1,3 +1,3 @@ -import { withIosWithoutAdIdSupport } from './podfile'; +import { withIosWithoutAdIdSupport, withIosGoogleAppMeasurementOnDeviceConversion } from './podfile'; -export { withIosWithoutAdIdSupport }; +export { withIosWithoutAdIdSupport, withIosGoogleAppMeasurementOnDeviceConversion }; diff --git a/packages/analytics/plugin/src/ios/podfile.ts b/packages/analytics/plugin/src/ios/podfile.ts index 0d3e2f4cbf..dea728b17b 100644 --- a/packages/analytics/plugin/src/ios/podfile.ts +++ b/packages/analytics/plugin/src/ios/podfile.ts @@ -9,6 +9,8 @@ import { PluginConfigType } from '../pluginConfig'; const TAG = '@react-native-firebase/analytics-withoutAdIdSupport'; const ANCHOR = /prepare_react_native_project!/; const FLAG = '$RNFirebaseAnalyticsWithoutAdIdSupport = true'; +const TAG_ODM = '@react-native-firebase/analytics-googleAppMeasurementOnDeviceConversion'; +const FLAG_ODM = '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true'; export function setAnalyticsPodfileWithoutAdIdSupport( src: string, @@ -28,6 +30,35 @@ export function setAnalyticsPodfileWithoutAdIdSupport( }).contents; } +export function setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion( + src: string, + enabled: boolean = false, +): string { + if (!enabled) { + return removeGeneratedContents(src, TAG_ODM) ?? src; + } + + return mergeContents({ + src, + newSrc: FLAG_ODM, + tag: TAG_ODM, + anchor: ANCHOR, + offset: 1, + comment: '#', + }).contents; +} + +export const withIosGoogleAppMeasurementOnDeviceConversion: ConfigPlugin = (config, props) => { + return withPodfile(config, config => { + config.modResults.contents = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion( + config.modResults.contents, + props?.ios?.googleAppMeasurementOnDeviceConversion === true, + ); + + return config; + }); +}; + export const withIosWithoutAdIdSupport: ConfigPlugin = (config, props) => { return withPodfile(config, config => { config.modResults.contents = setAnalyticsPodfileWithoutAdIdSupport( diff --git a/packages/analytics/plugin/src/pluginConfig.ts b/packages/analytics/plugin/src/pluginConfig.ts index 29aa84c79a..b8856086f3 100644 --- a/packages/analytics/plugin/src/pluginConfig.ts +++ b/packages/analytics/plugin/src/pluginConfig.ts @@ -4,4 +4,8 @@ export interface PluginConfigType { export interface PluginConfigTypeIos { withoutAdIdSupport?: boolean; + /** + * @platform ios iOS + */ + googleAppMeasurementOnDeviceConversion?: boolean; } From 0bcad043b77ed57a179343c6e1072733ba7fd61b Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 13 May 2026 11:07:47 +0100 Subject: [PATCH 2/4] test: ensure both flags are written when setting both options --- .../__snapshots__/iosPlugin.test.ts.snap | 16 ++++++++++ .../plugin/__tests__/iosPlugin.test.ts | 30 ++++++++++++++++--- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/packages/analytics/plugin/__tests__/__snapshots__/iosPlugin.test.ts.snap b/packages/analytics/plugin/__tests__/__snapshots__/iosPlugin.test.ts.snap index be03ea66f9..2affb5c3d2 100644 --- a/packages/analytics/plugin/__tests__/__snapshots__/iosPlugin.test.ts.snap +++ b/packages/analytics/plugin/__tests__/__snapshots__/iosPlugin.test.ts.snap @@ -1,5 +1,21 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +exports[`Analytics Config Plugin iOS Tests adds both iOS Podfile flags when both analytics options are enabled 1`] = ` +"platform :ios, '15.0' + +prepare_react_native_project! +# @generated begin @react-native-firebase/analytics-googleAppMeasurementOnDeviceConversion - expo prebuild (DO NOT MODIFY) sync-6d1952a1f7b9ccb2d313cbd66659db4cdeae591f +$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true +# @generated end @react-native-firebase/analytics-googleAppMeasurementOnDeviceConversion +# @generated begin @react-native-firebase/analytics-withoutAdIdSupport - expo prebuild (DO NOT MODIFY) sync-06c0e725ab83bc834a9294f76fea47cf14bb6ac3 +$RNFirebaseAnalyticsWithoutAdIdSupport = true +# @generated end @react-native-firebase/analytics-withoutAdIdSupport + +target 'ReactNativeFirebaseDemo' do +end +" +`; + exports[`Analytics Config Plugin iOS Tests adds the Podfile flag when googleAppMeasurementOnDeviceConversion is enabled 1`] = ` "platform :ios, '15.0' diff --git a/packages/analytics/plugin/__tests__/iosPlugin.test.ts b/packages/analytics/plugin/__tests__/iosPlugin.test.ts index c7940a54c7..71c89c32ac 100644 --- a/packages/analytics/plugin/__tests__/iosPlugin.test.ts +++ b/packages/analytics/plugin/__tests__/iosPlugin.test.ts @@ -1,6 +1,9 @@ import { describe, expect, it } from '@jest/globals'; -import { setAnalyticsPodfileWithoutAdIdSupport, setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion } from '../src/ios/podfile'; +import { + setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion, + setAnalyticsPodfileWithoutAdIdSupport, +} from '../src/ios/podfile'; const podfileFixture = `platform :ios, '15.0' @@ -36,16 +39,35 @@ describe('Analytics Config Plugin iOS Tests', function () { }); it('is idempotent when the ODM Podfile flag is already present', function () { - const onceModified = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion(podfileFixture, true); - const twiceModified = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion(onceModified, true); + const onceModified = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion( + podfileFixture, + true, + ); + const twiceModified = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion( + onceModified, + true, + ); expect(twiceModified).toEqual(onceModified); }); it('removes the generated Podfile flag when googleAppMeasurementOnDeviceConversion is disabled', function () { - const onceModified = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion(podfileFixture, true); + const onceModified = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion( + podfileFixture, + true, + ); const restored = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion(onceModified, false); expect(restored).toEqual(podfileFixture); }); + + it('adds both iOS Podfile flags when both analytics options are enabled', function () { + const withWithoutAdIdSupport = setAnalyticsPodfileWithoutAdIdSupport(podfileFixture, true); + const result = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion( + withWithoutAdIdSupport, + true, + ); + + expect(result).toMatchSnapshot(); + }); }); From 136f0df2a491d8df89d5936633b583164f43415c Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 13 May 2026 11:08:05 +0100 Subject: [PATCH 3/4] docs: update how to set both analytics expo flags --- docs/analytics/usage/index.mdx | 40 ++++++++++++++++++++++++++++++++++ docs/index.mdx | 7 +++--- packages/analytics/README.md | 7 +++--- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/docs/analytics/usage/index.mdx b/docs/analytics/usage/index.mdx index 157ee8e199..67b60a87ff 100644 --- a/docs/analytics/usage/index.mdx +++ b/docs/analytics/usage/index.mdx @@ -169,8 +169,48 @@ During `pod install`, using that variable installs the `FirebaseAnalytics/Core` or Firebase Analytics without needing the App Tracking Transparency handling (assuming no other parts of your app handle data in a way that requires ATT) +If you use Expo, including EAS Build, add the Analytics config plugin to your `app.json` / `app.config.js` instead of editing the generated Podfile manually: + +```json +[ + "@react-native-firebase/analytics", + { + "ios": { + "withoutAdIdSupport": true + } + } +] +``` + +This adds `$RNFirebaseAnalyticsWithoutAdIdSupport = true` to the generated iOS `Podfile` during prebuild. + Note that for obvious reasons, configuring Firebase Analytics for use without IDFA is incompatible with AdMob +# Google Analytics on-device conversion measurement + +If you would like to enable Google Analytics on-device conversion measurement APIs on iOS, define the following variable in your Podfile: + +```ruby +$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true +``` + +During `pod install`, using that variable adds the `GoogleAdsOnDeviceConversion` Pod. + +If you use Expo, including EAS Build, add the Analytics config plugin to your `app.json` / `app.config.js` instead of editing the generated Podfile manually: + +```json +[ + "@react-native-firebase/analytics", + { + "ios": { + "googleAppMeasurementOnDeviceConversion": true + } + } +] +``` + +This adds `$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true` to the generated iOS `Podfile` during prebuild. + # Device Identification If you would like to enable Firebase Analytics to generate automatic audience metrics for iOS (as it does by default in Android), you must link additional iOS libraries, [as documented by the Google Firebase team](https://support.google.com/firebase/answer/6318039). Specifically you need to link in `AdSupport.framework`. diff --git a/docs/index.mdx b/docs/index.mdx index 55f47aa079..2b791c1715 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -78,20 +78,21 @@ The following is an example `app.json` to enable the React Native Firebase modul > Listing a module in the Config Plugins (the `"plugins"` array in the JSON above) is only required for React Native Firebase modules that involve _native installation steps_ - e.g. modifying the Xcode project, `Podfile`, `build.gradle`, `AndroidManifest.xml` etc. React Native Firebase modules without native steps will work out of the box; no `"plugins"` entry is required. Not all modules have Expo Config Plugins provided yet. A React Native Firebase module has Config Plugin support if it contains an `app.plugin.js` file in its package directory (e.g.`node_modules/@react-native-firebase/app/app.plugin.js`). -If you use `@react-native-firebase/analytics` with Expo, including EAS Build, and want to opt out of iOS Ad ID support, add the Analytics config plugin with the `withoutAdIdSupport` iOS option: +If you use `@react-native-firebase/analytics` with Expo, including EAS Build, and want to configure iOS Analytics Podfile flags, add the Analytics config plugin with the relevant iOS options: ```json [ "@react-native-firebase/analytics", { "ios": { - "withoutAdIdSupport": true + "withoutAdIdSupport": true, + "googleAppMeasurementOnDeviceConversion": true } } ] ``` -This adds `$RNFirebaseAnalyticsWithoutAdIdSupport = true` to the generated iOS `Podfile` during prebuild. +The `withoutAdIdSupport` option adds `$RNFirebaseAnalyticsWithoutAdIdSupport = true` to opt out of iOS Ad ID support. The `googleAppMeasurementOnDeviceConversion` option adds `$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true` to include Google Analytics on-device conversion measurement support. You may omit either option if it is not needed. ### Local app compilation diff --git a/packages/analytics/README.md b/packages/analytics/README.md index d9db2dd6e9..ee9e49cab8 100644 --- a/packages/analytics/README.md +++ b/packages/analytics/README.md @@ -40,7 +40,7 @@ yarn add @react-native-firebase/analytics ### Expo -If you use Expo, including EAS Build, and want to build iOS Analytics without Ad ID support, add the Analytics config plugin to your `app.json` / `app.config.js`: +If you use Expo, including EAS Build, and want to configure iOS Analytics Podfile flags, add the Analytics config plugin to your `app.json` / `app.config.js`: ```json { @@ -50,7 +50,8 @@ If you use Expo, including EAS Build, and want to build iOS Analytics without Ad "@react-native-firebase/analytics", { "ios": { - "withoutAdIdSupport": true + "withoutAdIdSupport": true, + "googleAppMeasurementOnDeviceConversion": true } } ] @@ -59,7 +60,7 @@ If you use Expo, including EAS Build, and want to build iOS Analytics without Ad } ``` -This adds `$RNFirebaseAnalyticsWithoutAdIdSupport = true` to the generated iOS `Podfile` during prebuild, which excludes `FirebaseAnalytics/IdentitySupport`. +The `withoutAdIdSupport` option adds `$RNFirebaseAnalyticsWithoutAdIdSupport = true` during prebuild, which excludes `FirebaseAnalytics/IdentitySupport`. The `googleAppMeasurementOnDeviceConversion` option adds `$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true`, which includes Google Analytics on-device conversion measurement support. You may omit either option if it is not needed. ## Documentation From e8d4de2f88804679686a2410a8c7608649a33a70 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 13 May 2026 11:11:39 +0100 Subject: [PATCH 4/4] chore: remove duplicate methods --- packages/analytics/plugin/src/ios/podfile.ts | 35 ++++++++++---------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/analytics/plugin/src/ios/podfile.ts b/packages/analytics/plugin/src/ios/podfile.ts index dea728b17b..23c30196e4 100644 --- a/packages/analytics/plugin/src/ios/podfile.ts +++ b/packages/analytics/plugin/src/ios/podfile.ts @@ -12,43 +12,44 @@ const FLAG = '$RNFirebaseAnalyticsWithoutAdIdSupport = true'; const TAG_ODM = '@react-native-firebase/analytics-googleAppMeasurementOnDeviceConversion'; const FLAG_ODM = '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true'; -export function setAnalyticsPodfileWithoutAdIdSupport( +function setAnalyticsPodfileFlag( src: string, + tag: string, + flag: string, enabled: boolean = false, ): string { if (!enabled) { - return removeGeneratedContents(src, TAG) ?? src; + return removeGeneratedContents(src, tag) ?? src; } return mergeContents({ src, - newSrc: FLAG, - tag: TAG, + newSrc: flag, + tag, anchor: ANCHOR, offset: 1, comment: '#', }).contents; } -export function setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion( +export function setAnalyticsPodfileWithoutAdIdSupport( src: string, enabled: boolean = false, ): string { - if (!enabled) { - return removeGeneratedContents(src, TAG_ODM) ?? src; - } + return setAnalyticsPodfileFlag(src, TAG, FLAG, enabled); +} - return mergeContents({ - src, - newSrc: FLAG_ODM, - tag: TAG_ODM, - anchor: ANCHOR, - offset: 1, - comment: '#', - }).contents; +export function setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion( + src: string, + enabled: boolean = false, +): string { + return setAnalyticsPodfileFlag(src, TAG_ODM, FLAG_ODM, enabled); } -export const withIosGoogleAppMeasurementOnDeviceConversion: ConfigPlugin = (config, props) => { +export const withIosGoogleAppMeasurementOnDeviceConversion: ConfigPlugin = ( + config, + props, +) => { return withPodfile(config, config => { config.modResults.contents = setAnalyticsPodfileGoogleAppMeasurementOnDeviceConversion( config.modResults.contents,