Example optimisation code
Load Ad in sequence
Below is example code (not production-ready) showing the integration of dynamic floors in MAX.
- Native iOS: https://github.com/Nefta-io/NeftaMAXAdapter/blob/main/MaxIntegration/Rewarded.swift
- Unity: https://github.com/Nefta-io/NeftaMAXAdapter-Unity/blob/main/Assets/AdDemo/Rewarded.cs
// Wrapper for Ads with bid floor selection logic
class Rewarded : NSObject, MARewardedAdDelegate {
private let _defaultAdUnitId = "918ac...9c034"
static let InsightAdUnitId = "recommended_rewarded_ad_unit_id"
static let InsightFloorPrice = "calculated_user_floor_price_rewarded"
private var RequestNewInsights: () -> Void
private var _selectedAdUnitId: String?
private var _recommendedAdUnitId: String?
private var _calculatedBidFloor: Double = 0.0
private var _consecutiveAdFail = 0
private var _isLoadPending = false
private let _loadButton: UIButton
private let _showButton: UIButton
private let _status: UILabel
var _rewarded: MARewardedAd!
func OnBehaviourInsight(insights: [String: Insight]) {
_recommendedAdUnitId = insights[Rewarded.InsightAdUnitId]?._string
_calculatedBidFloor = insights[Rewarded.InsightFloorPrice]?._float ?? 0.0
print("OnBehaviourInsight for Rewarded recommended AdUnit: \(String(describing: _recommendedAdUnitId))/cpm:\(_calculatedBidFloor)")
_selectedAdUnitId = _recommendedAdUnitId
if _isLoadPending {
Load()
}
}
func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) {
ALNeftaMediationAdapter.onExternalMediationRequestFail(.rewarded, recommendedAdUnitId: _recommendedAdUnitId, calculatedFloorPrice: _calculatedBidFloor, adUnitIdentifier: adUnitIdentifier, error: error)
if error.code == .noFill {
_consecutiveAdFail += 1
if _consecutiveAdFail == 1 { // in case of first no fill, try to get new insight (will probably return adUnit with lower bid floor
_isLoadPending = true
RequestNewInsights()
} else { // for consequential no fills go with default (no bid floor) ad unit
_selectedAdUnitId = nil
Load()
}
}
SetInfo("didFailToLoadAd \(adUnitIdentifier): \(error)")
}
func didLoad(_ ad: MAAd) {
ALNeftaMediationAdapter.onExternalMediationRequestLoad(.rewarded, recommendedAdUnitId: _recommendedAdUnitId, calculatedFloorPrice: _calculatedBidFloor, ad: ad)
_consecutiveAdFail = 0
// Optionally request new insights on ad load, in case ad unit with higher bid floor gets recommended
// SelectAdUnitFromInsights()
SetInfo("didLoad \(ad)")
_showButton.isEnabled = true
}
init(requestNewInsights: @escaping (() -> Void), loadButton: UIButton, showButton: UIButton, status: UILabel) {
RequestNewInsights = requestNewInsights
_loadButton = loadButton
_showButton = showButton
_status = status
super.init()
_loadButton.addTarget(self, action: #selector(Load), for: .touchUpInside)
_showButton.addTarget(self, action: #selector(Show), for: .touchUpInside)
_showButton.isEnabled = false
}
@objc func Load() {
_rewarded = MARewardedAd.shared(withAdUnitIdentifier: _selectedAdUnitId ?? _defaultAdUnitId)
_rewarded.delegate = self
_rewarded.load()
}
@objc func Show() {
_rewarded.show()
_showButton.isEnabled = false
}
func didDisplay(_ ad: MAAd) {
SetInfo("didDisplay \(ad)")
ALNeftaMediationAdapter.onExternalMediationImpression(ad)
}
}
// Main application entry point:
override func viewDidLoad() {
super.viewDidLoad()
NeftaPlugin.EnableLogging(enable: true)
_plugin = NeftaPlugin.Init(appId: "5661184053215232")
_plugin.OnBehaviourInsight = OnBehaviourInsight
GetBehaviourInsights()
_title.text = "Nefta Adapter for MAX"
_banner = Banner(requestNewInsights: GetBehaviourInsights,
showButton: _showBanner, hideButton: _hideBanner, status: _bannerStatus, bannerPlaceholder: _bannerPlaceholder)
_interstitial = Interstitial(requestNewInsights: GetBehaviourInsights,
loadButton: _loadInterstitial, showButton: _showInterstitial, status: _interstitialStatus)
_rewardedVideo = Rewarded(requestNewInsights: GetBehaviourInsights,
loadButton: _loadRewarded, showButton: _showRewarded, status: _rewardedStatus)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.checkTrackingAndInitializeMax()
}
}
private func GetBehaviourInsights() {
_plugin.GetBehaviourInsight([
Banner.InsightFloorPrice,
Interstitial.InsightAdUnitId, Interstitial.InsightFloorPrice,
Rewarded.InsightAdUnitId, Rewarded.InsightFloorPrice
])
}
private func OnBehaviourInsight(insights: [String: Insight]) {
_banner.OnBehaviourInsight(insights: insights)
_interstitial.OnBehaviourInsight(insights: insights)
_rewardedVideo.OnBehaviourInsight(insights: insights)
}
// the rest of your class
}
// Wrapper for Ads with bid floor selection logic
public class Rewarded
{
#if UNITY_IOS
private const string _defaultAdUnitId = "08304643cb16df3b";
#else // UNITY_ANDROID
private const string _defaultAdUnitId = "3082ee9199cf59f0";
#endif
public static readonly string AdUnitId = "recommended_rewarded_ad_unit_id";
public static readonly string FloorPrice = "calculated_user_floor_price_rewarded";
private Action GetInsights;
private string _selectedAdUnitId;
private string _recommendedAdUnitId;
private double _calculatedBidFloor;
private int _consecutiveAdFail;
private bool _isLoadPending;
private string _loadedAdUnitId;
private readonly Action<string> _setStatus;
private readonly Action _onLoad;
public void OnBehaviourInsight(Dictionary<string, Insight> insights)
{
_recommendedAdUnitId = insights[AdUnitId]._string;
_calculatedBidFloor = insights[FloorPrice]._float;
Debug.Log($"OnBehaviourInsight for Rewarded recommended AdUnit: {_recommendedAdUnitId}, calculated bid floor: {_calculatedBidFloor}");
_selectedAdUnitId = _recommendedAdUnitId;
if (_isLoadPending)
{
Load();
}
}
public Rewarded(Action requestNewInsight, Action<string> setStatus, Action onLoad)
{
GetInsights = requestNewInsight;
_setStatus = setStatus;
_onLoad = onLoad;
MaxSdkCallbacks.Rewarded.OnAdLoadedEvent += OnAdLoadedEvent;
MaxSdkCallbacks.Rewarded.OnAdLoadFailedEvent += OnAdFailedEvent;
MaxSdkCallbacks.Rewarded.OnAdDisplayedEvent += OnAdDisplayedEvent;
}
public void Load()
{
_loadedAdUnitId = _selectedAdUnitId ?? _defaultAdUnitId;
MaxSdk.LoadRewardedAd(_loadedAdUnitId);
}
public void Show()
{
if (MaxSdk.IsRewardedAdReady(_loadedAdUnitId))
{
_setStatus("Showing");
MaxSdk.ShowRewardedAd(_loadedAdUnitId);
}
else
{
_setStatus("Ad not ready");
}
}
private void OnAdLoadedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
{
NeftaAdapterEvents.OnExternalMediationRequestLoaded(NeftaAdapterEvents.AdType.Rewarded, _recommendedAdUnitId, _calculatedBidFloor, adInfo);
_setStatus($"Loaded {adInfo.NetworkName} {adInfo.NetworkPlacement}");
_onLoad();
}
private void OnAdFailedEvent(string adUnitId, MaxSdkBase.ErrorInfo errorInfo)
{
NeftaAdapterEvents.OnExternalMediationRequestFailed(NeftaAdapterEvents.AdType.Rewarded, _recommendedAdUnitId, _calculatedBidFloor, adUnitId, errorInfo);
if (errorInfo.Code == MaxSdkBase.ErrorCode.NoFill)
{
_consecutiveAdFail++;
if (_consecutiveAdFail == 1) // in case of first no fill, try to get new insight (will probably return adUnit with lower bid floor
{
_isLoadPending = true;
GetInsights();
}
else // for consequential no fills go with default (no bid floor) ad unit
{
_selectedAdUnitId = null;
Load();
}
}
_setStatus("Load failed");
}
}
// Example usage from application entry point
public class AdDemoController : MonoBehaviour
{
private void Awake()
{
NeftaAdapterEvents.EnableLogging(true);
NeftaAdapterEvents.Init(NeftaId);
NeftaAdapterEvents.SetContentRating(NeftaAdapterEvents.ContentRating.MatureAudience);
NeftaAdapterEvents.BehaviourInsightCallback = OnBehaviourInsight;
GetBehaviourInsight();
// the rest of awake logic ..
}
private void GetBehaviourInsight()
{
NeftaAdapterEvents.GetBehaviourInsight(new string[]
{
Banner.FloorPrice,
Interstitial.AdUnitId, Interstitial.FloorPrice,
Rewarded.AdUnitId, Rewarded.FloorPrice
});
}
private void OnBehaviourInsight(Dictionary<string, Insight> behaviourInsight)
{
foreach (var insight in behaviourInsight)
{
var insightValue = insight.Value;
Debug.Log($"BehaviourInsight {insight.Key} status:{insightValue._status} i:{insightValue._int} f:{insightValue._float} s:{insightValue._string}");
}
_banner.OnBehaviourInsight(behaviourInsight);
_interstitial.OnBehaviourInsight(behaviourInsight);
_rewarded.OnBehaviourInsight(behaviourInsight);
}
// ...
Load Ad in parallel
//
// Interstitial.swift
// MaxIntegration
//
// Created by Tomaz Treven on 9. 05. 24.
//
import Foundation
import AppLovinSDK
class Interstitial : NSObject, MAAdDelegate, MAAdRevenueDelegate {
private let _defaultAdUnitId = "6d318f954e2630a8"
static let InsightAdUnitId = "recommended_interstitial_ad_unit_id"
static let InsightFloorPrice = "calculated_user_floor_price_interstitial"
private var _defaultController: MAInterstitialAd?
private var _defaultAd: MAAd?
private var _recommendedController: MAInterstitialAd?
private var _recommendedAd: MAAd?
private var RequestNewInsights: () -> Void
private var _recommendedAdUnitId: String?
private var _calculatedBidFloor: Double = 0.0
private var _isRecommendedLoadPending = false
private let _loadButton: UIButton
private let _showButton: UIButton
private let _status: UILabel
private let _onFullScreenAdDisplayed: (Bool) -> Void
func OnBehaviourInsight(insights: [String: Insight]) {
if !_isRecommendedLoadPending {
return
}
_recommendedAdUnitId = insights[Interstitial.InsightAdUnitId]?._string
_calculatedBidFloor = insights[Interstitial.InsightFloorPrice]?._float ?? 0.0
print("OnBehaviourInsight for Interstitial recommended AdUnit: \(String(describing: _recommendedAdUnitId))/cpm:\(_calculatedBidFloor)")
if let recommendedAdUnitId = _recommendedAdUnitId, _defaultAdUnitId != recommendedAdUnitId {
_recommendedController = MAInterstitialAd(adUnitIdentifier: recommendedAdUnitId)
_recommendedController!.delegate = self
_recommendedController!.load()
}
}
init(requestNewInsights: @escaping (() -> Void), loadButton: UIButton, showButton: UIButton, status: UILabel, onDisplay: @escaping (Bool) -> Void) {
RequestNewInsights = requestNewInsights
_loadButton = loadButton
_showButton = showButton
_status = status
_onFullScreenAdDisplayed = onDisplay
super.init()
_loadButton.addTarget(self, action: #selector(OnLoadClick), for: .touchUpInside)
_showButton.addTarget(self, action: #selector(OnShowClick), for: .touchUpInside)
_showButton.isEnabled = false
}
@objc func OnLoadClick() {
SetInfo("Load default: \(String(describing: _defaultAd)) recommended: \(String(describing: _recommendedController))")
if _defaultAd == nil {
_defaultController = MAInterstitialAd(adUnitIdentifier: _defaultAdUnitId)
_defaultController!.delegate = self
_defaultController!.load()
}
if _recommendedAd == nil {
_isRecommendedLoadPending = true
RequestNewInsights()
}
}
@objc func OnShowClick() {
SetInfo("Show default: \(String(describing: _defaultAd)) recommended: \(String(describing: _recommendedController))")
_showButton.isEnabled = false
if _recommendedAd != nil {
if _defaultAd != nil && _defaultAd!.revenue > _recommendedAd!.revenue {
_defaultController!.show()
_defaultController = nil
_defaultAd = nil
} else {
_recommendedController!.show()
_recommendedController = nil
_recommendedAd = nil
}
} else if _defaultAd != nil {
_defaultController!.show()
_defaultController = nil
_defaultAd = nil
}
}
func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) {
ALNeftaMediationAdapter.onExternalMediationRequestFail(.interstitial, recommendedAdUnitId: _recommendedAdUnitId, calculatedFloorPrice: _calculatedBidFloor, adUnitIdentifier: adUnitIdentifier, error: error)
if adUnitIdentifier == _defaultAdUnitId {
_defaultController = nil
} else {
_recommendedController = nil
}
if _recommendedController == nil && _defaultController == nil {
OnLoadClick()
}
SetInfo("didFailToLoadAd \(adUnitIdentifier): \(error)")
}
func didLoad(_ ad: MAAd) {
ALNeftaMediationAdapter.onExternalMediationRequestLoad(.interstitial, recommendedAdUnitId: _recommendedAdUnitId, calculatedFloorPrice: _calculatedBidFloor, ad: ad)
if ad.adUnitIdentifier == _defaultAdUnitId {
_defaultAd = ad
} else {
_recommendedAd = ad
}
_showButton.isEnabled = true
SetInfo("didLoad \(ad) at: \(ad.revenue)")
}
// the rest of the class
}
public class InterstitialController : MonoBehaviour
{
#if UNITY_IOS
private const string _defaultAdUnitId = "c9acf50602329bfe";
#else // UNITY_ANDROID
private const string _defaultAdUnitId = "60bbc7cc56dfa329";
#endif
public static readonly string AdUnitId = "recommended_interstitial_ad_unit_id";
public static readonly string FloorPrice = "calculated_user_floor_price_interstitial";
private string _defaultLoadingAdUnitId;
private MaxSdkBase.AdInfo _defaultAd;
private string _recommendedAdUnitId;
private MaxSdkBase.AdInfo _recommendedAd;
private Action _getInsights;
private double _calculatedBidFloor;
private bool _isRecommendedLoadPending;
[SerializeField] private Text _title;
[SerializeField] private Button _load;
[SerializeField] private Button _show;
[SerializeField] private Text _status;
private Queue<string> _statusQueue;
private Action<bool> _onFullScreenAdDisplayed;
public void OnBehaviourInsight(Dictionary<string, Insight> insights)
{
if (!_isRecommendedLoadPending)
{
return;
}
_recommendedAdUnitId = insights[AdUnitId]._string;
_calculatedBidFloor = insights[FloorPrice]._float;
Debug.Log($"OnBehaviourInsight for Interstitial recommended AdUnit: {_recommendedAdUnitId}, calculated bid floor: {_calculatedBidFloor}");
if (!String.IsNullOrEmpty(_recommendedAdUnitId) && _defaultAdUnitId != _recommendedAdUnitId)
{
MaxSdk.LoadInterstitial(_recommendedAdUnitId);
}
}
public void Init(Action getInsights, Action<bool> onFullScreenAdDisplayed)
{
_statusQueue = new Queue<string>();
_getInsights = getInsights;
_onFullScreenAdDisplayed = onFullScreenAdDisplayed;
MaxSdkCallbacks.Interstitial.OnAdLoadedEvent += OnAdLoadedEvent;
MaxSdkCallbacks.Interstitial.OnAdLoadFailedEvent += OnAdFailedEvent;
MaxSdkCallbacks.Interstitial.OnAdDisplayedEvent += OnShowEvent;
MaxSdkCallbacks.Interstitial.OnAdDisplayFailedEvent += OnAdDisplayFailedEvent;
MaxSdkCallbacks.Interstitial.OnAdHiddenEvent += OnAdHiddenEvent;
MaxSdkCallbacks.Interstitial.OnAdRevenuePaidEvent += OnRevenuePaidEvent;
_title.text = "Interstitial";
_load.onClick.AddListener(OnLoadClick);
_show.onClick.AddListener(OnShowClick);
_show.interactable = false;
}
private void OnLoadClick()
{
SetStatus($"Load default: {_defaultAd} recommended: {_recommendedAd}");
if (_defaultAd == null)
{
MaxSdk.LoadInterstitial(_defaultAdUnitId);
}
if (_recommendedAd == null)
{
_isRecommendedLoadPending = true;
_getInsights();
}
}
private void OnShowClick()
{
SetStatus($"Show default: {_defaultAd} recommended: {_recommendedAd}");
_show.interactable = false;
if (_recommendedAd != null)
{
if (_defaultAd != null && _defaultAd.Revenue > _recommendedAd.Revenue)
{
MaxSdk.ShowInterstitial(_defaultAdUnitId);
_defaultAd = null;
}
else
{
MaxSdk.ShowInterstitial(_recommendedAdUnitId);
_recommendedAd = null;
}
}
else if (_defaultAd != null)
{
MaxSdk.ShowInterstitial(_defaultAdUnitId);
_defaultAd = null;
}
}
private void OnAdFailedEvent(string adUnitId, MaxSdkBase.ErrorInfo error)
{
NeftaAdapterEvents.OnExternalMediationRequestFailed(NeftaAdapterEvents.AdType.Interstitial, _recommendedAdUnitId, _calculatedBidFloor, adUnitId, error);
if (adUnitId == _defaultAdUnitId)
{
_defaultLoadingAdUnitId = null;
}
else
{
_recommendedAdUnitId = null;
}
if (_defaultLoadingAdUnitId == null && _recommendedAdUnitId == null)
{
OnLoadClick();
}
SetStatus($"Load failed: {error.Message}");
}
private void OnAdLoadedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
{
NeftaAdapterEvents.OnExternalMediationRequestLoaded(NeftaAdapterEvents.AdType.Interstitial, _recommendedAdUnitId, _calculatedBidFloor, adInfo);
if (adUnitId == _defaultAdUnitId)
{
_defaultAd = adInfo;
}
else
{
_recommendedAd = adInfo;
}
SetStatus($"Loaded {adUnitId} at: {adInfo.Revenue}");
_show.interactable = true;
}
// the rest of the class
}
Updated 4 days ago