react-native-google-mobile-ads icon indicating copy to clipboard operation
react-native-google-mobile-ads copied to clipboard

Unhandled promise rejection: Error: InterstitialAd.show() The requested InterstitialAd has not loaded and could not be shown

Open DoneDeal0 opened this issue 2 years ago • 17 comments

Issue

I display a list of addresses. Each time the user clicks on an address, an ad must be shown. Once the ad is closed, the user is automatically redirected to a sub screen where the address is shown on a map.

I've used the useInterstitialAd and useRewardedAd hooks. It always works fine the first time the user clicks on an address.

But when he comes back from the Map screen to the AddressesList screen, the ad never shows up again when he clicks on a new address. It's like the ad is only loaded once, and can never be shown again afterwards.

The error is:

Unhandled promise rejection: Error: InterstitialAd.show() The requested InterstitialAd has not loaded and could not be shown

I would like to show an ad each time the user clicks on an address.

Here is the wrapper I've written around these hook:

import { useEffect, useState } from "react";
import { NavigationProp } from "@react-navigation/native";
import { TestIds, useInterstitialAd } from "react-native-google-mobile-ads";
import { Routes } from "routes";

const adsId = __DEV__
  ? TestIds.INTERSTITIAL
  : "ca-app-pub-blabla";

export default function useAds( navigation: NavigationProp<any> ) {
  const { isLoaded, isClosed, load, show } = useInterstitialAd(adsId, {
    requestNonPersonalizedAdsOnly: true,
  });
  const [navData, setNavData] = useState<string>("");

  useEffect(() => {
    load();
  }, [load]);

  useEffect(() => {
    if (isClosed) {
      navigation.navigate(Routes.Map, { address: navData.address });
    }
  }, [address, isClosed, navigation]);

  const onPress = async (address: string) => {
    if (isLoaded) {
      setNavData( address);
      return show();
    } else {
      return navigation.navigate(Routes.Map, { address });
    }
  };

  return {
    onPress,
  };
}

How to fix this?

DoneDeal0 avatar Jun 06 '22 21:06 DoneDeal0

Try wrapping onPress with useCallback.

wjaykim avatar Jun 07 '22 02:06 wjaykim

To me it looks like the ad is actualy just loaded once and thus can only be shown once. Maybe adding something like the following could work?

  React.useEffect(() => {
    if (isClosed) {
      load();
    }
  }, [isClosed, load]);

DoctorJohn avatar Jun 07 '22 13:06 DoctorJohn

Indeed, the ad is only loaded once. The loaded state is never reset after the promise resolves. This should be fixed in a pull request. Another option would be to expose a function that could reset the state programatically.

I eventually juste added the condition isLoaded && isOpened, so I make sure not to load the ad more than once.

DoneDeal0 avatar Jun 11 '22 16:06 DoneDeal0

@DoneDeal0 I am facing same issue with Rewarded Ads and RewardedInterstitial Ads.

VishnuBhanderi avatar Jun 14 '22 00:06 VishnuBhanderi

Indeed, the ad is only loaded once. The loaded state is never reset after the promise resolves.

This is intended behavior. You need to load ad again using load function after the ad is used(opened and closed). If the hook programmatically reloads the ad right after the ad is closed, the states(such as isClosed) will be reset right after the ad is closed, thus developers cannot handle ad's state after the ad is closed(isClosed never becomes to true).

wjaykim avatar Jun 14 '22 04:06 wjaykim

Also, the isOpened state is remained true even after the ad is closed unless new ad is requested. It is documented in documentation. So isOpened does not means if the ad is opened currently, but means if the ad had been opened.

wjaykim avatar Jun 14 '22 04:06 wjaykim

If you want to load ad again, you can do like this:

  useEffect(() => {
    if (isClosed) {
      navigation.navigate(Routes.Map, { address: navData.address });
      load();
    }
  }, [navData.address, isClosed, load, navigation]);

wjaykim avatar Jun 14 '22 04:06 wjaykim

Facing the same issue here...

It seems ad objects cannot be reused (i.e. re-loaded): https://github.com/invertase/react-native-google-mobile-ads/blob/5d2048b530b798740c9fed392772568f909dd6b2/src/ads/MobileAd.ts#L176

Even if the state within the hook resets, the ad object itself won't load more than once, so you need to create a new ad instance...

monti-python avatar Jun 22 '22 23:06 monti-python

Facing the same issue here...

It seems ad objects cannot be reused (i.e. re-loaded): https://github.com/invertase/react-native-google-mobile-ads/blob/5d2048b530b798740c9fed392772568f909dd6b2/src/ads/MobileAd.ts#L176

Even if the state within the hook resets, the ad object itself won't load more than once, so you need to create a new ad instance...

Please, try my solution I commented above. The hook won't reload ad for you. You have to load the ad again manually after the ad is used.

Also, the line you mentioned doesn't matter because of these lines: https://github.com/invertase/react-native-google-mobile-ads/blob/5d2048b530b798740c9fed392772568f909dd6b2/src/ads/MobileAd.ts#L98-99

wjaykim avatar Jun 23 '22 00:06 wjaykim

@wjaykim I can reload the ads manually after the previous ad is used, But I can't relead the new ad if the previous one is an error? Do you have any suggestion, thanks

ManhVuAmela avatar Jun 30 '22 03:06 ManhVuAmela

Hi @wjaykim, Thanks a lot for your reply. Indeed your solution works when triggering the load, show functions imperatively (e.g. via Button component). However, when passing them within a callback the behavior is not as expected and the ads won't show neither load. To illustrate it, below is an example of what I'm trying to achieve. The ads are shown and reload properly when using the buttons. However, they don't show up when the app comes back from the background, which would be the expected behavior as part of the handleAppStateChange.

import React, { useEffect, useRef } from 'react';
import {AppState, StyleSheet, StatusBar, View, Button} from "react-native";
import { MobileAds, useInterstitialAd, TestIds } from 'react-native-google-mobile-ads';

const adUnit = TestIds.INTERSTITIAL;
const requestOptions = {};


export default function App() {

    const { isLoaded, isClosed, load, show } = useInterstitialAd(adUnit, requestOptions)
    const appState = useRef(AppState.currentState);
    
    const handleAppStateChange = async (nextAppState) => {
        if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
            console.log('App has come to the foreground! Showing ad...');
            show();
            console.log('Ad should have been shown.');
        }
        else if (nextAppState === 'background') {
            console.log('App is going to the background')
        }
        else {
            console.log('inactive-state transition (iOS)')
        }
        appState.current = nextAppState;
    };


    initApp = async () => {
        StatusBar.setBarStyle('light-content');
        await MobileAds().initialize();
        load();

    }

    useEffect(() => {
      initApp();
      const sub = AppState.addEventListener('change', handleAppStateChange);
      return () => {sub.remove();}
    }, []);
  
    useEffect(() => {
      if (isClosed) {
        console.log("Reloading ad...");
        load();
      }
    }, [isClosed])

    return (
        <View 
            style={styles.app}
        >
            <View style={styles.container}>
                <Button 
                    title="Show Ad!"
                    color="#0ff233"
                    onPress={show}
                />
                <Button 
                    title="Load Ad!"
                    color="#0ff233"
                    onPress={load}
                />                        
            </View>

        </View>
    );
}

const styles = StyleSheet.create({
    app: {
        flex: 1,
        backgroundColor: '#646464',
    },
    container: {
        flex: 1,
        justifyContent: 'center',
        marginHorizontal: 16,
    },
});

monti-python avatar Jun 30 '22 22:06 monti-python

I could solve it by making the effect listen to changes in the show function. It seems the show, load functions are instantiated more than once... This way we rebind the event handler whenever the show,load functions change.

export default function App() {

    const { isLoaded, isClosed, load, show } = useInterstitialAd(adUnit, requestOptions)
    ...

    useEffect(() => {
      initApp();
    }, []);

    useEffect(() => {
      const sub = AppState.addEventListener('change', handleAppStateChange);
      return () => {sub.remove();}
    }, [show]);  

    ...

monti-python avatar Jun 30 '22 22:06 monti-python

@monti-python Always follow hook rules of react. You can lessen the human errors like those case by using eslint-plugin-react-hooks(The plugin is included in react native's default eslint rule).

Also, if you want to display ad when user get back to foreground, you can use app open ad type, FYI.

wjaykim avatar Jul 01 '22 00:07 wjaykim

There is no need at all to mess-up with the state to reload interstitials, you can do just

if (interstitial.loaded) await interstitial.show();
else {
  // It might be a good idea to show a fallback ad here,
  // e.g. in my case if no AdMob interstitial ad is loaded,
  // I fallback to show a dummy full screen ad, inviting users
  // to subscribe for ads-free use of my app.
}

// In my code there is always 1 sec interval between ".show()" and the next line,
// but probably no need for extra wait here, and then no need for "if" on the next line.
if (!interstitial.loaded) interstitial.load();

birdofpreyru avatar Jul 04 '22 13:07 birdofpreyru

There is no need at all to mess-up with the state to reload interstitials, you can do just

if (interstitial.loaded) await interstitial.show();
else {
  // It might be a good idea to show a fallback ad here,
  // e.g. in my case if no AdMob interstitial ad is loaded,
  // I fallback to show a dummy full screen ad, inviting users
  // to subscribe for ads-free use of my app.
}

// In my code there is always 1 sec interval between ".show()" and the next line,
// but probably no need for extra wait here, and then no need for "if" on the next line.
if (!interstitial.loaded) interstitial.load();

I didn't know that loaded is a method that we can use. I really wish this had been in the readme documentation. Would have saved us a lot of problems! Thanks so much for sharing!

deggertsen avatar Jul 11 '22 17:07 deggertsen

Hmm... no idea how do I know about loaded prop. Probably it was in older docs, got lost on the way, and should be re-added to the docs? @mikehardy

Though, as of the current implementation, that prop is available for all ads that inherit to MobileAd, thus these classes have it:

  • AppOpenAd
  • InterstitialAd
  • RewardedAd
  • RewardedInterstitialAd

birdofpreyru avatar Jul 12 '22 00:07 birdofpreyru

I'm traveling so this is really delayed, but I actually looked at it a few days ago when it first went through. I actually had trouble locating it in the source! So, it's hard to find. And near as I can tell it is not in our example either. @wjaykim is loaded property of MobileAd super class an intended way to program this stuff? :thinking: if so a PR to the docs + example showing it might be useful

mikehardy avatar Jul 15 '22 01:07 mikehardy

Had a chance to roll through some release mode issues and I can verify that in android on release mode at least (maybe other cases), the ad events are for some reason not coming through to the listener, which is related to this.

I've added some code to the example in a PR #244

mikehardy avatar Oct 12 '22 04:10 mikehardy

Hi, since i stumbled across the same error message and i had also troubles finding a solution, i share my working one - maybe somebody can use it:

const showAd = () => {
    const rewarded = RewardedAd.createForAdRequest(adUnitId, {
        requestNonPersonalizedAdsOnly: true,
        keywords: ['fashion', 'clothing'],
    });

    const unsubscribeLoaded = rewarded.addAdEventListener(RewardedAdEventType.LOADED, () => {
        rewarded.show();
    });

    const unsubscribeEarned = rewarded.addAdEventListener(
        RewardedAdEventType.EARNED_REWARD,
        reward => {
            console.log("Callback called!");
        },
    );

    rewarded.load();
}

jbravo94 avatar Oct 31 '22 00:10 jbravo94

Has anyone found a solution? For me, the ad never loads and therefore never displays.

victorroatt avatar Nov 22 '22 15:11 victorroatt

Has anyone found a solution? For me, the ad never loads and therefore never displays.

@victorroatt the related underlying code has changed since this issue was reported. I would recommend filling a new issue with all the relevant information (especially relevant code snippets and the library version you're using) or at least provide that information here.

DoctorJohn avatar Nov 22 '22 16:11 DoctorJohn

Hello 👋, to help manage issues we automatically close stale issues.

This issue has been automatically marked as stale because it has not had activity for quite some time.Has this issue been fixed, or does it still require attention?

This issue will be closed in 15 days if no further activity occurs.

Thank you for your contributions.

github-actions[bot] avatar Dec 20 '22 16:12 github-actions[bot]

Anyone can please share the code for showing multiple ads with the click of a button I am having the same issue first ad lodes perfectly but other ads not loading and while calling shows my app is crashing

dprajapati1179 avatar Apr 28 '23 03:04 dprajapati1179

+1

kusche12 avatar May 04 '23 23:05 kusche12

Hi, since i stumbled across the same error message and i had also troubles finding a solution, i share my working one - maybe somebody can use it:

const showAd = () => {
    const rewarded = RewardedAd.createForAdRequest(adUnitId, {
        requestNonPersonalizedAdsOnly: true,
        keywords: ['fashion', 'clothing'],
    });

    const unsubscribeLoaded = rewarded.addAdEventListener(RewardedAdEventType.LOADED, () => {
        rewarded.show();
    });

    const unsubscribeEarned = rewarded.addAdEventListener(
        RewardedAdEventType.EARNED_REWARD,
        reward => {
            console.log("Callback called!");
        },
    );

    rewarded.load();
}

Worked, Thanks a lot :)

usmanr626 avatar Jul 26 '23 17:07 usmanr626

Worked, Thanks a lot

A7medKhalifa avatar Jul 28 '23 22:07 A7medKhalifa

import React, { useEffect } from "react"; import { View, Text, SafeAreaView, StyleSheet, TouchableOpacity, } from "react-native";

import admob, { MaxAdContentRating, } from "react-native-google-mobile-ads";

import { InterstitialAd, RewardedAd, RewardedAdEventType, TestIds, AdEventType, BannerAd, BannerAdSize,

} from "react-native-google-mobile-ads";

admob() .setRequestConfiguration({ // Update all future requests suitable for parental guidance maxAdContentRating: MaxAdContentRating.PG,

// Indicates that you want your content treated as child-directed for purposes of COPPA.
tagForChildDirectedTreatment: true,

// Indicates that you want the ad request to be handled in a
// manner suitable for users under the age of consent.
tagForUnderAgeOfConsent: true,

}) .then(() => { // Request config successfully set! });

const interstialAdUnitId = DEV ? TestIds.INTERSTITIAL : "ca-app-pub-8363970344320746/2547838027";

const interstitial = InterstitialAd.createForAdRequest( interstialAdUnitId, { requestNonPersonalizedAdsOnly: true, keywords: ["fashion", "clothing"], } );

const rewardedAdUnitId = DEV ? TestIds.REWARDED : "ca-app-pub-8363970344320746/7062917668";

const rewarded = RewardedAd.createForAdRequest( rewardedAdUnitId, { requestNonPersonalizedAdsOnly: true, keywords: ["fashion", "clothing"], } );

const bannerAdUnitId = DEV ? TestIds.BANNER : "ca-app-pub-8363970344320746/9472339023";

const App = () => { useEffect(() => { // Event listener for interstitial Ads const interstitialAdEventListener = () => { console.log("Interstitial Ad Loaded"); };

  // Start loading the interstitial straight away
  interstitial.load();

  // Add event listener for interstitial ad loaded
  interstitial.addAdEventListener(AdEventType.LOADED, interstitialAdEventListener);

  // Event listener for rewarded Ads
  const rewardedAdEventListener = (type, error, reward) => {
    if (type === RewardedAdEventType.LOADED) {
      console.log("Rewarded Ad Loaded");
    }

    if (type === RewardedAdEventType.EARNED_REWARD) {
      console.log("User earned reward of ", reward);
    }
  };

  // Start loading the rewarded ad straight away
  rewarded.load();

  // Add event listener for rewarded ad loaded and reward earned
  rewarded.addAdEventListener(RewardedAdEventType.LOADED, rewardedAdEventListener);
  rewarded.addAdEventListener(RewardedAdEventType.EARNED_REWARD, rewardedAdEventListener);

  // Unsubscribe from events on unmount
  return () => {
    interstitial.removeEventListener(AdEventType.LOADED, interstitialAdEventListener);
    rewarded.removeEventListener(RewardedAdEventType.LOADED, rewardedAdEventListener);
    rewarded.removeEventListener(RewardedAdEventType.EARNED_REWARD, rewardedAdEventListener);
  };
}, []);

return ( <SafeAreaView style={styles.container}> <Text style={styles.heading}> React Native + AdMob Integration{"\n\n"} www.aboutreact.com </Text> <View style={{ flex: 1, alignItems: "center", justifyContent: "center", }} > <TouchableOpacity style={styles.buttonStyle} activeOpacity={0.5} onPress={() => interstitial.show()} > <Text style={styles.buttonTextStyle}> Show Interstitial Ad </Text> </TouchableOpacity> <TouchableOpacity style={styles.buttonStyle} activeOpacity={0.5} onPress={() => rewarded.show()} > <Text style={styles.buttonTextStyle}> Show Rewarded Ad </Text> </TouchableOpacity> </View> <BannerAd unitId={bannerAdUnitId} size={BannerAdSize.ANCHORED_ADAPTIVE_BANNER} requestOptions={{ requestNonPersonalizedAdsOnly: true, }} /> </SafeAreaView> ); };

export default App;

const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "white", padding: 16, }, heading: { fontSize: 20, textAlign: "center", marginTop: 30, }, buttonStyle: { minWidth: 300, backgroundColor: "#7DE24E", borderWidth: 0, color: "#FFFFFF", borderColor: "#7DE24E", height: 40, alignItems: "center", borderRadius: 30, marginLeft: 35, marginRight: 35, marginTop: 20, marginBottom: 25, }, buttonTextStyle: { color: "#FFFFFF", paddingVertical: 10, fontSize: 16, }, });

Same issue iam facing iam unable to load the ads and its showing Error: InterstitialAd.show() The requested InterstitialAd has not loaded and could not be shown., js engine: hermes

EtvWinReactNative avatar Sep 04 '23 07:09 EtvWinReactNative

Please try my solution. It is working

import {View, StyleSheet, TouchableOpacity, Button} from 'react-native';
import {useEffect, useState} from 'react';
import {
  InterstitialAd,
  TestIds,
  AdEventType,
} from 'react-native-google-mobile-ads';

const TestAds = props => {
  const [isAdViewHidden, setIsAdViewHidden] = useState(false);

  useEffect(() => {
    setIsAdViewHidden(false);
  }, [isAdViewHidden]);

  return (
    <View style={styles.container}>
      {!isAdViewHidden && (
        <InterstitialAdComponent
          onShowCallback={() => setIsAdViewHidden(true)}
        />
      )}
    </View>
  );
};

const InterstitialAdComponent = ({onShowCallback}) => {
  const adUnitId = TestIds.INTERSTITIAL;
  const interstitial = InterstitialAd.createForAdRequest(adUnitId, {
    keywords: ['fashion', 'clothing'],
  });

  useEffect(() => {
    const unsubscribe = interstitial.addAdEventListener(
      AdEventType.LOADED,
      () => {
        console.log('Loaded');
      },
    );

    // Start loading the interstitial straight away
    interstitial.load();

    // Unsubscribe from events on unmount
    return unsubscribe;
  }, []);

  useEffect(() => {
    const unsubscribe = interstitial.addAdEventListener(
      AdEventType.CLOSED,
      () => {
        console.log('closed');
        onShowCallback();
      },
    );

    // Unsubscribe from events on unmount
    return unsubscribe;
  }, []);

  return (
    <View>
      <Button
        title="Show Interstitial"
        onPress={() => {
          try {
            interstitial.show();
          } catch (error) {
            console.log(error);
          }
        }}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingVertical: 200,
  },
});

export default TestAds;

Saliheen avatar Dec 15 '23 06:12 Saliheen