Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ios, android): Add support for app.config.js #517

Merged
merged 13 commits into from
Jan 8, 2024
40 changes: 32 additions & 8 deletions android/app-json.gradle
Original file line number Diff line number Diff line change
@@ -1,30 +1,54 @@
import groovy.json.JsonOutput
import groovy.json.JsonSlurper

String fileName = "app.json"
String[] fileNames = ["app.json", "app.config.js"]
String fileName = null
String jsonRoot = "react-native-google-mobile-ads"
String jsonRaw = "GOOGLE_MOBILE_ADS_JSON_RAW"

File jsonFile = null
File configFile = null
File parentDir = rootProject.projectDir

for (int i = 0; i <= 3; i++) {
if (parentDir == null) break
parentDir = parentDir.parentFile
if (parentDir != null) {
jsonFile = new File(parentDir, fileName)
if (jsonFile.exists()) break
configFile = new File(parentDir, fileNames[0])
if (configFile.exists()) {
fileName = fileNames[0]
break
}
else {
configFile = new File(parentDir, fileNames[1])
if (configFile.exists()) {
fileName = fileNames[0]
break
}
}
}
}

if (jsonFile != null && jsonFile.exists()) {
rootProject.logger.info ":${project.name} ${fileName} found at ${jsonFile.toString()}"
if (configFile != null && configFile.exists()) {
rootProject.logger.info ":${project.name} ${fileName} found at ${configFile.toString()}"
Object json = null

try {
json = new JsonSlurper().parseText(jsonFile.text)
def configOutput = [
mikehardy marked this conversation as resolved.
Show resolved Hide resolved
"node",
"-e",
"console.log(JSON.stringify(require('${configFile.absolutePath}')));"
]
.execute(null, projectDir)
.text
.trim()

if (configOutput && !configOutput.isEmpty()) {
json = new JsonSlurper().parseText(configOutput.toString())
} else {
rootProject.logger.warn ":${project.name} received empty output while parsing ${configFile} found at ${configFile.toString()}."
}
} catch (Exception ignored) {
rootProject.logger.warn ":${project.name} failed to parse ${fileName} found at ${jsonFile.toString()}."
rootProject.logger.warn ":${project.name} failed to parse ${configFile} found at ${configFile.toString()}."
rootProject.logger.warn ignored.toString()
}

Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ if (!appJSONGoogleMobileAdsAppIDString) {
println "\n\n\n"
println "**************************************************************************************************************"
println "\n\n\n"
println "ERROR: react-native-google-mobile-ads requires an 'android_app_id' property inside a 'react-native-google-mobile-ads' key in your app.json."
println "ERROR: react-native-google-mobile-ads requires an 'android_app_id' property inside a 'react-native-google-mobile-ads' key in your app.json/app.config.js."
mikehardy marked this conversation as resolved.
Show resolved Hide resolved
println " No android_app_id property was found in this location. The native Google Mobile Ads SDK will crash on startup without it."
println "\n\n\n"
println "**************************************************************************************************************"
Expand Down
141 changes: 136 additions & 5 deletions docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ yarn add react-native-google-mobile-ads
On iOS if you need to use static frameworks (that is, `use_frameworks! :linkage => :static` in your `Podfile`) you must add the variable `$RNGoogleMobileAdsAsStaticFramework = true` to the targets in your `Podfile`. You may need this if you use this module in combination with react-native-firebase v15 and higher since it requires `use_frameworks!`.

Expo users may enable static frameworks by using the `expo-build-properties` plugin.
To do so [follow the official `expo-build-properties` installation instructions](https://docs.expo.dev/versions/latest/sdk/build-properties/) and merge the following code into your `app.json` file:
To do so [follow the official `expo-build-properties` installation instructions](https://docs.expo.dev/versions/latest/sdk/build-properties/) and merge the following code into your `app.json`/`app.config.js` file:
mikehardy marked this conversation as resolved.
Show resolved Hide resolved

#### app.json

```json
{
Expand All @@ -31,6 +33,25 @@ To do so [follow the official `expo-build-properties` installation instructions]
}
```

#### app.config.js

```js
{
expo: {
plugins: [
[
'expo-build-properties',
{
ios: {
useFrameworks: 'static',
},
},
],
];
}
}
```

## What does it do

The AdMob module allows you to display adverts to your users. All adverts are served over the Google AdMob network, meaning
Expand Down Expand Up @@ -63,15 +84,17 @@ Before you are able to display ads to your users, you must have a [Google AdMob
"Apps" menu item, create or choose an existing Android/iOS app. Each app platform exposes a unique account ID which needs to
be added to the project.

> Attempting to build your app without a valid App ID in `app.json` will cause the app to crash on start or fail to build.
> Attempting to build your app without a valid App ID in `app.json`/`app.config.js` will cause the app to crash on start or fail to build.
mikehardy marked this conversation as resolved.
Show resolved Hide resolved

Under the "App settings" menu item, you can find the "App ID":

![Google AdMob App ID](https://prismic-io.s3.amazonaws.com/invertase%2F52dd6900-108c-47a6-accb-699fde963b99_new+project+%2813%29.jpg)

Within the root of your React Native project, open the `app.json` file and add the
Within the root of your React Native project, open the `app.json`/`app.config.js` file and add the
mikehardy marked this conversation as resolved.
Show resolved Hide resolved
`android_app_id` & `ios_app_id` keys with the IDs from the Google AdMob console:

#### app.json

```json
// <project-root>/app.json
{
Expand All @@ -82,9 +105,23 @@ Within the root of your React Native project, open the `app.json` file and add t
}
```

#### app.config.js

```js
// <project-root>/app.config.js
module.exports = {
'react-native-google-mobile-ads': {
android_app_id: 'ca-app-pub-xxxxxxxx~xxxxxxxx',
ios_app_id: 'ca-app-pub-xxxxxxxx~xxxxxxxx',
},
};
```

If you're an expo user, make sure the `react-native-google-mobile-ads` block is outside of the `expo` block!
It should look like this:

#### app.json

```json
// <project-root>/app.json
{
Expand All @@ -98,6 +135,21 @@ It should look like this:
}
```

#### app.config.js

```js
// <project-root>/app.config.js
module.exports = {
expo: {
// ...
},
'react-native-google-mobile-ads': {
android_app_id: 'ca-app-pub-xxxxxxxx~xxxxxxxx',
ios_app_id: 'ca-app-pub-xxxxxxxx~xxxxxxxx',
},
};
```

For the changes to take effect, rebuild your project:

```bash
Expand Down Expand Up @@ -188,7 +240,9 @@ If you are using mediation, you may wish to wait until the promise is settled be
### Enable SKAdNetwork to track conversions (iOS)

The Google Mobile Ads SDK supports conversion tracking using Apple's SKAdNetwork, which lets Google and participating third-party buyers attribute an app install even when the IDFA is not available.
Within your projects `app.json` file, add the advised [SKAdNetwork identifiers](https://developers.google.com/ad-manager/mobile-ads-sdk/ios/3p-skadnetworks):
Within your projects `app.json`/`app.config.js` file, add the advised [SKAdNetwork identifiers](https://developers.google.com/ad-manager/mobile-ads-sdk/ios/3p-skadnetworks):

#### app.json

```json
{
Expand Down Expand Up @@ -250,10 +304,74 @@ Within your projects `app.json` file, add the advised [SKAdNetwork identifiers](
}
```

#### app.config.js

```js
module.exports = {
'react-native-google-mobile-ads': {
android_app_id: 'ca-app-pub-xxxxxxxx~xxxxxxxx',
ios_app_id: 'ca-app-pub-xxxxxxxx~xxxxxxxx',
sk_ad_network_items: [
'cstr6suwn9.skadnetwork',
'4fzdc2evr5.skadnetwork',
'4pfyvq9l8r.skadnetwork',
'2fnua5tdw4.skadnetwork',
'ydx93a7ass.skadnetwork',
'5a6flpkh64.skadnetwork',
'p78axxw29g.skadnetwork',
'v72qych5uu.skadnetwork',
'ludvb6z3bs.skadnetwork',
'cp8zw746q7.skadnetwork',
'3sh42y64q3.skadnetwork',
'c6k4g5qg8m.skadnetwork',
's39g8k73mm.skadnetwork',
'3qy4746246.skadnetwork',
'f38h382jlk.skadnetwork',
'hs6bdukanm.skadnetwork',
'v4nxqhlyqp.skadnetwork',
'wzmmz9fp6w.skadnetwork',
'yclnxrl5pm.skadnetwork',
't38b2kh725.skadnetwork',
'7ug5zh24hu.skadnetwork',
'gta9lk7p23.skadnetwork',
'vutu7akeur.skadnetwork',
'y5ghdn5j9k.skadnetwork',
'n6fk4nfna4.skadnetwork',
'v9wttpbfk9.skadnetwork',
'n38lu8286q.skadnetwork',
'47vhws6wlr.skadnetwork',
'kbd757ywx3.skadnetwork',
'9t245vhmpl.skadnetwork',
'eh6m2bh4zr.skadnetwork',
'a2p9lx4jpn.skadnetwork',
'22mmun2rn5.skadnetwork',
'4468km3ulz.skadnetwork',
'2u9pt9hc89.skadnetwork',
'8s468mfl3y.skadnetwork',
'klf5c3l5u5.skadnetwork',
'ppxm28t8ap.skadnetwork',
'ecpz2srf59.skadnetwork',
'uw77j35x4d.skadnetwork',
'pwa73g5rt2.skadnetwork',
'mlmmfzh3r3.skadnetwork',
'578prtvx9j.skadnetwork',
'4dzt52r2t5.skadnetwork',
'e5fvkxwrpn.skadnetwork',
'8c4e2ghe7u.skadnetwork',
'zq492l623r.skadnetwork',
'3rd42ekr43.skadnetwork',
'3qcr597p9d.skadnetwork',
mikehardy marked this conversation as resolved.
Show resolved Hide resolved
],
},
};
```

### App Tracking Transparency (iOS)

Apple requires apps to display the App Tracking Transparency authorization request for accessing the IDFA (leaving the choice to the user, whether to use personalized or non-personalized ads).
Within your projects `app.json` file, you have to use the `user_tracking_usage_description` to describe your usage:
Within your projects `app.json`/`app.config.js` file, you have to use the `user_tracking_usage_description` to describe your usage:
mikehardy marked this conversation as resolved.
Show resolved Hide resolved

#### app.json

```json
{
Expand All @@ -265,6 +383,19 @@ Within your projects `app.json` file, you have to use the `user_tracking_usage_d
}
```

#### app.config.js

```js
module.exports = {
'react-native-google-mobile-ads': {
android_app_id: 'ca-app-pub-xxxxxxxx~xxxxxxxx',
ios_app_id: 'ca-app-pub-xxxxxxxx~xxxxxxxx',
user_tracking_usage_description:
'This identifier will be used to deliver personalized ads to you.',
},
};
```

To request the App Tracking Transparency authorization we recommend using the [react-native-permissions](https://github.com/zoontek/react-native-permissions) library or making it part of the UMP consent flow [European User Consent page](/european-user-consent).

```js
Expand Down
40 changes: 34 additions & 6 deletions ios_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,27 @@
#
set -e

if [[ -f "$PODS_ROOT/../.xcode.env" ]]; then
source "$PODS_ROOT/../.xcode.env"
fi
if [[ -f "$PODS_ROOT/../.xcode.env.local" ]]; then
source "$PODS_ROOT/../.xcode.env.local"
fi

_MAX_LOOKUPS=2;
_SEARCH_RESULT=''
_RN_ROOT_EXISTS=''
_CURRENT_LOOKUPS=1
_PROJECT_ABBREVIATION="RNGoogleMobileAds"
_JSON_ROOT="'react-native-google-mobile-ads'"
_JSON_FILE_NAME='app.json'
_JS_APP_CONFIG_FILE_NAME='app.config.js'
_JSON_OUTPUT_BASE64='e30=' # { }
_CURRENT_SEARCH_DIR=${PROJECT_DIR}
_PLIST_BUDDY=/usr/libexec/PlistBuddy
_TARGET_PLIST="${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}"
_DSYM_PLIST="${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist"
_IS_CONFIG_JS=false

# plist arrays
_PLIST_ENTRY_KEYS=()
Expand Down Expand Up @@ -65,18 +74,36 @@ fi;

while true; do
_CURRENT_SEARCH_DIR=$(dirname "$_CURRENT_SEARCH_DIR")
if [[ "$_CURRENT_SEARCH_DIR" == "/" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;
echo "info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file."
_SEARCH_RESULT=$(find "$_CURRENT_SEARCH_DIR" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)
if [[ ${_SEARCH_RESULT} ]]; then
echo "info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT"

if [[ "$_CURRENT_SEARCH_DIR" == "/" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then
break;
fi;

echo "info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME}/${_JS_APP_CONFIG_FILE_NAME} file."

_SEARCH_RESULT=$(find "$_CURRENT_SEARCH_DIR" -maxdepth 2 \( -name ${_JSON_FILE_NAME} -o -name ${_JS_APP_CONFIG_FILE_NAME} \) -print | /usr/bin/head -n 1)

if [[ "$(basename ${_SEARCH_RESULT})" = "${_JS_APP_CONFIG_FILE_NAME}" ]]; then
_IS_CONFIG_JS=true
echo "info: ${_JS_APP_CONFIG_FILE_NAME} found at $_SEARCH_RESULT"
break;
fi;

if [[ "$(basename ${_SEARCH_RESULT})" = "${_JSON_FILE_NAME}" ]]; then
echo "info: ${_JSON_FILE_NAME} found at ${_SEARCH_RESULT}"
break;
fi;

_CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))
done

if [[ ${_SEARCH_RESULT} ]]; then
_JSON_OUTPUT_RAW=$(cat "${_SEARCH_RESULT}")
if [[ ${_IS_CONFIG_JS} == "true" ]]; then
_JSON_OUTPUT_RAW=$("${NODE_BINARY}" -e "console.log(JSON.stringify(require('${_SEARCH_RESULT}')));")
else
_JSON_OUTPUT_RAW=$(cat "${_SEARCH_RESULT}")
fi;

_RN_ROOT_EXISTS=$(ruby -KU -e "require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]" || echo '')

if [[ ${_RN_ROOT_EXISTS} ]]; then
Expand Down Expand Up @@ -176,3 +203,4 @@ for plist in "${_TARGET_PLIST}" "${_DSYM_PLIST}" ; do
done

echo "info: <- ${_PROJECT_ABBREVIATION} build script finished"

Loading