tomitank / cordova-plugin-admob-tomitank

Cordova AdMob Plugin
https://ratson.github.io/cordova-plugin-admob-free
MIT License
12 stars 4 forks source link

Ads (interstitial) not loading correctly when app has never accepted or denied consent in iOS 14 #3

Closed rastafan closed 3 years ago

rastafan commented 3 years ago

Hi,

we have encountered a problem using this plugin on iOS 14. If the app has neve given consent, nor denied it, the ads are not correctly loaded. We tried this with Interstitial ads only.

The LOAD_FAIL event fires correctly. Debugging the plugin, it seems to return code 1.

According to this link, it means request success, but no ad received.

This case can be reproduced on a device with iOS 14, that has never given tracking consent. Surely, one should call trackingStatusForm to trigger the consent popup, but there is a situation where this popup is not shown and therefore the app can not give or deny consent for tracking. Since calling the trackingStatusForm method will trigger the system native consent request, one should try this on a device like this:

In the aforementioned device, executing trackingStatusForm will not trigger any alert (as it should), but ads requests will fail and not show any ad.

Once th app asks at least once for authorization (be it given or denied) then the ads start working.

Please feel free to ask for more details or tests if needed.

Thank you very much.

The following is our environment:

Ionic:

Ionic CLI : 6.11.8 (/Users/alessioinnocenti/.nvm/versions/node/v10.9.0/lib/node_modules/@ionic/cli) Ionic Framework : @ionic/angular 5.1.1 @angular-devkit/build-angular : 0.801.3 @angular-devkit/schematics : 8.1.3 @angular/cli : 8.1.3 @ionic/angular-toolkit : 2.2.0

Cordova:

Cordova CLI : 10.0.0 Cordova Platforms : android 9.0.0, ios 6.1.1 Cordova Plugins : cordova-plugin-ionic-keyboard 2.2.0, cordova-plugin-ionic-webview 5.0.0, (and 25 other plugins)

Utility:

cordova-res (update available: 0.15.1) : 0.8.1 native-run (update available: 1.2.1) : 0.2.8

System:

Android SDK Tools : 26.1.1 (/Users/alessioinnocenti/Library/Android/sdk/) ios-deploy : 1.9.2 ios-sim : 8.0.2 NodeJS : v10.9.0 (/Users/alessioinnocenti/.nvm/versions/node/v10.9.0/bin/node) npm : 6.14.4 OS : macOS Catalina Xcode : Xcode 12.0.1 Build version 12A7300

tomitank commented 3 years ago
"settings->privacy->tracking->allow apps to request to track" turned off (see here for more details)

In the aforementioned device, executing trackingStatusForm will not trigger any alert (as it should), but ads requests will fail and not show any ad.

Key is the getTrackingStatus() function. Please read the full example of my plugin!

rastafan commented 3 years ago

HI, thank you for your response.

unfortunately, we are already calling that function. This is the code we use at app startup:

    //CALLING CONSENT PLUGIN FOR ANDROID FIRST
    consent.checkConsent(this.publisherIds).then((status) => {
      this.consentStatus = status;

      console.log('CONSENT STATUS : ' + this.consentStatus);

    //IF WE ARE ON IOS , CALL  getTrackingStatus
      if (this.platform.is('ios')) {
        return this.admob.getTrackingStatus();
      } else {
        return false;
      }

    }).then((statusIos) => {
      console.log('IOS STATUS : ', statusIos);

      //UNIFORMING STATUS TO THE CONSENT PLUGIN ONE
      if (statusIos === 'notDetermined') {
        this.consentStatus = 'UNKNOWN'; 
      } else {
        this.consentStatus = statusIos === 'authorized' ? 'PERSONALIZED' : 'NON_PERSONALIZED';
      }

    });

Can you give us some hint on this? The ads can not load when tracking is disabled.

Also, we noticed that the plugin appears to build two requests to instantiate an Interstitial. It calls __buildAdRequest once in prepareInterstitial and once in __cycleInterstitial (which is called by prepareInterstitial). We do not think this is the issue, but it's worth reporting at least.

tomitank commented 3 years ago

where do you show ads?

rastafan commented 3 years ago

I'm sorry, what do you mean? Are you asking for the code part where we call the show method?

Here is the complete code we use to show interstitial ads. Feel free to ask for details if needed.

public consentStatus = 'UNKNOWN'; //This var will contain the consent status

//INITIALIZE PLUGIN SETTINGS - THIS IS CALLED AT STARTUP
async initialize() {

    this.admob.interstitial.config({
        id: this.platform.is('android') ? 'ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX' : 'ca-app-pub-YYYYYYYYYYYYYYYY/YYYYYYYYYY',
        isTesting: true,
        forChild: false,
        forFamily: true,
        autoShow: false
    });

    await this.updateConsentStatus();

    //Prepares observables to listen to all plugin events
    this.eventObs = merge(
        this.admob.on('admob.interstitial.events.LOAD'),
        this.admob.on('admob.interstitial.events.LOAD_FAIL'),
        this.admob.on('admob.interstitial.events.OPEN'),
        this.admob.on('admob.interstitial.events.CLOSE'),
        this.admob.on('admob.interstitial.events.EXIT_APP')
    ).pipe(
        tap((event) => {
            console.log('AD EVENT NO FILTER : ', event);
            //If the event is OPEN or LOAD_FAIL we remove the spinner
            if (event.type === 'admob.interstitial.events.LOAD_FAIL' ||
                event.type === 'admob.interstitial.events.OPEN') {
                this.spinner.dismiss();
            }
        }),
        catchError((error, caught) => {
            //Somehow the observables trew an error. This is just a safety check
            return from(['ERROR']);
        }),
        filter((event, index) => {
            if (event.type === 'admob.interstitial.events.LOAD') {
                //THE AD IS LOADED, SO WE SHOW IT
                console.log('AD LOADED');
                this.admob.interstitial.show().then((value) => {
                    console.log('AD SHOWN : ', value);
                }).catch(e => console.log('INTERSTITIAL ERROR 2 - ', e));
            }

            //FILTERS ONLY LOAD_FAIL, CLOSE AND EXIT_APP EVENTS
            return (event.type === 'admob.interstitial.events.LOAD_FAIL' ||
                event.type === 'admob.interstitial.events.CLOSE' ||
                event.type === 'admob.interstitial.events.EXIT_APP');
        }),
        take(1), // We only take one event - since we only let out events that terminates the flow
        switchMap((event, index) => {
            console.log('AD EVENT');
            console.log(event);
            return from(['OK']);
        })
    );

}

showInterstitialAd(): Observable < string > {

    this.spinner.create();

    this.showConsent()
        .then(async () => {
            console.log('consentStatus', this.consentStatus);

            if (this.consentStatus === 'PERSONALIZED') {
                this.admob.interstitial.config({
                    adExtras: { npa: '1' } //we send the "npa" flag set to '1' to request non personalized ads
                });
            } else {
                this.admob.interstitial.config({
                    adExtras: {} //we clear the extras flag
                });
            }

            return this.admob.interstitial.prepare();

        }).catch(console.error);

    return this.eventObs; //Returns the events observable so that the caller can know when the ad is closed 

}

//Shows the consent modals, using both the consent plugin (android and iOS) and the trackingStatusForm function (iOS only)
async showConsent(force = false) {

    if (!this.consentStatus || this.consentStatus === 'UNKNOWN' || force) {
        // Selezione non effettuata, si mostra il pannello di consenso

        const ok = await consent.isRequestLocationInEeaOrUnknown();

        const form = new consent.Form({
            privacyUrl: 'https://policies.google.com/privacy',
            nonPersonalizedAds: true,
            personalizedAds: true,
            adFree: false
        });
        await form.load();
        const result = await form.show();
        this.consentStatus = result.consentStatus;
        console.log('UPDATED CONSENT STATUS : ' + this.consentStatus);

        // On IOS, if user accepted the first consent alert, we show the native consent 
        if (this.platform.is('ios') && this.consentStatus === 'PERSONALIZED') {
            await this.admob.trackingStatusForm().then((status) => { // iOS tracking form..
                if (status !== 'authorized') {
                    // User did not give consent, so we set it as "Non personalized"
                    this.consentStatus = 'NON_PERSONALIZED';
                }
            });
        }

    }

}

async updateConsentStatus() {
    //CALLING CONSENT PLUGIN FOR ANDROID FIRST
    consent.checkConsent(this.publisherIds).then((status) => {
        this.consentStatus = status;

        console.log('CONSENT STATUS : ' + this.consentStatus);

        //IF WE ARE ON IOS , CALL  getTrackingStatus
        if (this.platform.is('ios')) {
            return this.admob.getTrackingStatus();
        } else {
            return false;
        }

    }).then((statusIos) => {
        console.log('IOS STATUS : ', statusIos);

        //UNIFORMING STATUS TO THE CONSENT PLUGIN ONE
        if (statusIos === 'notDetermined') {
            this.consentStatus = 'UNKNOWN';
        } else {
            this.consentStatus = statusIos === 'authorized' ? 'PERSONALIZED' : 'NON_PERSONALIZED';
        }

    })

}
tomitank commented 3 years ago

@rastafan is it sure that it is always resolve? I don't see this code, but i think it's have a catch() function as well, which is unhandled. consent.checkConsent(this.publisherIds).then((status) => {

rastafan commented 3 years ago

Yes, it probably has a catch function that is not handled, but i don't think this could be the problem, since checkConsent is a function from the other plugin. Also, it always resolves positively in our tests. Have you tried the plugin with the "allow apps to request to track" turned off to reproduce the issue on iOS?

We may be missing something but we do not know what or how to find it right now. We tried a lot of combinations but to no avail. It almost looks like a GoogleAdsSDK issue, but it seems strange since it is used by many people around the world and no one seems to mention this issue anywhere.

Thank you.

svzi commented 3 years ago

Yes, it probably has a catch function that is not handled, but i don't think this could be the problem, since checkConsent is a function from the other plugin.

@rastafan Would you mind sharing which plugin you use?

rastafan commented 3 years ago

@svzi sure, we are using the consent plugin (cordova-plugin-consent), which is part of the admob-plus plugin repository.

svzi commented 3 years ago

@rastafan Thanks a lot for sharing!

tomitank commented 3 years ago

@rastafan The plugin works perfectly for me. Please try to test without cordova-plugin-consent.

rastafan commented 3 years ago

We even tried to make a small native app to test out the behaviour. It behaves exactly as our cordova app, this meaning that if we turn off "allow apps to request to track" ads are not loading properly. Do you have a piece of code that works for you even with "allow apps to request to track" turned off? So we can try and exclude a problem with our devices. We can share the native app project (not right now but during the next days) if you wish to test it.

Thank you.

tomitank commented 3 years ago

@rastafan Please try out my app and write here what you are experience: iOS: https://apps.apple.com/us/app/sakk/id1150654415 Android: https://play.google.com/store/apps/details?id=sakk.tanky.hu

If all is well, then the fault is in yours code.

rastafan commented 3 years ago

Hi,

we tried your app in iOS (nice job!), but we could only see banners (which were working fine). Did you implement interstitial as well? How can we test them?

We recorded a short video of the native app we used to test the behaviour, showing it with "allow apps to request to track" both turned off and on. You can download it here.

Also, to exclude something admob-side, we could share with you our production admob codes, if you can try it out on your working app.

thank you for your help.

tomitank commented 3 years ago

Did you implement interstitial as well? How can we test them?

Play a game (make some move) and restart the game again. (not the app)

tomitank commented 3 years ago

@rastafan any news? :)

rastafan commented 3 years ago

Hi, we tried and your app seems to work fine in all cases. If you can not see errors in our code/procedure and do not have additional steps to perform, we really do not know what to do.

Only thing is, our app is still not published, so maybe this is the main problem, since test ads are working fine. But we are not sure.

We'll tell you as soon as the app is published (next month) if this situation persists or not. Meanwhile, thanks a lot.

tomitank commented 3 years ago

@rastafan it's work? :)

rastafan commented 3 years ago

HI,

we ended up reverting to the admob-free plugin, since apple kept refusing our app, because they could not find the Tracking Request popup (even thought it was working fine for us). Anyway, even in production we could not see ads at first. Now, sometimes, they come out. We are thinking this might be some sort of decision on the admob side. Maybe haveng not much traffic on our app caused this.

I guess you can close this issue for now. If something arises, we'll open it again.

Thank you for your help so far.