Using AdMob Plugin on CC 3.8

Hi there,
I’ve been working on AdMob plugin for hours, and after a few hours, I could build integrated project with AdMob on Android Studio.
But now I don’t know how to use it!
I can’t find any references.

I’ve found RewardedAdClient but after calling load() and show() nothing will happen.
I’m using default Google’s test unitId

I’ve found an issue with AdMob plugin. Seems it has a conflict with JsbBridgeWrapper, so if you need to use both of them you are required to put AdServiceHub.instance().init(this); after all jbw.addScriptEventListeners.

Something like this

JsbBridgeWrapper jbw = JsbBridgeWrapper.getInstance();
jbw.addScriptEventListener("requestFCMToken", arg ->{
	Log.d("FCM Token","FCM Token request's received");
	jbw.dispatchEventToScript("updateFCMToken",fcm_token);
});

jbw.addScriptEventListener("requestDeepLink", arg ->{
	Log.d("Deep Link","Deep Link request's received");
	jbw.dispatchEventToScript("updateDeepLink",token);
}); 
AdServiceHub.instance().init(this);

For someone who wants to use AdMob plugin

let a = new RewardedAdClient();
BusyIndicatorCode.show();
a.load("ca-app-pub-3940256099942544/5224354917",{
	onAdFailedToLoad:()=>{
		console.log("Ad Failed to load");
		BusyIndicatorCode.hide();
	},
	onAdLoaded:()=>{
		console.log("Ad Loaded");
		a.show();
		BusyIndicatorCode.hide();
	},
	onAdImpression() {
		console.log("Ad Imoression");				
	},
});

Another issue I encountered was Android Build bugs.
If you are using Gradle +8.0 you are required to following changes on Google AdMob Extension

  1. Remove the package name from extensions\Google AdMob\template\android\libadmob\AndroidManifest.xml
    It must be like this
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET"/>

    <application android:name="com.cocos.admob.AdMobApplication" android:hardwareAccelerated="true">

        <!-- Tell CocosNativeActivity the name of our .so -->
        <meta-data android:name="android.app.lib_name" android:value="cocos"/>

        <!-- Google admob application ID-->
        <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-6276501902953384~4865867847"/>

    </application>

</manifest>
  1. Add package name which removed in 1 in extensions\Google AdMob\template\android\libadmob\build.gradle as a namespace
    It must be like this
plugins {
    id 'com.android.library'
}

android {
    compileSdkVersion PROP_COMPILE_SDK_VERSION.toInteger()
    //=======================
    //THIS LINE MUST BE ADDED
    namespace "com.cocos.admob"
    
    defaultConfig {
        minSdkPreview PROP_MIN_SDK_VERSION
        targetSdkPreview PROP_TARGET_SDK_VERSION
        versionCode 1
        versionName "1.0"
    }

    sourceSets.main {
        java.srcDirs "src"
        res.srcDirs 'res'
        jniLibs.srcDirs 'libs'
        manifest.srcFile "AndroidManifest.xml"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation project(':libcocos')
    implementation 'com.google.android.gms:play-services-ads:22.1.0'
    implementation 'com.google.code.gson:gson:2.9.0'
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    implementation "androidx.lifecycle:lifecycle-runtime:2.5.1"
    implementation 'androidx.lifecycle:lifecycle-process:2.5.1'
    annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.5.1"
    implementation project(':nativetemplates')
}

Do these steps for extensions\Google AdMob\template\android\googleads-mobile-android-native-templates-main\nativetemplates\src\main\AndroidManifest.xml

and

extensions\Google AdMob\template\android\googleads-mobile-android-native-templates-main\nativetemplates\build.gradle

the namespace for this step is "com.google.android.ads.nativetemplates"

Another issue for this plugin is that it doesn’t support SSV, in some circumstances, you need to send a user id and custom data to the server, which is needed for Rewarded ads. To fix this issue follow the instructions
1- Change loadAd function in extensions\Google AdMob\template\android\libadmob\src\com\cocos\admob\service\RewardedAdService.java to

    private void loadAd(String options) {
        try {
            JSONObject jsonObject = new JSONObject(options);
            this.unitId = jsonObject.getString("unitId");
            if(this.unitId == null)
                throw new Exception("unitId must have value");
        }catch (Exception e){

        }

        AdRequest adRequest = new AdRequest.Builder()
                .setRequestAgent(AdServiceHub.extensionVersion)
                .build();
        RewardedAd.load(activity, unitId,
                adRequest, new RewardedAdLoadCallback() {
                    @Override
                    public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
                        // Handle the error.
                        Log.d(TAG, loadAdError.toString());
                        Toast.makeText(activity, "Rewarded ad failed to load.", Toast.LENGTH_SHORT).show();
                        rewardedAd = null;
                        bridge.sendToScript(RewardedAdLoadCallbackNTF.class.getSimpleName(), new RewardedAdLoadCallbackNTF(unitId, "onAdFailedToLoad", loadAdError.toString()));
                    }

                    @Override
                    public void onAdLoaded(@NonNull RewardedAd ad) {
                        rewardedAd = ad;
                        try {
                            JSONObject jsonObject = new JSONObject(options);
                            String customData = jsonObject.getString("customData");
                            String userId = jsonObject.getString("userId");
                            ServerSideVerificationOptions ssvOption = new ServerSideVerificationOptions.Builder()
                                    .setCustomData(customData)
                                    .setUserId(userId)
                                    .build();
                            rewardedAd.setServerSideVerificationOptions(ssvOption);
                        }catch (Exception e){

                        }

                       Log.d(TAG, "Ad was loaded.");
                        Toast.makeText(activity, "Rewarded ad loaded.", Toast.LENGTH_SHORT).show();
                        bridge.sendToScript(RewardedAdLoadCallbackNTF.class.getSimpleName(), new RewardedAdLoadCallbackNTF(unitId, "onAdLoaded"));

                        rewardedAd.setOnPaidEventListener(adValue -> {
                            RewardedPaidEventNTF rewardedPaidEventNTF = new RewardedPaidEventNTF(unitId);

                            rewardedPaidEventNTF.valueMicros = adValue.getValueMicros();
                            rewardedPaidEventNTF.currencyCode = adValue.getCurrencyCode();
                            rewardedPaidEventNTF.precision = adValue.getPrecisionType();

                            AdapterResponseInfo loadedAdapterResponseInfo = rewardedAd.getResponseInfo().
                                    getLoadedAdapterResponseInfo();
                            rewardedPaidEventNTF.adSourceName = loadedAdapterResponseInfo.getAdSourceName();
                            rewardedPaidEventNTF.adSourceId = loadedAdapterResponseInfo.getAdSourceId();
                            rewardedPaidEventNTF.adSourceInstanceName = loadedAdapterResponseInfo.getAdSourceInstanceName();
                            rewardedPaidEventNTF.adSourceInstanceId = loadedAdapterResponseInfo.getAdSourceInstanceId();

                            Bundle extras = rewardedAd.getResponseInfo().getResponseExtras();
                            rewardedPaidEventNTF.mediationGroupName = extras.getString("mediation_group_name");
                            rewardedPaidEventNTF.mediationABTestName = extras.getString("mediation_ab_test_name");
                            rewardedPaidEventNTF.mediationABTestVariant = extras.getString("mediation_ab_test_variant");

                            bridge.sendToScript(RewardedPaidEventNTF.class.getSimpleName(), rewardedPaidEventNTF);
                        });

                        rewardedAd.setFullScreenContentCallback(new FullScreenContentCallback() {
                            @Override
                            public void onAdClicked() {
                                // Called when a click is recorded for an ad.
                                Log.d(TAG, "Ad was clicked.");
                                bridge.sendToScript(RewardedFullScreenContentCallbackNTF.class.getSimpleName(), new RewardedFullScreenContentCallbackNTF(unitId, "onAdClicked"));
                            }

                            @Override
                            public void onAdDismissedFullScreenContent() {
                                // Called when ad is dismissed.
                                // Set the ad reference to null so you don't show the ad a second time.
                                Log.d(TAG, "Ad dismissed fullscreen content.");
                                rewardedAd = null;
                                bridge.sendToScript(RewardedFullScreenContentCallbackNTF.class.getSimpleName(), new RewardedFullScreenContentCallbackNTF(unitId, "onAdDismissedFullScreenContent"));
                            }

                            @Override
                            public void onAdFailedToShowFullScreenContent(AdError adError) {
                                // Called when ad fails to show.
                                Log.e(TAG, "Ad failed to show fullscreen content.");
                                rewardedAd = null;
                                bridge.sendToScript(RewardedFullScreenContentCallbackNTF.class.getSimpleName(), new RewardedFullScreenContentCallbackNTF(unitId, "onAdFailedToShowFullScreenContent", adError.toString()));
                            }

                            @Override
                            public void onAdImpression() {
                                // Called when an impression is recorded for an ad.
                                Log.d(TAG, "Ad recorded an impression.");
                                bridge.sendToScript(RewardedFullScreenContentCallbackNTF.class.getSimpleName(), new RewardedFullScreenContentCallbackNTF(unitId, "onAdImpression"));
                            }

                            @Override
                            public void onAdShowedFullScreenContent() {
                                // Called when ad is shown.
                                Log.d(TAG, "Ad showed fullscreen content.");
                                bridge.sendToScript(RewardedFullScreenContentCallbackNTF.class.getSimpleName(), new RewardedFullScreenContentCallbackNTF(unitId, "onAdShowedFullScreenContent"));
                            }
                        });
                    }
                });
    }

2- change load function in extensions\Google AdMob\assets\ads\client\RewardedAdClient.ts to

    load(unitId: string,customData:string,userId, rewardedListener: RewardedAdListener) {
        log(module, `load, unitId = ${unitId}`);
        this.unitId = unitId;
        this.rewardedListener = rewardedListener;
        bridge.sendToNative(js.getClassName(LoadRewardedAdREQ), { unitId: JSON.stringify({unitId,customData,userId}) }, js.getClassName(LoadRewardedAdACK), (ack: LoadRewardedAdACK) => {
            log(module, `LoadRewardedAdACK, ${ack}`);
        }, this);
    }

The AdMob really is great and easy to use for everyone!
But there are some catches, if you are working on a final product you will face some odd problems, if your skill isn’t enough you will be in trouble!

Another issue I’ve found is that the AdMob plugin will overwrite native.bridge.onNative. It will cuz your incoming actions from native are never triggered! Also, you are supposed to send your data from native to script as JSON forma otherwise an error will be thrown.

Here is the modified version of Bridge.ts to fix the issue

type onNativeType = (arg0: string, arg1?: string | null)=>void;
const module = "[Bridge]";
export class Bridge {

    private OriginalOnNative:onNativeType=null;

    init(): Bridge {
        log(module, "init");
        this.overwriteCallback();

        const engineVersion = `cocos-${AdMobVersion}`;
        console.log(module, "init", `report engineVersion: ${engineVersion}.`);
        this.sendToNative(js.getClassName(VersionREQ), new VersionREQ('', engineVersion), null, null);
        return this;
    }

    destroy() {
        log(module, "destroy");
    }

    overwriteCallback() {
        log(module, "overwriteCallback");

        if (NATIVE) {
            this.OriginalOnNative = native.bridge.onNative;
            native.bridge.onNative = this.onNative;
        }
    }

    AdMobonNative = (arg0: string, arg1: string) => {
        log(module, `onNative method: ${arg0} | content: ${arg1}`,);
        //te.instance.dispatch(arg0, Route.instance.codec.decode(arg1));            
        const ack = route.codec.decode<Base>(arg1)
        route.dispatch(arg0, ack);
    }

    onNative = (arg0: string, arg1: string) => {
        this.OriginalOnNative(arg0,arg1);
        this.AdMobonNative(arg0,arg1);
    }

    sendToNative<TProto extends Base>(arg0: string, req: TProto, responseMethod?: string, onResponse?: INativeResponse, thisArg?: any) {
        log(module, "sendToNative", `method = ${arg0}, req.unitId = ${req.unitId}`);

        if (onResponse) {
            route.once(responseMethod, onResponse, thisArg);
        }

        if (NATIVE) {
            native.bridge.sendToNative(arg0, route.codec.encode(req));
        }
    }

}

And for JSON errors, you need to change Codec.ts as follows

export interface ICodec{
    decode<T>(content:string) : T
    encode<T>(t:T):string
}

export class Codec implements ICodec{

    decode<T>(content:string) : T{
        try {
            let json = JSON.parse(content);
            if(json)
                return json as T;                
        } catch (error) {
            return content as T;
        }
    }
    
    encode<T>(t:T):string{
        return JSON.stringify(t);
    }
}

Change overwriteCallback function in Bridge.java as follows

    private void overwriteCallback() {
        Log.d(TAG, "overwriteCallback: ");
        //==========
        //IMPORTANT JsbBridge.callback is private and you need to make it public
        //==========
        JsbBridge.ICallback callback = JsbBridge.callback;
        JsbBridge.setCallback(new JsbBridge.ICallback() {
            @Override
            public void onScript(String arg0, String arg1) {
                Log.d(TAG, "onScript: " + arg0 + " | " + arg1);
                route.dispatch(arg0, arg1);
                callback.onScript(arg0,arg1);
            }
        });
    }

If there is an AdMob plugin source code repository, I would like to push these fixes!

https://github.com/cocos/cocos-google-admob/

1 Like

“Hello, can you help me? When I add the AdMob plugin, my app immediately stops working on the phone.”

then how to call the a.load() with addition of user id and custom data? I mean what are those data I need to put for showing rewarded ad?