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
686 stars 139 forks source link

🔥[🐛] Can't close interstitial ads on iOS - unclickable dead zone on close buttons #204

Closed bitfabrikken closed 2 years ago

bitfabrikken commented 2 years ago

Issue

On certain iOS devices, when certain interstitial ads are shown, they cannot be closed. The type of ad is shown here, with an unclickable area highlighted in red:

image

Ads with regular close buttons can be closed without problems.

This type starts out with a button that says "Skip video", then when clicked (or after a while), changes to an "X". The unclickable invisible area persists through these button changes.

So to close the ad, the user has to know of this issue, and almost pixel-perfectly be able to click on the tiny area responding to clicks.

Effectively this renders the app itself unusable, as the user is unable to close the ad. Have had many complaints and bad ratings due to this so it's definitely happening to a lot of people.

I've tested on 32 iOS devices, mostly iPhones, via browserstack:

Other stuff that might be relevant

Reproduce

Project Files

Javascript

Click To Expand

#### `package.json`: ```json { "name": "appname", "version": "1.2.3", "versionCode": 1230, "versionName": "1.2.3", "private": true, "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", "start": "react-native start", "test": "jest", "lint": "eslint ." }, "dependencies": { "@npmcli/config": "^4.1.0", "@react-native-async-storage/async-storage": "^1.15.17", "@react-native-community/netinfo": "^7.1.9", "@react-native-firebase/analytics": "^14.11.0", "@react-native-firebase/app": "^14.11.0", "@react-native-firebase/crashlytics": "^14.11.0", "expo": "^45.0.0", "expo-tracking-transparency": "~2.2.0", "fbjs": "^3.0.2", "jsc-android": "^241213.1.0", "react": "17.0.2", "react-native": "0.67.2", "react-native-device-info": "^8.4.8", "react-native-fs": "^2.18.0", "react-native-google-mobile-ads": "^7.0.1", "react-native-iap": "8.0.10", "react-native-in-app-review": "^2.1.2", "react-native-localize": "^1.4.2", "react-native-orientation-locker": "^1.5.0", "react-native-rate": "^1.2.9", "react-native-scrolling-images": "^1.0.6", "react-native-share": "^4.0.4", "react-native-view-shot": "3.1.2", "react-native-webview": "^11.23.0" }, "devDependencies": { "@babel/core": "^7.12.9", "@babel/runtime": "^7.12.5", "@react-native-community/eslint-config": "^2.0.0", "babel-jest": "^26.6.3", "eslint": "7.14.0", "jest": "^26.6.3", "logkitty": "^0.7.1", "metro-react-native-babel-preset": "^0.66.2", "react-test-renderer": "17.0.2" }, "expo": { "autolinking": { "exclude": [ "expo-application", "expo-asset", "expo-constants", "expo-file-system", "expo-font", "expo-keep-awake" ] } }, "jest": { "preset": "react-native" } } ``` #### `admob.json`: ```json # N/A ```

iOS

Click To Expand

#### `ios/Podfile`: ```ruby require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") require_relative '../node_modules/react-native/scripts/react_native_pods' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' platform :ios, '12.0' inhibit_all_warnings! #added to remove unneccesory pods warning target 'appname' do use_expo_modules! post_integrate do |installer| begin expo_patch_react_imports!(installer) rescue => e Pod::UI.warn e end end config = use_native_modules! use_react_native!( :path => config[:reactNativePath], # to enable hermes on iOS, change `false` to `true` and then install pods :hermes_enabled => false ) post_install do |installer| react_native_post_install(installer) installer.pods_project.build_configurations.each do |config| config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64" end end end ``` --- ### Android

Click To Expand

#### `android/build.gradle`: ```groovy // N/A ``` #### `android/app/build.gradle`: ```groovy // N/A ``` #### `android/settings.gradle`: ```groovy // N/A ``` #### `AndroidManifest.xml`: ```xml ```

--- ## Environment
Click To Expand

**`react-native info` output:** ``` System: OS: macOS 12.2.1 CPU: (12) x64 Intel Core 2 Duo P9xxx (Penryn Class Core 2) Memory: 13.37 GB / 32.00 GB Shell: 5.8 - /bin/zsh Binaries: Node: 16.15.1 - ~/.nvm/versions/node/v16.15.1/bin/node Yarn: 1.22.19 - /usr/local/bin/yarn npm: 8.11.0 - ~/.nvm/versions/node/v16.15.1/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman Managers: CocoaPods: 1.11.2 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: DriverKit 21.4, iOS 15.5, macOS 12.3, tvOS 15.4, watchOS 8.5 Android SDK: Not Found IDEs: Android Studio: Not Found Xcode: 13.4.1/13F100 - /usr/bin/xcodebuild Languages: Java: Not Found npmPackages: @react-native-community/cli: Not Found react: 17.0.2 => 17.0.2 react-native: 0.67.2 => 0.67.2 react-native-macos: Not Found npmGlobalPackages: *react-native*: Not Found ``` - **Platform that you're experiencing the issue on**: - [X] iOS - [ ] Android - [ ] **iOS** but have not tested behavior on Android - [ ] **Android** but have not tested behavior on iOS - [ ] Both - **Are you using `TypeScript`?** - `N`

--- - 👉 Check out [`Invertase`](https://twitter.com/invertaseio) on Twitter for updates on the library.
DoctorJohn commented 2 years ago

Just spend some time looking into this. The react-native-google-mobile-ads source code looks fine compared to the official examples. I found out though, that other admob wrappers have reports of similar issues (flutterfire, flutterfire again, googleads-mobile-flutter, googleads-mobile-flutter again, native ios sdk). This could be an issue with the native sdk. The listed issues in other repositories also mention workarounds. But most of them are related to the statusbar. So there is a chance this is an unrelated issue, since you said you checked the status bar.

EDIT: there are reports of this in the google support forum (1, 2, 3).

bitfabrikken commented 2 years ago

@DoctorJohn thanks for spending time on this :) I've got the same suspicion, that it can be something in the SDK. In my case it's definitely not the StatusBar, as this seems to be about 3* it's height. I've emailed their support and will report back here what they say.

mikehardy commented 2 years ago

Just curious if there was official resolution on this from the SDK? I'm traveling so have not been interactive but I was tracking this for when I got back...

bitfabrikken commented 2 years ago

Just curious if there was official resolution on this from the SDK? I'm traveling so have not been interactive but I was tracking this for when I got back...

Sadly haven't heard anything no. Will update if I get any info but by now I think it's a long shot.

mikehardy commented 1 year ago

There may be a solution implementable here, will require (I think, could be wrong...) some ability to hack in the native files here for iOS with regard to the view controller (?) and a motivated person https://groups.google.com/g/google-admob-ads-sdk/c/JqS14ZpvTGw/m/O_CWcAWRAQAJ

zzzej commented 1 year ago

Any particular reason this was closed?

zzzej commented 1 year ago

For those using expo this was my solution:

import * as StatusBar from 'expo-status-bar';
import { useEffect, useState } from 'react';
import { InterstitialAd, AdEventType } from 'react-native-google-mobile-ads';

import { intersitialAdUnitId } from '../utils/config';

const ad = InterstitialAd.createForAdRequest(intersitialAdUnitId, {
  requestNonPersonalizedAdsOnly: false,
  keywords: ['keyword1example'],
});

export default function useIntersitialAdUnit() {
  const [adClosed, setAdClosed] = useState(false);
  const [adLoaded, setAdLoaded] = useState(false);
  const [adShowing, setAdShowing] = useState(false);

  useEffect(() => {
    const shouldHideAd = true; //

    if (shouldHideAd) {
      setAdClosed(true);
      return;
    }

    const unsubscribeLoaded = ad.addAdEventListener(AdEventType.LOADED, () => {
      setAdLoaded(true);

      if (shouldHideAd) {
        setAdClosed(true);
        setAdLoaded(false);
        setAdShowing(false);
        return;
      }

      ad.show()
        .then(() => {
          setAdShowing(true);
          // Hide status bar so users on all iPhone 14 models can exit out of the ad
          StatusBar.setStatusBarHidden(true); // Hide status bar
        })
        .catch((e) => {
          console.error({ showAdError: e });
          setAdShowing(false);
          StatusBar.setStatusBarHidden(false); // Show status bar in case of error
        });
    });

    const unsubscribeClosed = ad.addAdEventListener(AdEventType.CLOSED, () => {
      setAdClosed(true);
      setAdLoaded(false);
      setAdShowing(false);
      StatusBar.setStatusBarHidden(false); // Show status bar after the ad is closed
    });

    ad.load();

    return () => {
      unsubscribeLoaded();
      unsubscribeClosed();
    };
  }, []);

  return { adClosed, adLoaded, adShowing };
}
Macarthurval commented 1 year ago

I've resolved this by hiding status bar while showing ads:

<StatusBar hidden={ this.props.settings.actionBarHidden } />
this.intersticialAd.addAdEventsListener(({type, payload}) => {
  if( type === AdEventType.CLOSED ){
    store.dispatch( setActionBarHidden( false ) )
  }else if( type === AdEventType.OPENED ){
    store.dispatch( setActionBarHidden( true ) )
  }
})