DbTradeAlert for Android: Add Google AdMob

First post in this series: Introduction to DbTradeAlert

Previous post: Integrate Google Play Services


Google AdMob consists of two programs:

  • AdWords is Google’s main source of income and provides pay-per-click advertising to its users
  • AdSense is AdWords’ counterpart and provides website and app publishers with an opportunity to earn money by showing ads

This post requires an existing app in the Play Store. It will add AdSense to DbTradeAlert and the first step is to create an AdMob account.

1. Create an AdMob Account

To create an AdMob account follow these steps and be aware that some of the information cannot be changed later:

  1. Navigate to the Firebase console at console.firebase.google.com
  2. Select your project
  3. On the navigation bar click AdMob
  4. In the center click Register for AdMob
  5. In the next step select a Google account you want to connect to AdMob
  6. In the next step enter information for your AdSense account:
    1. Country will for example determine the payment currency
    2. Individual / business account determines whether payments will be made to the account holder or a company
    3. Full address, email and phone of payee
  7. In the next step enter the timezone and billing currency information for your AdWords account
  8. In the next step agree to the fine print and click Create AdMob Account

The next step is to register an app with AdMob.

2. Register an App with AdMob

You normally register an app with AdMob by searching the Play Store. That search will only return indexed apps and indexing can take a day or more after initially publishing an app. This is not ideal as it makes publishing an app a two-step process with possibly a multi-day delay.

It seems Google created a new option because of this where you add an app by typing in its Id. But for me that created an new Play Store listing with the same app Id (de.dbremes.dbtradealert.withads) and a lot of confusion. Also note that you cannot delete an AdMob registration. So here is the safe way of selecting an app by searching the Play Store:

  1. Navigate to https://apps.admob.com and log in – the top right shows your Publisher-ID by the way
  2. Click the Monetize tab
  3. The first step is to select the app and the “Search for Your App” tab should be open
    1. Type in a part of the app’s name – “dbtradealert” in my case
    2. The list below the search box should now show the app – click its Select button
    3. Back in the overview step click Add App
  4. The second step is to choose an ad format and name the ad unit
    1. Select an ad format – banner in my case – and fill in the format specific options – I went with the defaults and named the ad unit after the activity that will show it
    2. Click Save – the list of Ad units shows a new entry and most importantly its Ad unit Id which you’ll need later
  5. The third step is to connect AdMob to Firebase Analytics which is optional
    1. Click Connect to Firebase
    2. Confirm that you are allowed to connect this AdMob account to Firebase Analytics
    3. Confirm the package name
    4. Select a Firebase project – or create a new one
  6. In the next step click the link to download the configuration file
    1. You are transferred to the Firebase console where you can download yet another google-services.json file
  7. In the AdMob console click Finish

3. Create an App Flavor for AdSense Integration

Similar to the playStore flavor DbTradeAlert now gains a withAds flavor. This will incorporate Google’s AdSense for monetization.

The intended state of the app’s flavors is:

  • naked flavor: just for reference, without Google Play Services, and not available in the Play Store
  • playStore flavor: with Firebase Analytics for tracking and available in the Play Store
  • withAds flavor: like the playStore flavor and with AdSense for banner adverts added

To have the playStore flavor’s functionality available in the withAds flavor:

  • playStore flavor’s functionality moves to a common package
  • both flavors will point to that package
  • the withAds flavor will get an additional AdHelper class providing AdSense specific functionality
  • the playStore and naked flavors will get the same AddHelper class but with empty methods

For now the withAds flavor could simply point to the playStore flavor’s package. But with each getting its own AdHelper implementation that approach would be a dead end.

4. Move playStore Flavor’s Functionality to a Common Package

The first step is to create the “common” package where playStore flavor’s code will go. I didn’t find a way to have Android Studio do this with flavors added so I did it manually:

  1. Create a new flavor “commmon”
  2. Add a Java directory to “common”
  3. Switch to build variant “commonDebug”
  4. Add a new package to “..\app\src\java\common” using the general package name – “de.dbremes.dbtradealert”

After that move PlayStoreHelper.java to its new package. Same reason as above for doing this manually:

  1. In Explorer move PlayStoreHelper.java from the “playStore” package to the new one
  2. Switch back to build variant playStoreDebug
  3. Open build.gradle (Module: app) and change it like this:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    // ...

    productFlavors {
        naked
        playStore {
            applicationId = "${android.defaultConfig.applicationId}.playStore"
        }
    }
    sourceSets {
        playStore.java.srcDirs = ['src/common/java']
    }
}

// ...

The “common” flavor is gone – it just served to let Android Studio create the folder hierarchy. And the new sourceSets closure makes the playStore flavor use the files in the “commmon” package. After this the playStore flavor should build like before.

5. Create the withAds Flavor

The first step is again to create the new flavor in build.gradle:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    // ...

    productFlavors {
        naked
        playStore {
            applicationId = "${android.defaultConfig.applicationId}.playStore"
        }
        withAds {
            applicationId = "${android.defaultConfig.applicationId}.withAds"
        }
    }
    sourceSets {
        playStore.java.srcDirs = ['src/common/java']
        withAds.java.srcDirs = ['src/common/java', 'src/withAds/java']
    }
}

dependencies {
    // ...
    playStoreCompile 'com.google.firebase:firebase-core:9.4.0'
    withAdsCompile 'com.google.firebase:firebase-core:9.4.0'
    withAdsCompile 'com.google.firebase:firebase-ads:9.4.0'
    compile 'org.jetbrains:annotations-java5:15.0'
}

// ...

Like the playStore flavor this one sets the applicationId, adds Firebase core as a dependency, and uses the common package’s java sources. In addition to that it adds Firebase Ads as a dependency and specifies an additional package for its source set.

You probably remember from creating the previous flavor that the new one won’t build yet – the Google Services plugin for Gradle will search in vain for the google-services.json file and this file also doesn’t contain the flavor’s applicationId yet.

So generate a google-services.json file like before:

  1. Navigate to https://console.firebase.google.com and log in
  2. Click Add App
  3. In the popup window click Add Firebase to my Android app
  4. In the “Enter app details” step add the applicationId – “de.dbremes.dbtradealert.withAds” in my case
  5. After clicking Add App a file named google-services.json is downloaded

When you compare this file to the previous one you’ll find that it has the same information plus data for a new client – de.dbremes.dbtradealert.withAds.

This file will be used by both the playStore and the withAds flavor and therefore needs to be in the app directory above them. And of course the playStore flavor’s google-services.json needs to go.

The withAds flavor should now compile providing the same functionality as the playStore flavor. To test that generate signed APKs for both flavors – the naked flavor won’t build with the Google Services plugin applied of course.

After that create a strings.xml file for the withAds flavor, too:

  1. Let Android Studio create a new resource directory for the withAds source set
  2. Let Android Studio create a new resource file named strings.xml in that directory
  3. Copy the app_name element from one of the existing strings.xml files and change its value to “DbTradeAlert A”

6. Add Flavor Specific Functionality

The withAds flavor’s only addition to the playStore flavor is a banner ad on the main screen which involves three tasks:

  1. Connnect an AdView control to AdMob
  2. Inject that control into the main screen
  3. Make the other flavors work without tying them to AdMob

6.1 Connnect an AdView Control to AdMob

To connnect an AdView control to AdMob you’ll have to specify an ad unit Id which looks like this: “ca-app-pub-3940256099942544/6300978111”. An ad unit was obtained in the previous sections and lets Google know whom to pay. It also allows you to track the source of your income if you have multiple adverts in an app.

But while I didn’t find any best practices it seems to be worthwhile to keep your ad unit Id secret because it contains your publisher Id – that’s the part before the “/”. Off the top of my head I can think of two misuses and I’m not even a security guy:

  • Someone could use your publisher Id in frivolous actions and get your account banned
  • With some social engineering they could also take over your account

For this reason my ad unit Id is not in the Git repository but loaded from an external file. Another twist regarding the ad unit Id is that you should only use test ads during development because tapping a real ad for test reasons can get your AdMob account suspended. Gradle takes care of both:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    defaultConfig {
        applicationId "de.dbremes.dbtradealert"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        archivesBaseName = "${parent.name}-${android.defaultConfig.versionName}"
        // Ad unit Id for sample ads from
        // https://firebase.google.com/docs/admob/android/google-services.json
        buildConfigField "String", "AD_UNIT_ID", "\"ca-app-pub-3940256099942544/6300978111\""
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            Properties props = new Properties()
            props.load(new FileInputStream("$project.rootDir/../../DbTradeAlert/project.properties"))
            buildConfigField "String", "AD_UNIT_ID", "\"${props.getProperty("ad_unit_id")}\""
        }
    }
    // ...
}

// ...

The defaultConfig defines a buildConfigField named “AD_UNIT_ID” providing an ad unit Id that was copied from one of Google’s samples and will only deliver test ads. Note that you have to generate quotes around its value because Gradle would otherwise read it as an integer type which results in an “integer number too large” error.

The release build type creates the same field which overrides the default one. The actual value is read from a file named “project.properties” which has just one line – with a proper ad unit Id of course:
ad_unit_id=ca-app-pub-1234567891234567/1234567891

Of course the publisher Id can still be extracted from the APK. But why make a crook’s life easier …

Now comes a second preparation step – create the AdHelper class. And make shure to put it in the right package …

Android Studio’s Project view will show two packages named “de.dbremes.dbtradealert (withAds)”. That’s because the previous section specified two source directories in build.gradle. The new AdHelper class needs to go in the empty package in the withAds directory.

package de.dbremes.dbtradealert;

import android.content.Context;
import android.view.View;

import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdSize;
import com.google.android.gms.ads.AdView;
import com.google.android.gms.ads.MobileAds;

public class AdHelper {

    public static View getAdView(Context context) {
        // Create AdView
        AdView adView = new AdView(context);
        adView.setAdSize(AdSize.BANNER);
        adView.setAdUnitId(BuildConfig.AD_UNIT_ID);
        // Load ad
        AdRequest adRequest = new AdRequest.Builder().build();
        adView.loadAd(adRequest);
        return adView;
    } // getAdView()

    public static void initialize(Context context) {
        MobileAds.initialize(context, BuildConfig.AD_UNIT_ID);
    } // initialize()
} // class AdHelper

The interesting method is getAdView() which prepares an AdView control by setting its size and ad unit Id which can now conveniently be accessed as BuildConfig.AD_UNIT_ID.

The AdView then loads an AdRequest and by calling Builder.addTestDevice() you can specify that you want to receive only test ads even if you use a real ad unit ID – we’ll come back to that later.

6.2 Inject the AdView Control into the Main Screen

To avoid adding AdMob dependencies to the main screen the AdView control is injected at runtime:

// ...

public class WatchlistListActivity extends AppCompatActivity
        implements WatchlistFragment.OnListFragmentInteractionListener {
    // ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_watchlist_list);
        // ...

        // Show banner ad in withAds flavor
        AdHelper.initialize(getApplicationContext());
        View adView = AdHelper.getAdView(getApplicationContext());
        if (adView != null) {
            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.MATCH_PARENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT);
            lp.gravity = Gravity.BOTTOM | Gravity.CENTER;
            adView.setLayoutParams(lp);
            LinearLayout ll = (LinearLayout) findViewById(R.id.main_linear_layout);
            ll.addView(adView);
        }
    } // onCreate()

    // ...
} // class WatchlistListActivity

After performing the existing initialization WatchlistListActivity.onCreate() now calls AdHelper.initialize() and AdHelper.getAdView(). In this case AdHelper.initialize() probably doesn’t help much to speed up showing the first ad because that is requested only split seconds afterwards.

The AdView control receives some layout information so it shows up at the bottom of the screen and finally it gets added to its parent view. I had to add this parent view to prevent the AdView from blocking the actual content.

That parent view is a LinearLayout with its orientation attribute set to vertical and it wraps the CoordinatorLayout which was originally the outermost control. The CoordinatorLayout also lost its layout_height which was set to match_parent and made it grab the whole screen. As a replacement it got a layout_weight attribute with a value of 1. That makes it still grab the screen as long as it’s alone but it will leave room to other controls when necessary.

Time to enjoy ads flying in your face. To test vertical scroll behavior it’s a good idea to add two more securities – I added CSGN.VX (Credit Suisse) and UBSG.VX (UBS Group) to the CH watchlist.

After bulding and deploying the app lines like those should show up in Android Studio’s logcat window:
... I/Ads: Starting ad request.
... I/Ads: Use AdRequest.Builder.addTestDevice("xyz") to get test ads on this device.
... E/Ads: JS: Uncaught ReferenceError: AFMA_ReceiveMessage is not defined (:1)
... I/Ads: Scheduling ad refresh 60000 milliseconds from now.
... I/Ads: Ad finished loading.
... I/Ads: Starting ad request.
... ...

The line with addTestDevice(“xyz”) reports a hash code for your device. Use that to request test ads if you use a real ad unit Id. Google confirmed the Uncaught ReferenceError is a bug and they are workng on it.

The test ad should show up at the bottom of the screen:

AdMob in action

AdMob in action

Note that I scrolled all the way up to see if the last report is still accessible. Title and toolbar look a bit messed up but that is actually a feature as that area moves out of the way to give more space to the actual content.

If the ad is somehow hidden you’ll see lines like this in Android Studio’s logcat window:
... de.dbremes.dbtradealert.withAds I/Ads: Ad is not visible. Not refreshing ad.
... de.dbremes.dbtradealert.withAds I/Ads: Scheduling ad refresh 60000 milliseconds from now.
... ...

6.3 Make the Other Flavors Work Without Tying them to AdMob

Like previously seen with the playStore flavor the existing non-withAds flavors need their own empty implementation of an AdHelper class. And ideally an interface or abstract base class would make shure those AdHelpers are implemented correctly.

But neither of them work with static methods. As I don’t like to change code for technical reasons I’ll leave it to the build server to point out diverging AdHelper implementations and just create two identical copies.

Let’s start with an AdHelper class for the playStore flavor:

  1. Add “src/playStore/java” to playStore.java.srcDirs in build.gradle and let Gradle sync
  2. Switch to the playStoreDebug variant
  3. In the app context menu select New | Folder | Java Folder
  4. In the Configure Component window select playStore as the Target Source Set and click Finish
  5. In the app context menu select New | Java Package
  6. In the Choose Destination Directory window select “..\app\src\playStore\java” and click OK
  7. In the New Package window enter “de.dbremes.dbtradealert” and click OK
  8. In the new package’s context menu select New | Java Class
  9. In the Create New Class window enter “AdHelper” and click OK

After that copy the contents of the existing AdHelper class into the new one and:

  1. Remove all “com.google.android.gms.ads” imports
  2. Remove the code in getAdView() and return null
  3. Remove the code in initialize()

Finally build the project to see if it likes the new AdHelper class.

Fixing the naked flavor is a lot easier. Just copy the new AdHelper.java to the respective directory, switch to the nakedDebug variant and Android Studio should pick up the file. Just remember to comment the line applying the Google services plugin out in build.gradle before starting the build.

Next post: more finishing touches (add Firebase Crash reporting, Firebase Remote Config, an About box, and perform Play Store optimization)

Additional Resources

Advertisements
This entry was posted in Uncategorized and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s