Monetize your NativeScript apps with AdMob (part 2 - Android)


Or how to create a simple module when both iOS and Android come into play

This is the second part of the Monetize your NativeScript apps with AdMob blog. In part one I discussed how you can easily access and use the native API of a native framework in JavaScript, taking the AdMob library for iOS as an example. To do that, you only needed the existing AdMob documentation in combination with a few simple rules to translate the Objective-C API calls to JavaScript.

The popular mobile platforms, however, are two - iOS and Android. So, it would be fair if I cover Android as well. Moreover, further in this article as things get more interesting and we are close to having a cross-platform AdMob usage, I will show you what’s the recommended way of separating and isolating the native API calls in a NativeScript project. So, let’s begin.

googleads-bannerview-android googleads-interstitial-android

Just like in the iOS case, we will focus on these two types of ads:

  • Banners - These ads appear at the bottom of your application without interruption
  • Interstitials - These ads appear from time to time occupying the whole screen estate
Following the documentations links above and with some help from the NativeScript documentation here is how to plug the AdMob SDKs in a NativeScript app.

Prerequisites

We made an important update in the latest version of NativeScript that allows calling Java methods of private classes. This will allow the usage of the Android AdMob API. Therefore, please check your version with:

tns --version

It should read 1.5.2

If this is not the case, update your NativeScript version following this documentation article.

Regarding the AdMob SDK itself, it is distributed as a part of Google Play Services. So, I extended my nativescript-google-mobile-ads-sdk plugin to have a dependency on Google Play Services.

We will enable our Banner and Interstitial ads in the same apps we created in Part 1 of the blog. Therefore, make sure you first uninstall the old version of the plugin from these apps:

tns plugin remove nativescript-google-mobile-ads-sdk

and then add it anew:

tns plugin add nativescript-google-mobile-ads-sdk

It’s coding time!

Using Banner ads

Just like in the iOS case, our Android banner will be hosted in a Placeholder on its creatingView event. Considering the existing iOS implementation, our code would look like this:

function creatingView(args) {
    if(platformModule.device.os == platformModule.platformNames.ios) {
        bannerView = GADBannerView.alloc().initWithAdSize(kGADAdSizeSmartBannerPortrait);
        args.view = bannerView;
    }
    else {
        var bannerView = new com.google.android.gms.ads.AdView(args.object._context);
        bannerView.setAdSize(com.google.android.gms.ads.AdSize.SMART_BANNER);
        args.view = bannerView;
    }
}
 
exports.creatingView = creatingView;

Now it’s time to set up a few important properties of the bannerView in the loaded event of the page:

bannerView = placeholder.android;
bannerView.setAdUnitId("ca-app-pub-3940256099942544/6300978111");
 
var MyAdListener = com.google.android.gms.ads.AdListener.extend(
{
    onAdLeftApplication: function() {
        // do sth as the user is leaving the app, because of a clicked ad
        console.log("Leaving the app, bye bye!");
    }
});
var listener = new MyAdListener();
bannerView.setAdListener(listener);
 
var adRequest = new com.google.android.gms.ads.AdRequest.Builder();
adRequest.addTestDevice(com.google.android.gms.ads.AdRequest.DEVICE_ID_EMULATOR);
var requestBuild = adRequest.build();
bannerView.loadAd(requestBuild);

The complete source of the pageLoaded function called for the loaded event of the page will be:

function pageLoaded(args) {
    var page = args.object;
    page.bindingContext = vmModule.mainViewModel;
 
    var placeholder = page.getViewById("bannerView");
    var bannerView;
 
    if(platformModule.device.os == platformModule.platformNames.ios) {
        bannerView = placeholder.ios;
        bannerView.adUnitID = "ca-app-pub-3940256099942544/2934735716";
        bannerView.strongDelegateRef = bannerView.delegate = GADBannerViewDelegateImpl.new();
        bannerView.rootViewController = page.ios;
        var request = GADRequest.request();
        request.testDevices = [kGADSimulatorID];
        bannerView.loadRequest(request);
    }
    else {
        bannerView = placeholder.android;
        bannerView.setAdUnitId("ca-app-pub-3940256099942544/6300978111");
         
        var MyAdListener = com.google.android.gms.ads.AdListener.extend(
        {
            onAdLeftApplication: function() {
                // do sth as the user is leaving the app, because of a clicked ad
                console.log("Leaving the app, bye bye!");
            }
        });
        var listener = new MyAdListener();
        bannerView.setAdListener(listener);
         
        var adRequest = new com.google.android.gms.ads.AdRequest.Builder();
        adRequest.addTestDevice(com.google.android.gms.ads.AdRequest.DEVICE_ID_EMULATOR);
        var requestBuild = adRequest.build();
        bannerView.loadAd(requestBuild);
    }
}
 
exports.pageLoaded = pageLoaded;

The intriguing thing here is the usage of the AdListener which is an abstract class in Java. When an object of that type is attached to our banner, it allows us to handle various interesting events related to the banner - for example, when the banner is clicked and this puts the app in the background in favor of a browser with the landing page related to the ad.

Here is the result:



The complete iOS + Android source code of the Banner NativeScript app can be found at GitHub.

Using Interstitial ads

Again... interstitials… these ‘disrupting the UX’ ads… Let’s see how they are implemented.

First, we should create the interstitial on the loaded event of the page, or in other words, in the pageLoaded function:

var interstitial = new com.google.android.gms.ads.InterstitialAd(page._context);
interstitial.setAdUnitId("ca-app-pub-3940256099942544/1033173712");
 
var MyAdListener = com.google.android.gms.ads.AdListener.extend(
{
    onAdClosed: function() {
        loadAndroidAd(interstitial);
    },
    onAdLeftApplication: function() {
        // do sth as the user is leaving the app, because of a clicked ad
        console.log("Leaving the app, bye bye!");
    }
});    
var listener = new MyAdListener();     
interstitial.setAdListener(listener);
 
loadAndroidAd(interstitial);
         
frameModule.topmost().currentPage.interstitial = interstitial;

As you can see, we are again taking benefit from an AdListener object attached to our interstitial. Actually, in the case of the interstitials, it’s not just our own scenario which may require the use of AdListener, but it’s the Google recommendation which makes it necessary. Because, we should prepare the next ad request the moment we close the previous interstitial, which happens on the onAdClosed function. And, here is the loadAndroidAd implementation:

function loadAndroidAd(interstitial) {
    var adRequest = new com.google.android.gms.ads.AdRequest.Builder();
    adRequest.addTestDevice(com.google.android.gms.ads.AdRequest.DEVICE_ID_EMULATOR);
    var requestBuild = adRequest.build();          
    interstitial.loadAd(requestBuild);
}

Finally, we should display the interstitial. In our sample case we do that on a button click. In a real-world scenario, this would happen between two important events in your app, like two game levels. Make sure you do not annoy your end-users too much. :)

function buttonTapped(args) {
    var interstitial = frameModule.topmost().currentPage.interstitial;
 
    if(platformModule.device.os == platformModule.platformNames.ios) {
        if(interstitial.isReady) {
            interstitial.presentFromRootViewController(args.object.page.frame.ios.controller);
        }
    }
    else {
        if (interstitial.isLoaded()) {
            interstitial.show();
        }
    }
}

Tap the TAP button to see the result:



The complete iOS + Android source code of the Interstitial app can be found at GitHub.

But...wait a minute! All these platform checks… it’s madness! Can we do something about it? Yes, we can!

If you check the given GitHub repositories above you may not find the code there very pretty. We have just a few platform checks in our case, but in a real-world project with quite a rich native SDK, the platform checks may kind of uglify your code. If you are a fan of the clean code, just like me, then you can isolate the iOS and Android native API calls into a module.

If you check the tns-core-modules under the node_modules folder of the project, you will notice that a module mainly consists of modulename.ios.js, modulename.android.android.js, modulename-common.js and package.json . Well, as you may guess the modulename.ios.js contains the JavaScript calls to the native iOS API and the modulename.android.js contains the JavaScript calls to the native Android API. And on top of that we have modulename-common.js to expose unified API, so that you don’t bother with calls to the native APIs yourself. 

NativeScript is smart enough to load only modulename.ios.js when run on an iOS device and modulename.android.js when run on an Android device. 

Banner ads

Using the feature discussed above, let’s create  bannerview.ios.js and bannerview.android.js and put the appropriate API calls there, thus avoiding the platform checks in our main-page.js file.

iOS

For the bannerview.ios.js file we will expose two static method - the first one will create the banner instance and the second will set a few important banner properties:

function createBanner(args) {
    var bannerView = GADBannerView.alloc().initWithAdSize(kGADAdSizeSmartBannerPortrait);
    return bannerView;
}
 
function loadBanner(placeholder) {
    bannerView = placeholder.ios;
    bannerView.adUnitID = "ca-app-pub-3940256099942544/2934735716";
    bannerView.strongDelegateRef = bannerView.delegate = GADBannerViewDelegateImpl.new();
    bannerView.rootViewController = placeholder.page;
    var request = GADRequest.request();
    request.testDevices = [kGADSimulatorID];
    bannerView.loadRequest(request);
}
 
var GADBannerViewDelegateImpl = (function (_super) {
    __extends(GADBannerViewDelegateImpl, _super);
    function GADBannerViewDelegateImpl() {
        _super.apply(this, arguments);
    }
    GADBannerViewDelegateImpl.prototype.adViewWillLeaveApplication = function (bannerView) {
        // do sth as the user is leaving the app, because of a clicked ad
        console.log("Leaving the app, bye bye!");
    };
    GADBannerViewDelegateImpl.ObjCProtocols = [GADBannerViewDelegate];
    return GADBannerViewDelegateImpl;
})(NSObject);
 
exports.createBanner = createBanner;
exports.loadBanner = loadBanner;

This is it with the iOS, part. Now to the Android one.

Android

In Android we should create the same static methods, this time filled with Android native API calls:

function createBanner(args) {
    var bannerView = new com.google.android.gms.ads.AdView(args.object._context);
    bannerView.setAdSize(com.google.android.gms.ads.AdSize.SMART_BANNER);
     
    return bannerView;
}
 
function loadBanner(placeholder) {
    bannerView = placeholder.android;
    bannerView.setAdUnitId("ca-app-pub-3940256099942544/6300978111");
     
    var MyAdListener = com.google.android.gms.ads.AdListener.extend(
    {
        onAdLeftApplication: function() {
            // do sth as the user is leaving the app, because of a clicked ad
            console.log("Leaving the app, bye bye!");
        }
    });
    var listener = new MyAdListener();
    bannerView.setAdListener(listener);
     
    var adRequest = new com.google.android.gms.ads.AdRequest.Builder();
    adRequest.addTestDevice(com.google.android.gms.ads.AdRequest.DEVICE_ID_EMULATOR);
    var requestBuild = adRequest.build();
    bannerView.loadAd(requestBuild);
}
 
exports.createBanner = createBanner;
exports.loadBanner = loadBanner;

main-page.js

The fact that the methods declarations in both files are the same allows us to simply call them in our main-page.js file and NativeScript will take the appropriate file (bannerview.ios.js or bannerview.android.js) depending on the platform the app is run at. Here is how simple main-page.js becomes:

var vmModule = require("./main-view-model");
var bannerModule = require("./bannerview");
 
function pageLoaded(args) {
    var page = args.object;
    page.bindingContext = vmModule.mainViewModel;
 
    var placeholder = page.getViewById("bannerView");
    bannerModule.loadBanner(placeholder);
}
 
function creatingView(args) {
    args.view = bannerModule.createBanner(args);
}
 
exports.pageLoaded = pageLoaded;
exports.creatingView = creatingView;

Note how that we are loading the bannerview module at the top.

You can get the complete source code of the bannerview module at GitHub.

Interstitial ads

Let’s do the same exercise for the interstitial ads. First, create two js files - interstitial.ios.js and interstitial.android.js. 

iOS

In the interstitial.ios.js file we will expose two static method - one that creates the interstitial and one that shows it:

var frameModule = require("ui/frame");
 
function createInterstitial() {
    var currentPage = frameModule.topmost().currentPage;
    currentPage.delegate = GADInterstitialDelegateImpl.new();
    currentPage.interstitial = createAndLoadiOSInterstitial();
}
 
function showInterstitial(args) {
    var interstitial = frameModule.topmost().currentPage.interstitial;
 
    if(interstitial.isReady) {
        interstitial.presentFromRootViewController(args.object.page.frame.ios.controller);
    }
}
 
function createAndLoadiOSInterstitial() {
    var interstitial = GADInterstitial.alloc().initWithAdUnitID("ca-app-pub-3940256099942544/4411468910");
    var request = GADRequest.request();
    interstitial.strongDelegateRef = interstitial.delegate = frameModule.topmost().currentPage.delegate;
    request.testDevices = [kGADSimulatorID];
    interstitial.loadRequest(request);
 
    return interstitial;
}
 
var GADInterstitialDelegateImpl = (function (_super) {
    __extends(GADInterstitialDelegateImpl, _super);
    function GADInterstitialDelegateImpl() {
        _super.apply(this, arguments);
    }
    GADInterstitialDelegateImpl.prototype.interstitialDidDismissScreen = function (gadinterstitial) {
        frameModule.topmost().currentPage.interstitial = createAndLoadiOSInterstitial();
    };
    GADInterstitialDelegateImpl.ObjCProtocols = [GADInterstitialDelegate];
    return GADInterstitialDelegateImpl;
})(NSObject);
 
exports.createInterstitial = createInterstitial;
exports.showInterstitial = showInterstitial;

Android

As expected, we should also create and expose two static methods for Android doing the same:

var frameModule = require("ui/frame");
 
function createInterstitial(page) {
 
    var currentPage = frameModule.topmost().currentPage;
    var interstitial = new com.google.android.gms.ads.InterstitialAd(currentPage._context);
    interstitial.setAdUnitId("ca-app-pub-3940256099942544/1033173712");
     
    var MyAdListener = com.google.android.gms.ads.AdListener.extend(
    {
        onAdClosed: function() {
            loadAndroidAd(interstitial);
        },
        onAdLeftApplication: function() {
            // do sth as the user is leaving the app, because of a clicked ad
            console.log("Leaving the app, bye bye!");
        }
    });    
    var listener = new MyAdListener();     
    interstitial.setAdListener(listener);
 
    loadAndroidAd(interstitial);
             
    currentPage.interstitial = interstitial;
}
 
function showInterstitial(args) {
    var interstitial = frameModule.topmost().currentPage.interstitial;
 
    if (interstitial.isLoaded()) {
        interstitial.show();
    }
}
 
function loadAndroidAd(interstitial) {
    var adRequest = new com.google.android.gms.ads.AdRequest.Builder();
    adRequest.addTestDevice(com.google.android.gms.ads.AdRequest.DEVICE_ID_EMULATOR);
    var requestBuild = adRequest.build();          
    interstitial.loadAd(requestBuild);
}
 
exports.createInterstitial = createInterstitial;
exports.showInterstitial = showInterstitial;

main-page.js

Finally, thanks to the code separation and abstraction, here is how simple the main-page.js looks now:

var vmModule = require("./main-view-model");
var platformModule = require("platform");
var frameModule = require("ui/frame");
 
var interstitialModule = require("./interstitial");
 
function pageLoaded(args) {
    var page = args.object;
    page.bindingContext = vmModule.mainViewModel;
     
    interstitialModule.createInterstitial();
}
 
function buttonTapped(args) {
    interstitialModule.showInterstitial(args);
}
 
exports.pageLoaded = pageLoaded;
exports.buttonTapped = buttonTapped;

Simple, huh? Get the complete source code of that modulized example from GitHub.

Now you know how to directly use the APIs of a native library by only referring to the documentation of that library and the NativeScript documentation. And, you also know how to extract the native API calls in a module thus keeping your code clean and tidy.

Stay tuned for the introduction of the verified AdMob plugin at the {N} Verified Plugins Marketplace.

For more information and updates on NativeScript, please follow @nativescript at Twitter or follow the NativeScript project.

Comments


Comments are disabled in preview mode.
NativeScript is licensed under the Apache 2.0 license
© 2020 All Rights Reserved.