invertase / react-native-google-mobile-ads

React Native Google Mobile Ads enables you to monetize your app with AdMob.
https://docs.page/invertase/react-native-google-mobile-ads
Other
691 stars 140 forks source link

migrate expo-ads-admob to react-native-google-mobile-ads [solutions: cannot use Expo Go client, must have valid app.json] #150

Closed Arekjaar4 closed 2 years ago

Arekjaar4 commented 2 years ago

I am migrating from expo-ads-admob to react-native-google-mobile-ads but when testing the app I get the following error: Invariant Violation: requireNativeComponent: "RNGoogleMobileAdsBannerView" was not found in the UIManager.

Do you know why this error occurs and how to fix it?

mikehardy commented 2 years ago

Hi there! With apologies, I've got no idea because there are no details or steps to reproduce https://stackoverflow.com/help/mcve

In this case steps to reproduce would start from an expo init I suppose, and then list the further commands you run, contents of any config files you change, finishing with the build failure

Arekjaar4 commented 2 years ago

Hi,

Thanks for the answer.

This is a code that reproduces the error: https://github.com/Arekjaar4/testads

mikehardy commented 2 years ago

please describe how it was built and the commands to run in order to demonstrate the error

start from an expo init I suppose, and then list the further commands you run, contents of any config files you change, finishing with the build failure

Arekjaar4 commented 2 years ago

expo start I use the Expo app to test

mikehardy commented 2 years ago

Perhaps you add the module somehow? You don't have to specify iOS or anything ? I don't use Expo Please write the steps exactly as I requested, as if for a grade school student

harreson-lima commented 2 years ago

I have the same issue, I create a project with expo init, and don't matter if I chose a minimal blank project or a bare one. Next, I install yarn add react-native-google-mobile-ads like the documentation, and add the "react-native-google-mobile-ads": { "android_app_id": "ca-app-pub-xxxxxxxx~xxxxxxxx", "ios_app_id": "ca-app-pub-xxxxxxxx~xxxxxxxx" } in the root of app.json. Finally, when import the import {BannerAd, TestIds } from 'react-native-google-mobile-ads <BannerAd unitId={TestIds.BANNER} />' into my app.js, I receive this error message

Invariant Violation: requireNativeComponent: "RNGoogleMobileAdsBannerView" was not found in the UIManager.

Note: I haven't tried in ios, just my android, and I use my Admob ID.

VictorioMolina commented 2 years ago

@Harreson-Lima I am about to do the same thing, from a managed workflow. Did you build your project using the EAS feature? Bare or managed?

VictorioMolina commented 2 years ago

@mikehardy I think that, in order to migrate to this package, from expo, we will have to make a build using EAS.

When we simply run expo init the CLI ask us if we want to create our project using the managed workflow (only JS, no native code) or the bare one (JS + native code). Here you can read about workflows.

So, I suppose that, as I can read in the Expo documentation for expo-ads-admob

This module will be removed in SDK 46. There will be no replacement that works with the classic build service (expo build) because the classic build service has been superseded by EAS Build. With EAS Build and Development Builds, you should use react-native-google-mobile-ads instead.

we will need to build our app, or at least, move to the bare workflow in order to play with this module (not really sure, but I suppose this library is not compatible with the managed workflow).

So, @Arekjaar4 @Harreson-Lima running expo prebuild could be an option.

mikehardy commented 2 years ago

indeed, it will be EAS only as far as I know. There is no way for the Expo GO / managed client to have our native code, and our native code is essential to the functioning of the app. Going bare could also work, if you install the module after ejecting.

mikehardy commented 2 years ago

If any one could propose a doc update to state very very explicitly in Expo docs that you should use this module with EAS build or bare workflow that would probably help reduce confusion as apparently we're going to have a lot of new Expo users soon...

harreson-lima commented 2 years ago

@VictorioMolina I haven't used the EAS feature, but i tried the bare workflow and don't work

duggster commented 2 years ago

I am also migrating from expo-ads-admob, and I have a working expo project with all my ads code commented out and built with EAS build. I then run npm install react-native-google-mobile-ads, add the ad IDs to my app.json, and run the EAS build again. Trying to launch the resulting app on iOS crashes immediately, and I don't get any error messages or anything. This is without even importing the library in my code yet. So really the only difference is the presence of the library. I'll try to work on getting a minimum reproducible example to share, but wondering if anyone else has gotten it to work yet or if its just me? I posted on Expo forums too and no response yet.

harreson-lima commented 2 years ago

@mikehardy I'll migrate my project to the EAS feature now. I'm a newbie in react native, to be honest, thank you.

VictorioMolina commented 2 years ago

I'll be building my project using EAS in a few weeks (1/2), so I can't be very explicit about the process at this point (I haven't tested it). But if by then you haven't found another solution, in case I'm lucky, I'll try to explain you the migration steps in detail.

harreson-lima commented 2 years ago

I'll try to use this documentation to migrate https://docs.expo.dev/build-reference/migrating/ if I've news I come back to inform you all.

mikehardy commented 2 years ago

I am also migrating from expo-ads-admob, and I have a working expo project with all my ads code commented out and built with EAS build. I then run npm install react-native-google-mobile-ads, add the ad IDs to my app.json, and run the EAS build again. Trying to launch the resulting app on iOS crashes immediately, and I don't get any error messages or anything. This is without even importing the library in my code yet. So really the only difference is the presence of the library. I'll try to work on getting a minimum reproducible example to share, but wondering if anyone else has gotten it to work yet or if its just me? I posted on Expo forums too and no response yet.

If ti crashes on startup, that is almost certainly a missing admob id (the app output if you run it from xcode or if you watch it in Console.app will show the exception so you can be sure) - the chain of events that needs to happen for the admob id to be present in a way the native SDK sees it on startup is:

Something in that chain of events is not happening and if you search for Info.plist file in the build output directory and examine it you will probably not see the admob id, but if you run our example app you should see it. Something sort of expo specific will be in the mix here

duggster commented 2 years ago

Thanks for the detailed info, definitely helps point me in a direction to look into more! I created a new expo app from scratch and added the library, and it doesn't crash. I looked at the built info.plist files for both and sure enough the new app has a GADApplicationIdentifier key with the admob id whereas my original app doesn't. That said, I'm not sure where it's going wrong. Here is an excerpt from my original app.json that crashes:

{
  "expo": {
  ...
  },
  "react-native-google-mobile-ads": {
    "android_app_id": "ca-app-pub-[X]",
    "ios_app_id": "ca-app-pub-[Y]"
  }
}

I examined the build logs and while I'm not really sure what I'm looking for I do see this which might indicate it's doing something with RNGoogleMobileAds and info.plist?

› Preparing Scoot » Info.plist
› Executing Scoot » Bundle React Native code and images
    the transform cache was reset.
› Generating debug Scoot » Scoot.app.dSYM
› Executing Scoot » [CP] Copy Pods Resources
› Executing Scoot » Upload Debug Symbols to Sentry
› Executing Scoot » [CP-User] [RNGoogleMobileAds] Configuration
› Signing   Scoot » Scoot.app
› Creating  Scoot » Scoot.app
› Archive Succeeded

Does anything stand out as being amiss, or is there any other information I could provide that might help?

mikehardy commented 2 years ago

› Executing Scoot » [CP-User] [RNGoogleMobileAds] Configuration

That's our run script added by our react-native-config.js as a pod step, and it should be calling ios-config.sh (I think, going from memory sorry - but the code's all in the repo ;-) )

That does look right, in your app.json. Maybe the first project is missing the config plugin entry? I'm really not sure - I'm sorry - but I'm gratified to know it's at least possible, creating a fresh app via expo and seeing it work is encouraging as it shows it should be working :thinking:

I can't recommend this as a general solution, but you can of course just add the ID in your Info.plist. All of this config machinery is suppose to keep things "react-native style" by letting you just drop it in an app.json and poof all the native stuff hooks up, but really it's just to copy the plist info in. You can just set it and be unblocked for now while further investigation happens at a lower priority for you

Arekjaar4 commented 2 years ago

This is the error displayed when trying to create the apk with the following command: eas build -p android --profile preview

[stderr] /home/expo/workingdir/build/android/app/src/main/AndroidManifest.xml:22:85-105 Error: [stderr] Attribute meta-data#com.google.android.gms.ads.DELAY_APP_MEASUREMENT_INIT@value value=(true) from AndroidManifest.xml:22:85-105 [stderr] is also present at [:react-native-google-mobile-ads] AndroidManifest.xml:19:13-34 value=(false). [stderr] Suggestion: add 'tools:replace="android:value"' to element at AndroidManifest.xml:22:5-107 to override. [stderr] FAILURE: Build failed with an exception. [stderr] What went wrong: [stderr] Execution failed for task ':app:processReleaseMainManifest'. [stderr] > Manifest merger failed : Attribute meta-data#com.google.android.gms.ads.DELAY_APP_MEASUREMENT_INIT@value value=(true) from AndroidManifest.xml:22:85-105 [stderr] is also present at [:react-native-google-mobile-ads] AndroidManifest.xml:19:13-34 value=(false). [stderr] Suggestion: add 'tools:replace="android:value"' to element at AndroidManifest.xml:22:5-107 to override. [stderr] Try: [stderr] > Run with --stacktrace option to get the stack trace. [stderr] > Run with --info or --debug option to get more log output. [stderr] > Run with --scan to get full insights. [stderr] * Get more help at https://help.gradle.org [stderr] BUILD FAILED in 4m 46s

duggster commented 2 years ago

I just added in the android_app_id key to the app.json of my fresh new expo app and built it, and it is working also. So I tried it with my original app on android and that is also working. So now it is just my original iOS app that is not working for me due to the missing keys in info.plist. But now that it's been narrowed down to that part of the build, I will move over to expo forums to see if they can help figure out that part. @mikehardy thanks for your assistance and responsiveness! (also I can't really manually update info.plist without ejecting to expo's bare workflow, so will keep trucking on to solve this for now).

Arekjaar4 commented 2 years ago

I just added in the android_app_id key to the app.json of my fresh new expo app and built it, and it is working also. So I tried it with my original app on android and that is also working. So now it is just my original iOS app that is not working for me due to the missing keys in info.plist. But now that it's been narrowed down to that part of the build, I will move over to expo forums to see if they can help figure out that part. @mikehardy thanks for your assistance and responsiveness! (also I can't really manually update info.plist without ejecting to expo's bare workflow, so will keep trucking on to solve this for now).

You can show your app.json to see how you have added the android_app_id?

duggster commented 2 years ago
{
  "expo": {
    "name": "my-app",
    "slug": "my-app",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "light",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.duggster.myapp"
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#FFFFFF"
      },
      "package": "com.duggster.myapp"
    },
    "web": {
      "favicon": "./assets/favicon.png"
    }
  },
  "react-native-google-mobile-ads": {
    "android_app_id": "ca-app-pub-1234567890123456~1234567890",
    "ios_app_id": "ca-app-pub-1234567890123456~1234567890"
  }
}
mikehardy commented 2 years ago

also I can't really manually update info.plist without ejecting to expo's bare workflow, so will keep trucking on to solve this for now)

I think (sorry if wrong) that Expo EAS in their config already has a way to add plist entries as part of prebuild? You might be able to use that? I'm just trying to think of workarounds for you so you are not blocked, but I have to say my summary of the situation at the moment is "the module appears to be working on fresh projects, and most existing projects" so I am not currently thinking of this as actionable here, but I'm listening in case any new experiment results invalidate that current stance

Good luck - I hope it resolves

duggster commented 2 years ago

@mikehardy indeed expo does have a way to inject plist entries in app.json, I forgot about that and actually expo uses that as a way to specify the different permissions messages. Here is mine:

"infoPlist": {
        "NSLocationWhenInUseUsageDescription": "Allow app to use location for the purpose of loading weather conditions at the user's location.",
        "NSPhotoLibraryUsageDescription": "Allow app to use photos to be able to set a background image and images for each family member.",
        "NSCalendarsUsageDescription": "Allow Scoot to show your iOS calendar events in this view. You can disable this later in your Scoot Settings if desired.",
        "NSRemindersUsageDescription": "Allow app to integrate your iOS reminder events in with Scoot calendar events.",
        "NSUserTrackingUsageDescription": "This will be used to deliver personalized ads to you.",
        "NSContactsUsageDescription": "Allow app to access your contacts so you can send event reminders to them."
      }

Turns out one of the strings I have there appears to be causing the issue because of an apostrophe. I received some pointers from expo forums that led me to this error in the xcode build logs:

info: -> RNGoogleMobileAds build script started
info: 1) Locating app.json file:
info:      (1 of 2) Searching in '/Users/expo/workingdir/build' for a app.json file.
info:      app.json found at /Users/expo/workingdir/build/app.json
-e:47: syntax error, unexpected local variable or method, expecting ')'
...eather conditions at the user's location.",
...                              ^
-e:47: syntax error, unexpected string literal
...itions at the user's location.",
...                              ^
-e:48: syntax error, unexpected constant, expecting end-of-input
..."NSPhotoLibraryUsageDescription": "Allow app to use photos t...
... ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
info: 2) Injecting Info.plist entries: 
    ->  0) google_mobile_ads_json_raw string e30=
info:      setting plist entry 'google_mobile_ads_json_raw' of type 'string' in file '/Users/expo/Library/Developer/Xcode/DerivedData/Scoot-dxafajfnmudobohdoabbyjtwdhpq/Build/Intermediates.noindex/ArchiveIntermediates/Scoot/BuildProductsPath/Debug-iphoneos/Scoot.app/Info.plist'
info:      setting plist entry 'google_mobile_ads_json_raw' of type 'string' in file '/Users/expo/Library/Developer/Xcode/DerivedData/Scoot-dxafajfnmudobohdoabbyjtwdhpq/Build/Intermediates.noindex/ArchiveIntermediates/Scoot/BuildProductsPath/Debug-iphoneos/Scoot.app.dSYM/Contents/Info.plist'
info: <- RNGoogleMobileAds build script finished

Is this something you can adjust in the script so apostrophes don't throw an error? I'm not sure if I should have escaped that apostrophe or not, but if it happened to me I'm sure it could happen to others too.

mikehardy commented 2 years ago

Hmm - well that's definitely invalid JSON so it looks like what you have actually done is find two bugs at once:

1) your project seems like it had a bug before this, with invalid app.json, that was sliding through somehow? 2) our validation and error handling in the ios config script needs to fail fast, it's the silent replacement with '{}' (that's what e30= is, after base64) is a disaster here as a dev experience

mikehardy commented 2 years ago

I won't have time to make the defensive programming patch here but the enhancement is, I think:

1) don't just parse and silently fail, in this module the file must exist because the app id is required, so if the parsing is about to return e30= for _JSON_OUTPUT_RAW print out a huge ASCII-art style error message and exit 1

https://github.com/invertase/react-native-google-mobile-ads/blob/324682078f9b266197fb7ae26c3fa44697e6a962/ios_config.sh#L82-L85

2) the file is not optional in this module (it was in react-native-firebase), so if the file is not found, also print out a huge error message and exit 1 (probably abstract to a "print huge error message and exit 1" method and just call it with the message

https://github.com/invertase/react-native-google-mobile-ads/blob/324682078f9b266197fb7ae26c3fa44697e6a962/ios_config.sh#L114-L119

3) app id is not optional, so the if here needs an else and a huge error message + exit 1 if the app id is not found in app.json:

https://github.com/invertase/react-native-google-mobile-ads/blob/324682078f9b266197fb7ae26c3fa44697e6a962/ios_config.sh#L99-L105

duggster commented 2 years ago

Quick note about the solution in the new title, it's OK to use Expo managed workflow (I am), but you just cannot use the Expo Go app to run it and you need to build a dev-client using the EAS build process. Could be something more like "must use eas build dev-client or bare workflow"

Arekjaar4 commented 2 years ago

Hello,

I was able to fix the error with the command "expo prebuild" In my case I have also had to modify this line in the AndroidManifest Attribute meta-data#com.google.android.gms.ads.DELAY_APP_MEASUREMENT_INIT@value value=(true) to Attribute meta-data#com.google.android.gms.ads.DELAY_APP_MEASUREMENT_INIT@value value=(false) To make it work with IOS you need to uninstall the "expo-ads-admob" library

Thank you all for your contributions.

Greetings.

julste commented 2 years ago

I'm using the app.config.js file to load my values from env, but in the console I got the error:

Error: Problem validating fields in app.json. Learn more: https://docs.expo.dev/workflow/configuration/ • should NOT have additional property 'react-native-android-mobile-ads'.

Do you have an idea how to solve it?

mikehardy commented 2 years ago

I'm not even aware of what app.config.js is. You may want to approach expo forums for support on this one It's my understanding that app.json is "configuration by convention" and expo should tolerate literally any valid JSON in there, as they use the file but do not own the file.

Arekjaar4 commented 2 years ago

Hello,

I managed to solve my problem and I was able to do the migration. I explain everything I did in this Medium article:

https://medium.com/react-native-expo/migrate-expo-ads-admob-to-react-native-google-mobile-ads-3747b3a3b75e

julste commented 2 years ago

@Arekjaar4 Do you also a solution for app.config.js configuration?

Arekjaar4 commented 2 years ago

@Arekjaar4 Do you also a solution for app.config.js configuration?

no, sorry

allandiego commented 2 years ago

@julste my dynamic config file is working fine, just set ads keys outside expo prop

app.config.ts

import { ExpoConfig, ConfigContext } from '@expo/config';

// https://docs.expo.io/versions/latest/config/app/
const VERSION_CODE = 5;
const RELEASE_DATE = new Date().toISOString().split('T')[0];
const APP_PACKAGE_NAME = 'com.mypackage.app';

type AppConfig = {
  expo: ExpoConfig;
  [key: string]: any;
};

const appConfig: AppConfig = {
  'react-native-google-mobile-ads': {
    android_app_id: 'ca-app-pub-xxxx',
    ios_app_id: 'ca-app-pub-xxxx',
    delay_app_measurement_init: false, 
    user_tracking_usage_description: 'This identifier will be used to deliver personalized ads to you.' 
  },
  'expo': {
    name: 'My App',
    slug: 'my-app',
    scheme: APP_PACKAGE_NAME,
    description: 'test',
    privacy: 'public',
    version: `${VERSION_CODE}_${RELEASE_DATE}`,
    jsEngine: 'hermes',
    orientation: 'default',
    icon: './assets/app-icon.png',
    splash: {
      image: './assets/app-splash-screen.png',
      resizeMode: 'contain',
      backgroundColor: '#7159c1',
    },
   ...
  },
};

export default appConfig;
DavidBriglio commented 2 years ago

@allandiego Using your suggestion doesn't seem to work when building on EAS. I get the error that android_app_id cannot be found. Have you tried this with EAS? I have been trying to find a way to get the dynamic app config working with this package but haven't had any luck.

duggster commented 2 years ago

Hi @DavidBriglio , here's how I did it to get it work with EAS for the purpose of building an app variant:

app.json

{
  "react-native-google-mobile-ads": {
    "android_app_id": "ca-app-pub-XXXX",
    "ios_app_id": "ca-app-pub-YYYY"
  }
}

app.config.js

const APP_VARIANT = process.env.APP_VARIANT;
const IS_DEV = APP_VARIANT === 'dev';

export default {
  expo: {
    ....everything else
  }
}
DoctorJohn commented 2 years ago

The expo docs describe how to use app.json and app.config.js at the same time. Make sure the react-native-google-mobile-ads block is in app.json since app.config.js is an expo feature and not a react-native-google-mobile-ads feature.

It basically works like this:

app.json

{
  "expo": {
    "name": "My App"
  },
  "react-native-google-mobile-ads": {
    "android_app_id": "ca-app-pub-XXXX",
    "ios_app_id": "ca-app-pub-YYYY"
  }
}

app.config.js

module.exports = ({ config }) => {
  console.log(config.name); // prints 'My App'
  return {
    // Here is the place to merge your dynamic config with your static app.json config
    ...config,
  };
};
DavidBriglio commented 2 years ago

Thank you @duggster and @DoctorJohn, these work well. Where I got tripped up was adding my app.json file to my .gitignore so that I could avoid adding my admob ids into version control. Since that file was in the ignore, it was not sent to EAS in the build, and that was causing the undefined android_app_id error.

My solution is adding app.json to the .git/info/exclude file instead. It will prevent changes from being committed to version control, but still be included in the EAS build since it is not in the .gitignore file.