Skip to content

Instantly share code, notes, and snippets.

@thomasmso
Created March 23, 2020 20:54
Show Gist options
  • Save thomasmso/7bbbd4f64cb90e768b7db061d6b147c0 to your computer and use it in GitHub Desktop.
Save thomasmso/7bbbd4f64cb90e768b7db061d6b147c0 to your computer and use it in GitHub Desktop.
MAX Facebook Adapter - iOS
//
// MAFacebookMediationAdapter.m
// AppLovinSDK
//
// Created by Santosh Bagadi on 8/31/18.
// Copyright © 2019 AppLovin Corporation. All rights reserved.
//
#import "ALFacebookMediationAdapter.h"
#import <FBAudienceNetwork/FBAudienceNetwork.h>
#import "ALSdk.h"
#import "ALUtils.h"
#import "NSDictionary+ALUtils.h"
#import "NSString+ALUtils.h"
#import "ALAtomicBoolean.h"
#define ADAPTER_VERSION @"5.7.1.0"
#define MEDIATION_IDENTIFIER [NSString stringWithFormat: @"APPLOVIN_%@:%@", [ALSdk version], self.adapterVersion]
@interface ALFacebookMediationAdapterInterstitialAdDelegate : NSObject<FBInterstitialAdDelegate>
@property (nonatomic, weak) ALFacebookMediationAdapter *parentAdapter;
@property (nonatomic, strong) id<MAInterstitialAdapterDelegate> delegate;
- (instancetype)initWithParentAdapter:(ALFacebookMediationAdapter *)parentAdapter andNotify:(id<MAInterstitialAdapterDelegate>)delegate;
@end
@interface ALFacebookMediationAdapterRewardedVideoAdDelegate : NSObject<FBRewardedVideoAdDelegate>
@property (nonatomic, weak) ALFacebookMediationAdapter *parentAdapter;
@property (nonatomic, strong) id<MARewardedAdapterDelegate> delegate;
@property (nonatomic, assign, getter=hasGrantedReward) BOOL grantedReward;
- (instancetype)initWithParentAdapter:(ALFacebookMediationAdapter *)parentAdapter andNotify:(id<MARewardedAdapterDelegate>)delegate;
@end
@interface ALFacebookMediationAdapterAdViewDelegate : NSObject<FBAdViewDelegate>
@property (nonatomic, weak) ALFacebookMediationAdapter *parentAdapter;
@property (nonatomic, strong) id<MAAdViewAdapterDelegate> delegate;
- (instancetype)initWithParentAdapter:(ALFacebookMediationAdapter *)parentAdapter andNotify:(id<MAAdViewAdapterDelegate>)delegate;
@end
@interface ALFacebookMediationAdapter()
@property (nonatomic, strong) FBInterstitialAd *interstitialAd;
@property (nonatomic, strong) FBRewardedVideoAd *rewardedVideoAd;
@property (nonatomic, strong) FBAdView *adView;
@property (nonatomic, strong) ALFacebookMediationAdapterInterstitialAdDelegate *interstitialAdapterDelegate;
@property (nonatomic, strong) ALFacebookMediationAdapterRewardedVideoAdDelegate *rewardedAdapterDelegate;
@property (nonatomic, strong) ALFacebookMediationAdapterAdViewDelegate *adViewAdapterDelegate;
@end
@implementation ALFacebookMediationAdapter
static NSObject *ALFBAdSettingsLock;
static ALAtomicBoolean *ALFacebookSDKInitialized;
static MAAdapterInitializationStatus ALFacebookSDKInitializationStatus = NSIntegerMin;
#pragma mark - Class Initialization
+ (void)initialize
{
[super initialize];
ALFBAdSettingsLock = [[NSObject alloc] init];
ALFacebookSDKInitialized = [[ALAtomicBoolean alloc] init];
}
#pragma mark - MAAdapter Methods
- (void)initializeWithParameters:(id<MAAdapterInitializationParameters>)parameters withCompletionHandler:(void(^)(void))completionHandler
{
[self updateAdSettingsWithParameters: parameters];
// If initialized already
if ( [ALFacebookSDKInitialized compareAndSet: NO update: YES] )
{
NSArray<NSString *> *placementIDs = [parameters.serverParameters al_arrayForKey: @"placement_ids"];
void (^facebookCompletionHandler)(FBAdInitResults *results) = ^(FBAdInitResults *initResult) {
if ( [initResult isSuccess] )
{
[self log: @"Facebook SDK successfully finished initialization"];
}
else
{
[self log: @"Facebook SDK failed to finished initialization: %@", initResult.message];
}
completionHandler();
};
[self log: @"Initializing Facebook SDK with placements: %@", placementIDs];
FBAdInitSettings *initSettings = [[FBAdInitSettings alloc] initWithPlacementIDs: placementIDs mediationService: MEDIATION_IDENTIFIER];
[FBAudienceNetworkAds initializeWithSettings: initSettings completionHandler: facebookCompletionHandler];
}
else
{
[self log: @"Facebook attempted initialization already - marking initialization as completed"];
completionHandler();
}
}
- (void)initializeWithParameters:(id<MAAdapterInitializationParameters>)parameters completionHandler:(void (^)(MAAdapterInitializationStatus, NSString * _Nullable))completionHandler
{
[self updateAdSettingsWithParameters: parameters];
// If initialized already
if ( [ALFacebookSDKInitialized compareAndSet: NO update: YES] )
{
ALFacebookSDKInitializationStatus = MAAdapterInitializationStatusInitializing;
NSArray<NSString *> *placementIDs = [parameters.serverParameters al_arrayForKey: @"placement_ids"];
void (^facebookCompletionHandler)(FBAdInitResults *results) = ^(FBAdInitResults *initResult) {
if ( [initResult isSuccess] )
{
[self log: @"Facebook SDK successfully finished initialization: %@", initResult.message];
ALFacebookSDKInitializationStatus = MAAdapterInitializationStatusInitializedSuccess;
completionHandler(ALFacebookSDKInitializationStatus, nil);
}
else
{
[self log: @"Facebook SDK failed to finished initialization: %@", initResult.message];
ALFacebookSDKInitializationStatus = MAAdapterInitializationStatusInitializedFailure;
completionHandler(ALFacebookSDKInitializationStatus, initResult.message);
}
};
[self log: @"Initializing Facebook SDK with placements: %@", placementIDs];
FBAdInitSettings *initSettings = [[FBAdInitSettings alloc] initWithPlacementIDs: placementIDs mediationService: MEDIATION_IDENTIFIER];
[FBAudienceNetworkAds initializeWithSettings: initSettings completionHandler: facebookCompletionHandler];
}
else
{
[self log: @"Facebook attempted initialization already - marking initialization as %ld", ALFacebookSDKInitializationStatus];
completionHandler(ALFacebookSDKInitializationStatus, nil);
}
}
- (NSString *)SDKVersion
{
return FB_AD_SDK_VERSION;
}
- (NSString *)adapterVersion
{
return ADAPTER_VERSION;
}
- (void)destroy
{
// [self log: @"Destroy called for adapter %@", self ];
//
// self.interstitialAd.delegate = nil;
// self.interstitialAd = nil;
// self.interstitialAdapterDelegate = nil;
//
// self.rewardedVideoAd.delegate = nil;
// self.rewardedVideoAd = nil;
// self.rewardedAdapterDelegate = nil;
//
// self.adView.delegate = nil;
// self.adView = nil;
// self.adViewAdapterDelegate = nil;
}
#pragma mark - MAInterstitialAdapter Methods
- (void)loadInterstitialAdForParameters:(id<MAAdapterResponseParameters>)parameters andNotify:(id<MAInterstitialAdapterDelegate>)delegate
{
[self log: @"Loading interstitial ad: %@...", parameters.thirdPartyAdPlacementIdentifier];
[self updateAdSettingsWithParameters: parameters];
self.interstitialAd = [[FBInterstitialAd alloc] initWithPlacementID: parameters.thirdPartyAdPlacementIdentifier];
self.interstitialAdapterDelegate = [[ALFacebookMediationAdapterInterstitialAdDelegate alloc] initWithParentAdapter: self andNotify: delegate];
self.interstitialAd.delegate = self.interstitialAdapterDelegate;
NSString *bidResponse = parameters.bidResponse;
// If there is no bid response
if ( ![bidResponse al_isValidString] )
{
[self log: @"Loading mediated interstitial ad..."];
[self.interstitialAd loadAd];
}
// Otherwise (we have a bid response)
else
{
[self log: @"Loading bidding interstitial ad..."];
[self.interstitialAd loadAdWithBidPayload: bidResponse];
}
}
- (void)showInterstitialAdForParameters:(id<MAAdapterResponseParameters>)parameters andNotify:(id<MAInterstitialAdapterDelegate>)delegate
{
[self log: @"Showing interstitial: %@...", parameters.thirdPartyAdPlacementIdentifier];
// Check if ad is already expired or invalidated, and do not show ad if that is the case. You will not get paid to show an invalidated ad.
if ( [self.interstitialAd isAdValid] )
{
[self.interstitialAd showAdFromRootViewController: [ALUtils topViewControllerFromKeyWindow]];
}
else
{
[self log: @"Unable to show interstitial ad: ad is not valid - marking as expired"];
[delegate didFailToDisplayInterstitialAdWithError: MAAdapterError.adExpiredError];
}
}
#pragma mark - MARewardedAdapter Methods
- (void)loadRewardedAdForParameters:(id<MAAdapterResponseParameters>)parameters andNotify:(id<MARewardedAdapterDelegate>)delegate
{
[self log: @"Loading rewarded ad: %@...", parameters.thirdPartyAdPlacementIdentifier];
[self updateAdSettingsWithParameters: parameters];
self.rewardedVideoAd = [[FBRewardedVideoAd alloc] initWithPlacementID: parameters.thirdPartyAdPlacementIdentifier];
self.rewardedAdapterDelegate = [[ALFacebookMediationAdapterRewardedVideoAdDelegate alloc] initWithParentAdapter: self andNotify: delegate];
self.rewardedVideoAd.delegate = self.rewardedAdapterDelegate;
if ( [self.rewardedVideoAd isAdValid] )
{
[self log: @"A rewarded ad has been loaded already"];
[delegate didLoadRewardedAd];
}
else
{
NSString *bidResponse = parameters.bidResponse;
// If there is no bid response
if ( ![bidResponse al_isValidString] )
{
[self log: @"Loading mediated rewarded ad..."];
[self.rewardedVideoAd loadAd];
}
// Otherwise (we have a bid response)
else
{
[self log: @"Loading bidding rewarded ad..."];
[self.rewardedVideoAd loadAdWithBidPayload: bidResponse];
}
}
}
- (void)showRewardedAdForParameters:(id<MAAdapterResponseParameters>)parameters andNotify:(id<MARewardedAdapterDelegate>)delegate
{
[self log: @"Showing rewarded ad: %@...", parameters.thirdPartyAdPlacementIdentifier];
// Check if ad is already expired or invalidated, and do not show ad if that is the case. You will not get paid to show an invalidated ad.
if ( [self.rewardedVideoAd isAdValid] )
{
// Configure reward from server.
[self configureRewardForParameters: parameters];
[self.rewardedVideoAd showAdFromRootViewController: [ALUtils topViewControllerFromKeyWindow]];
}
else
{
[self log: @"Unable to show rewarded ad: ad is not valid - marking as expired"];
[delegate didFailToDisplayRewardedAdWithError: MAAdapterError.adExpiredError];
}
}
#pragma mark - MASignalProvider Methods
- (void)collectSignalWithParameters:(id<MASignalCollectionParameters>)parameters andNotify:(id<MASignalCollectionDelegate>)delegate
{
[self log: @"Collecting signal..."];
NSString *signal = FBAdSettings.bidderToken;
[delegate didCollectSignal: signal];
}
#pragma mark - MAAdViewAdapter Methods
- (void)loadAdViewAdForParameters:(id<MAAdapterResponseParameters>)parameters
adFormat:(MAAdFormat *)adFormat
andNotify:(id<MAAdViewAdapterDelegate>)delegate
{
[self log: @"Loading banner ad: %@...", parameters.thirdPartyAdPlacementIdentifier];
FBAdSize adSize = [self FBAdSizeFromAdFormat: adFormat];
self.adView = [[FBAdView alloc] initWithPlacementID: parameters.thirdPartyAdPlacementIdentifier
adSize: adSize
rootViewController: [ALUtils topViewControllerFromKeyWindow]];
self.adView.frame = CGRectMake(0, 0, adSize.size.width, adSize.size.height);
self.adViewAdapterDelegate = [[ALFacebookMediationAdapterAdViewDelegate alloc] initWithParentAdapter: self andNotify: delegate];
self.adView.delegate = self.adViewAdapterDelegate;
NSString *bidResponse = parameters.bidResponse;
// If there is no bid response
if ( ![bidResponse al_isValidString] )
{
[self log: @"Loading mediated banner ad..."];
[self.adView loadAd];
}
// Otherwise (we have a bid response)
else
{
[self log: @"Loading bidding banner ad..."];
[self.adView loadAdWithBidPayload: bidResponse];
}
}
#pragma mark - Shared Methods
- (void)updateAdSettingsWithParameters:(id<MAAdapterParameters>)parameters
{
// FBAdSettings is apparently not thread-safe on iOS - and may occassionally crash :/
@synchronized ( ALFBAdSettingsLock )
{
FBAdSettings.mixedAudience = [parameters isAgeRestrictedUser];
NSString *testDevicesString = parameters.serverParameters[@"test_device_ids"];
if ( [testDevicesString al_isValidString] )
{
NSArray<NSString *> *testDevices = [testDevicesString componentsSeparatedByString: @","];
[FBAdSettings addTestDevices: testDevices];
}
if ( [parameters isTesting] )
{
[FBAdSettings setLogLevel: FBAdLogLevelDebug];
}
[FBAdSettings setMediationService: MEDIATION_IDENTIFIER];
}
}
- (FBAdSize)FBAdSizeFromAdFormat:(MAAdFormat *)adFormat
{
if ( adFormat == MAAdFormat.banner )
{
return kFBAdSizeHeight50Banner;
}
else if ( adFormat == MAAdFormat.leader )
{
return kFBAdSizeHeight90Banner;
}
else if ( adFormat == MAAdFormat.mrec )
{
return kFBAdSizeHeight250Rectangle;
}
else
{
[NSException raise: NSInvalidArgumentException format: @"Invalid ad format: %@", adFormat];
return kFBAdSizeHeight50Banner;
}
}
+ (MAAdapterError *)maxErrorFromFacebookError:(NSError *)error
{
// From https://developers.facebook.com/docs/audience-network/testing/#errors
NSInteger errorCode;
NSInteger facebookCode = error.code;
if ( facebookCode == 1000 ) // Network Error
{
errorCode = MAAdapterError.errorCodeNoConnection; // -5003
}
else if ( facebookCode == 1001 ) // No Fill
{
errorCode = MAAdapterError.errorCodeNoFill; // 204
}
else if ( facebookCode == 1002 ) // Ad Load Too Frequently
{
errorCode = MAAdapterError.errorCodeInvalidLoadState;
}
else if ( facebookCode == 1011 ) // Display Format Mismatch
{
errorCode = MAAdapterError.errorCodeInvalidConfiguration; // -5202
}
else if ( facebookCode == 2000 ) // Server Error
{
errorCode = MAAdapterError.errorCodeServerError; // -5206
}
else if ( facebookCode == 2001 ) // Internal Error - actually a timeout error
{
errorCode = MAAdapterError.errorCodeTimeout; // -5206 (to be changed)
}
else
{
errorCode = MAAdapterError.errorCodeUnspecified; // -5200
}
return [MAAdapterError errorWithCode: errorCode adapterErrorCode: facebookCode];
}
@end
#pragma mark - ALFacebookMediationAdapterInterstitialAdDelegate
@implementation ALFacebookMediationAdapterInterstitialAdDelegate
- (instancetype)initWithParentAdapter:(ALFacebookMediationAdapter *)parentAdapter andNotify:(id<MAInterstitialAdapterDelegate>)delegate
{
self = [super init];
if ( self )
{
self.parentAdapter = parentAdapter;
self.delegate = delegate;
}
return self;
}
- (void)interstitialAdDidLoad:(FBInterstitialAd *)interstitialAd
{
[self.parentAdapter log: @"Interstitial ad loaded: %@", interstitialAd.placementID];
[self.delegate didLoadInterstitialAd];
}
- (void)interstitialAd:(FBInterstitialAd *)interstitialAd didFailWithError:(NSError *)error
{
[self.parentAdapter log: @"Interstitial ad (%@) failed to load with error: %@", interstitialAd.placementID, error];
MAAdapterError *adapterError = [ALFacebookMediationAdapter maxErrorFromFacebookError: error];
[self.delegate didFailToLoadInterstitialAdWithError: adapterError];
}
- (void)interstitialAdWillLogImpression:(FBInterstitialAd *)interstitialAd
{
[self.parentAdapter log: @"Interstitial ad shown: %@", interstitialAd.placementID];
[self.delegate didDisplayInterstitialAd];
}
- (void)interstitialAdDidClick:(FBInterstitialAd *)interstitialAd
{
[self.parentAdapter log: @"Interstitial ad clicked: %@", interstitialAd.placementID];
[self.delegate didClickInterstitialAd];
}
- (void)interstitialAdDidClose:(FBInterstitialAd *)interstitialAd
{
[self.parentAdapter log: @"Interstitial ad hidden: %@", interstitialAd.placementID];
[self.delegate didHideInterstitialAd];
}
@end
#pragma mark - ALFacebookMediationAdapterRewardedVideoAdDelegate
@implementation ALFacebookMediationAdapterRewardedVideoAdDelegate
- (instancetype)initWithParentAdapter:(ALFacebookMediationAdapter *)parentAdapter andNotify:(id<MARewardedAdapterDelegate>)delegate
{
self = [super init];
if ( self )
{
self.parentAdapter = parentAdapter;
self.delegate = delegate;
}
return self;
}
- (void)rewardedVideoAdDidLoad:(FBRewardedVideoAd *)rewardedVideoAd
{
[self.parentAdapter log: @"Rewarded ad loaded: %@", rewardedVideoAd.placementID];
[self.delegate didLoadRewardedAd];
}
- (void)rewardedVideoAd:(FBRewardedVideoAd *)rewardedVideoAd didFailWithError:(NSError *)error
{
[self.parentAdapter log: @"Rewarded ad (%@) failed to load with error: %@", rewardedVideoAd.placementID, error];
MAAdapterError *adapterError = [ALFacebookMediationAdapter maxErrorFromFacebookError: error];
[self.delegate didFailToLoadRewardedAdWithError: adapterError];
}
- (void)rewardedVideoAdDidClose:(FBRewardedVideoAd *)rewardedVideoAd
{
if ( [self hasGrantedReward] || [self.parentAdapter shouldAlwaysRewardUser] )
{
MAReward *reward = [self.parentAdapter reward];
[self.parentAdapter log: @"Rewarded user with reward: %@", reward];
[self.delegate didRewardUserWithReward: reward];
}
[self.parentAdapter log: @"Rewarded ad hidden: %@", rewardedVideoAd.placementID];
[self.delegate didHideRewardedAd];
}
- (void)rewardedVideoAdDidClick:(FBRewardedVideoAd *)rewardedVideoAd
{
[self.parentAdapter log: @"Rewarded ad clicked: %@", rewardedVideoAd.placementID];
[self.delegate didClickRewardedAd];
}
- (void)rewardedVideoAdVideoComplete:(FBRewardedVideoAd *)rewardedVideoAd
{
[self.parentAdapter log: @"Rewarded video completed: %@", rewardedVideoAd.placementID];
[self.delegate didCompleteRewardedAdVideo];
self.grantedReward = YES;
}
- (void)rewardedVideoAdWillLogImpression:(FBRewardedVideoAd *)rewardedVideoAd
{
[self.parentAdapter log: @"Rewarded video started: %@", rewardedVideoAd.placementID];
[self.delegate didDisplayRewardedAd];
[self.delegate didStartRewardedAdVideo];
}
@end
#pragma mark - ALFacebookMediationAdapterAdViewDelegate
@implementation ALFacebookMediationAdapterAdViewDelegate
- (instancetype)initWithParentAdapter:(ALFacebookMediationAdapter *)parentAdapter andNotify:(id<MAAdViewAdapterDelegate>)delegate
{
self = [super init];
if ( self )
{
self.parentAdapter = parentAdapter;
self.delegate = delegate;
}
return self;
}
- (void)adViewDidLoad:(FBAdView *)adView
{
[self.parentAdapter log: @"Banner loaded: %@", adView.placementID];
[self.delegate didLoadAdForAdView: adView];
}
- (void)adView:(FBAdView *)adView didFailWithError:(NSError *)error
{
[self.parentAdapter log: @"Banner (%@) failed to load with error: %@", adView.placementID, error];
MAAdapterError *adapterError = [ALFacebookMediationAdapter maxErrorFromFacebookError: error];
[self.delegate didFailToLoadAdViewAdWithError: adapterError];
}
- (void)adViewWillLogImpression:(FBAdView *)adView
{
[self.parentAdapter log: @"Banner shown: %@", adView.placementID];
[self.delegate didDisplayAdViewAd];
}
- (void)adViewDidClick:(FBAdView *)adView
{
[self.parentAdapter log: @"Banner clicked: %@", adView.placementID];
[self.delegate didClickAdViewAd];
}
- (void)adViewDidFinishHandlingClick:(FBAdView *)adView
{
[self.parentAdapter log: @"Banner finished handling click: %@", adView.placementID];
[self.delegate didCollapseAdViewAd];
}
- (UIViewController *)viewControllerForPresentingModalView
{
return [ALUtils topViewControllerFromKeyWindow];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment