JUCE + Firebase/AdMob: Lets monetize our JUCE iOS apps with Ads (partial How-To)


#1

I would like to open the discussion of added Ads via Google Mobile Ads/Firebase Admob to JUCE. I’ll start by explaining how I got it to “work” on my end, getting around the issue of Google wanting us to use CocoaPods to install/configure Firebase-AdMob with our xcode projects. So, without further adieu…


After downloading the SDK from the Google Firebase website, and creating a generic JUCE GUI application for iOS:
I was able to get it working/compiling by doing the following:

  1. I had to include the following Apple frameworks manually in ProJucer:
CoreMotion, 
MediaPlayer, 
AdSupport, 
MessageUI, 
MobileCoreServices, 
CoreTelephony, 
Security, 
StoreKit, 
SystemConfiguration, 
UIKit, 
Foundation, 
CoreMedia, 
GLKit, 
  1. then the Firebase core frameworks:
/Volumes/Thunderbay/MyBook2/Programming/Firebase/Analytics/FirebaseAnalytics, 
/Volumes/Thunderbay/MyBook2/Programming/Firebase/Analytics/FirebaseCore, 
/Volumes/Thunderbay/MyBook2/Programming/Firebase/Analytics/FirebaseCoreDiagnostics, 
/Volumes/Thunderbay/MyBook2/Programming/Firebase/Analytics/FirebaseInstanceID, 
/Volumes/Thunderbay/MyBook2/Programming/Firebase/Analytics/GoogleAppMeasurement, 
/Volumes/Thunderbay/MyBook2/Programming/Firebase/Analytics/GoogleUtilities, 
/Volumes/Thunderbay/MyBook2/Programming/Firebase/Analytics/MeasurementNanoPB, 
/Volumes/Thunderbay/MyBook2/Programming/Firebase/Analytics/nanopb,
  1. and finally, the AdMob framework:
/Volumes/Thunderbay/MyBook2/Programming/Firebase/AdMob/GoogleMobileAds, 

All of these are added to the Extra Frameworks field in Projucer.

  1. All of the frameworks that get used need to be included as a single string, separated via spaces, inside quotes as the argument to FRAMEWORK_SEARCH_PATHS="/path/to/A /path/to/B"
    under Custom Xcode Flags in ProJucer add:
FRAMEWORK_SEARCH_PATHS="/Volumes/Thunderbay/MyBook2/Programming/Firebase/Analytics /Volumes/Thunderbay/MyBook2/Programming/Firebase/Admob"
  1. These are the projucer Extra Linker Flags that are required:
$(OTHER_LDFLAGS) -ObjC -lsqlite3 -lz
  1. the Header search paths and Extra library search paths:
/Volumes/Thunderbay/MyBook2/Programming/Firebase/Analytics;
/Volumes/Thunderbay/MyBook2/Programming/Firebase/AdMob;

both fields get the same paths.

  1. Finally:
    remove Main.cpp as a compile target in Projucer. Add a file that ends in .mm to Projucer and set that as the Compile target.
    Inside that .mm file, add this:
#pragma once
#import "Firebase.h"

#include "Main.cpp"

Firebase.h needs to be added to ProJucer via “Add Existing File”. it’s in the main Firebase Directory.

  1. now, inside initialise() in Main.cpp, and every other file in the project (because they all end up being included inside Main.cpp) you can write Objective C.
#include "../JuceLibraryCode/JuceHeader.h"
#include "MainComponent.h"

//Google's test ID for admob
NSString *adID = @"ca-app-pub-3940256099942544/4411468910";

//==============================================================================
class FirebaseAdMobApplication  : public JUCEApplication
{
public:
    //==============================================================================
    FirebaseAdMobApplication() {}

    const String getApplicationName() override       { return ProjectInfo::projectName; }
    const String getApplicationVersion() override    { return ProjectInfo::versionString; }
    bool moreThanOneInstanceAllowed() override       { return true; }
//==============================================================================
    void initialise (const String& commandLine) override
    {
        // This method is where you should put your application's initialisation code..

        [GADMobileAds configureWithApplicationID:adID];   
        mainWindow = new MainWindow (getApplicationName());
    }

and you’re off and running!


I think JUCE for IOS and Android would be way more popular as a development platform if it was much easier to monetize via Ads for the developers. I’ve been asking a lot of questions in the IOS Developers slack server, and although there are ~20K iOS developers on that server, practically none of them have heard of JUCE for iOS development.
So, let’s get the discussion going, and see if we can bring some ad revenue to the iOS side of JUCE for all the folks writing cool AUv3 plugins and iOS Audio Apps!


#2

Have a look at UIViewComponent for displaying native views. The BluetoothMidiSelectorOverlay provides an example where JUCE does this.


#3

Alright, I was able to get ads appearing in my little demo project and here’s how:

  1. add 2 files to your project that was created in the original post:
AdView.mm
AdView.h

in AdView.h we will define the UIViewController instance that displays the ad. We’ll also define the JUCE component that will hold the UIViewController instance:

first include the necessary things:

//for UIViewController
#import <Foundation/Foundation.h>
//for GADBannerViewDelegate
#import "Firebase.h"
//for Component
#include "../JuceLibraryCode/JuceHeader.h"

now define the BannerViewController just like in the Google Admob guide:

@interface BannerViewController : UIViewController <GADBannerViewDelegate>

@property(nonatomic, strong) GADBannerView* bannerView;
//UIViewController functions we're "overriding"
-(void)viewDidLoad;

//GADBannerViewDelegate functions we're "overriding"
- (void)adViewDidReceiveAd:(GADBannerView *)bannerView;
- (void)adView:(GADBannerView *)bannerView didFailToReceiveAdWithError:(GADRequestError *)error;
- (void)adViewWillPresentScreen:(GADBannerView *)bannerView;
- (void)adViewWillDismissScreen:(GADBannerView *)bannerView;
- (void)adViewDidDismissScreen:(GADBannerView *)bannerView;
- (void)adViewWillLeaveApplication:(GADBannerView *)bannerView;

- (void)addBannerViewToView:(UIView *)bannerView;
@end

if you want to know what those GADBannerViewDelegate functions do, just command-click on GADBannerViewDelegate to be taken to that Protocol file which has the descriptions.

Now define the JUCE component that’ll hold the BannerViewController instance:

struct BannerAdView : public Component
{
    BannerViewController* bannerViewController;
    UIViewComponent uiViewComponent;
    
    BannerAdView();
    ~BannerAdView();
    void resized() override;
    void paint(Graphics& g) override;
};

Now in AdView.mm. first the implementation of the BannerViewController:

#include "AdView.h"

//the testBannerID Google provides during app development
NSString* testBannerID = @"ca-app-pub-3940256099942544/2934735716";

@implementation BannerViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // In this case, we instantiate the banner with desired ad size.
    self.bannerView = [[GADBannerView alloc]
                                 initWithAdSize:kGADAdSizeSmartBannerLandscape];
    
    [self addBannerViewToView:self.bannerView];
    self.bannerView.adUnitID = testBannerID;
    self.bannerView.rootViewController = self;
    [self.bannerView loadRequest:[GADRequest request]];
}

- (void)addBannerViewToView:(UIView *)bannerView
{
    bannerView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:bannerView];
}

- (void)adViewDidReceiveAd:(GADBannerView *)adView {
    // Add adView to view and add constraints as above.
    DBG( "adViewDidReceiveAd()");
    [self addBannerViewToView:self.bannerView];
}

- (void)adView:(GADBannerView *)adView
didFailToReceiveAdWithError:(GADRequestError *)error
{
    DBG( "adView:didFailToReceiveAdWithError: " << [[error localizedDescription]cStringUsingEncoding:NSUTF8StringEncoding] );
}

- (void)adViewWillPresentScreen:(GADBannerView *)adView
{
    DBG( "adViewWillPresentScreen()" );
}

- (void)adViewWillDismissScreen:(GADBannerView *)adView
{
    DBG("adViewWillDismissScreen");
}

- (void)adViewDidDismissScreen:(GADBannerView *)adView
{
    DBG("adViewDidDismissScreen");
}

- (void)adViewWillLeaveApplication:(GADBannerView *)adView
{
    DBG("adViewWillLeaveApplication");
}

@end

Again, all of this is basically taken from the Admob Banner Ad guide
After that, add the BannerAdView juce component implementation:

//the JUCE component that holds our ViewController instance
BannerAdView::BannerAdView()
{
    bannerViewController = [[BannerViewController alloc] init];
    uiViewComponent.setView([bannerViewController view]);
    
    addAndMakeVisible(uiViewComponent);
    auto area = Desktop::getInstance().getDisplays().getMainDisplay().totalArea;
    DBG( "ios area: " << area.toString() );
    setSize(area.getWidth(), area.getHeight());
}

BannerAdView::~BannerAdView()
{
    [bannerViewController release];
}
void BannerAdView::resized()
{
    uiViewComponent.setBounds(getLocalBounds());
}

void BannerAdView::paint(Graphics& g)
{
    g.fillAll(Colours::green);
}

Finally, in Main.cpp include AdView.h and change this line:
setContentOwned (new MainContentComponent(), true);
to this:
setContentOwned(new BannerAdView(), true);

Tadah:


Admob in a juce app for iOS.


#4

If any more advanced users of JUCE who want to take a crack at getting Firebase working with the Firebase C++ SDK, here is the link to the SDK:
https://firebase.google.com/docs/cpp/setup

I didn’t have any success, but would love to learn what tricks are necessary to get it working!
I could never get past the _OBJC_CLASS_$_ linker errors when using the C++ SDK, so I stuck with the ObjC SDK.

the C++ SDK would be ideal to use as your JUCE IOS apps could be exported directly to Android.


#5

Progress update! I got the C++ SDK working.
Solution:

Create your juce project exactly as in the first post, then, download the C++ SDK linked in the previous post. Add the firebase.framework and firebase_admob.framework frameworks to the Extra Frameworks field in ProJucer.

add the folder holding the frameworks to the FRAMEWORK_SEARCH_PATHS list, after the original SDK framework. i.e.:

FRAMEWORK_SEARCH_PATHS="/Users/me/Firebase/Analytics 
/Users/me/Firebase/AdMob 
/Users/me/firebase_cpp_sdk/frameworks/ios/universal"

under `Custom plist, you’ll need to enable connections to http links:

<plist version="1.0">
  <dict>
  <key>NSAppTransportSecurity</key>
  <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
  </dict>
    </dict>
</plist>

change the .mm file in Projucer to no longer be a compile target. set Main.cpp to be a compile target. That’s right, you don’t need to do any Objective-C++ bridging anymore, unlike in the earlier post! it’s pure C++ the whole time!

Save and Open in IDE.
change initialise() to do what they say to do in the C++ SDK setup:

#include "firebase/admob.h"
#include "firebase/admob/types.h"
#include "firebase/app.h"
#include "firebase/future.h"

and place this in initialise()

// Create the Firebase app.
firebase::App* app = firebase::App::Create(firebase::AppOptions());
// Initialize the AdMob library with your AdMob app ID.
const char* kAdMobAppID = "ca-app-pub-XXXXXXXXXXXXXXXX~NNNNNNNNNN";
firebase::admob::Initialize(*app, kAdMobAppID);

Compile and run. your app is initialized to use Admob now. You should be able to follow the tutorial for Banner Ads and Interstitial Ads here now:
https://firebase.google.com/docs/admob/cpp/quick-start?authuser=0#interact_with_the_google_mobile_ads_sdk

With that said, this also opens the door for Firebase via the C++ SDK. just change with framework from the C++ SDK you add, based on the directions on the Firebase C++ page.

Here’s what my initialise() and MainContentComponent.cpp looked like:

#include "../JuceLibraryCode/JuceHeader.h"
#include "firebase/app.h"
#include "firebase/admob.h"
//...snip

    void initialise (const String& commandLine) override
    {
        // This method is where you should put your application's initialisation code..
        firebase::App* app = firebase::App::Create(firebase::AppOptions());

        StringRef googleTestAdID = "ca-app-pub-3940256099942544~1458002511";
        firebase::admob::Initialize(*app, googleTestAdID);
        
        mainWindow = new MainWindow (getApplicationName());
    }

MainContentComponent.h :

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"
#include "firebase/future.h"
#include "firebase/admob/banner_view.h"

class MainContentComponent   : public Component, public Timer
{
public:
    MainContentComponent();
    ~MainContentComponent();

    void paint (Graphics&) override;
    void resized() override;
    void timerCallback() override;
    static void initFutureCallback(const firebase::Future<void>& resultData,
                                           void* userData);
    static void showFutureCallback(const firebase::Future<void>& resultData, void* userData );
private:
    std::unique_ptr<firebase::admob::BannerView> bannerView;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

and MainContentComponent.cpp:

#include "MainComponent.h"
#include "firebase/admob/types.h"

MainContentComponent::MainContentComponent()
{
    auto area  = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
    setSize(area.getWidth(), area.getHeight());
    startTimer(1000);
}

void MainContentComponent::timerCallback()
{
    stopTimer();
    bannerView = std::make_unique<firebase::admob::BannerView>();
    firebase::admob::AdSize adSize;
    adSize.ad_size_type = firebase::admob::kAdSizeStandard;
    adSize.width = getWidth();
    adSize.height = 50;


    StringRef googleTestBannerID = "ca-app-pub-3940256099942544/2934735716";
    bannerView->Initialize(static_cast<firebase::admob::AdParent>(getTopLevelComponent()->getWindowHandle()),
                           googleTestBannerID,
                           adSize);

    bannerView->InitializeLastResult().OnCompletion(MainContentComponent::initFutureCallback, bannerView.get());
}

void MainContentComponent::initFutureCallback(const firebase::Future<void> &resultData, void *userData)
{
    DBG( "MainContentComponent::initFutureCallback" );
    switch (resultData.status())
    {
        case firebase::FutureStatus::kFutureStatusComplete:
        {
            if( auto* bannerView = static_cast<firebase::admob::BannerView*>(userData) )
            {
                auto showFuture = bannerView->Show();
                showFuture.OnCompletion(MainContentComponent::showFutureCallback, bannerView);
            }
            break;
        }
        case firebase::FutureStatus::kFutureStatusPending:
        {
            DBG( "Init() still pending!" );
            jassertfalse;
            break;
        }
        case firebase::FutureStatus::kFutureStatusInvalid:
        {
            DBG( "invalid!" );
            jassertfalse;
            break;
        }
    }
}
void MainContentComponent::showFutureCallback(const firebase::Future<void> &resultData, void *userData)
{
    DBG( "MainContentComponent::showFutureCallback()" );
    switch (resultData.status())
    {
        case firebase::FutureStatus::kFutureStatusComplete:
        {
            if( auto* bannerView = static_cast<firebase::admob::BannerView*>(userData) )
            {
                firebase::admob::AdRequest request = {};
                request.gender = firebase::admob::kGenderUnknown;

                static const char* adKeywords[] = {"AdMob", "C++", "Fun"};
                request.keyword_count = sizeof(adKeywords) / sizeof(adKeywords[0]);
                request.keywords = adKeywords;

                static const char* testIds[] = {"kGADSimulatorID"};
                request.test_device_ids = testIds;
                request.test_device_id_count = sizeof(testIds) / sizeof(testIds[0]);;

                bannerView->LoadAd(request);
            }
            else
            {
                jassertfalse;
            }
            break;
        }
        case firebase::FutureStatus::kFutureStatusPending:
        {
            DBG( "Show() still pending" );
            jassertfalse;
            break;
        }
        case firebase::FutureStatus::kFutureStatusInvalid:
        {
            DBG( "invalid!" );
            jassertfalse;
            break;
        }
    }
}

MainContentComponent::~MainContentComponent()
{
    if( bannerView.get() )
    {
        bannerView->Destroy();
    }
}