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

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!

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

1 Like

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.

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.

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();
    }
}
4 Likes

Very cool!
Would this work for a Windows Desktop Application as well?
Otherwise, does anyone have an idea how one would go about implementing ads into a Windows Desktop Application?
Thanks!

I’m pretty sure that violates the AdMob Terms and Conditions…

I hope this is not drifting too far off topic, but how about running a webrowser component to display ads?

That is how I imagined one would approach this. Curious if others implement it this way…

Hello. I am trying to build JUCE with Firebase. I am able to get the frameworks (cocoapods or from binaries), however, I don’t know how to use the cpp headers in the iOS project. In fact, I can’t find them. Can you give me some directions?

@matkatmusic Thanks a lot for tutorial! Both your posts on forum and your Youtube AdMob tutorials were super useful.
Some things changed with Firebase since time of posting. So, I put up sample project with updated instructions how to setup Juce iOS app with Firebase C++ SDK. I’m interested in Firebase Analytics and Crash Reporting, so github project shows that. But should be trivial to include more Firebase modules if needed.

I also had to change Projucer’s Source code to no longer force Xcode to use legacy build system and recognize .xcframeworks.
I would love this to be included to Juce. Juce team, can get it addressed in the future?

2 Likes

Thanks for all this info. I’m trying to do this myself but I can’t even get Xcode to locate the xc frameworks.

I’m currently on Xcode 13.2 and it states that 13.3.1 is required on firebase website, but I think that may be more to do with if you’re using cocoapods. Am I right in thinking this or could that be the cause of my problems?

When I try to build in Xcode I get a linker error saying “could not locate framework firebaseAnalytics.xcframework” (the only one I’m trying to add while I’m experiencing problems) Anyone got any clues? I’ve followed this thread to the letter, including the further info provided by dikadk13, to no avail.

Thanks

1 Like

I’ve updated Xcode to the v14 beta. Same issue. The way I’ve finally got these .xcframeworks to be detected by Xcode is to add them to the embedded frameworks in Projucer, instead of custom frameworks.

I now have a problem with a dependancy cycle which I’m trying to work through. It’s quite a lot of hassle getting these ads incorporated, I must say. Is there any plans for Juce to simplify the process in any way? I thought this would have been pretty common practice but there isn’t much info out there for Juce/C++/Xcode and in app ads.

1 Like

I’m still having issues with this. I can now get the project to build successfully (by adding frameworks to embedded section in Projucer) but I am then getting this issue. I have followed the folder structure and it goes as far as com.apple.mobile.installd.staging, but that folder is empty. I’m at a loss here, anyone have any pointers? Thanks

1 Like