Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved handling of Amazon Integration #407

Open
christianrank opened this issue Jan 15, 2025 · 4 comments
Open

Improved handling of Amazon Integration #407

christianrank opened this issue Jan 15, 2025 · 4 comments
Labels
enhancement New feature or request

Comments

@christianrank
Copy link

christianrank commented Jan 15, 2025

Feature Description

I am currently integrating Amazon with the approach described in this PR: #291.

First of all, this should be part of the official docs. I had to be lucky to find that PR here. In the React-Native docs under "Preparing Mediated Networks", when you check the Amazon checkbox, there is only an example that works for a native integration (it assumes you have access to AdView).

Now the problem I found is, that the Amazon bid response is saved with the adUnitId.

If there are multiple ads with the same adUnitId on the same screen that load a bid simultaneously (that is the case in our implementation), how would it be secured that the bids of different adUnits don't overwrite each other? Bids can get lost and only the first position would get bids.

There needs to be a new, unique identifier, like positionId, that can be used instead of adUnitId to attach the bid. So every position can get the correct bid response attached.

@christianrank christianrank added the enhancement New feature or request label Jan 15, 2025
@christianrank christianrank changed the title Improved handeling of Amazon Integration Improved handling of Amazon Integration Jan 16, 2025
@christianrank
Copy link
Author

christianrank commented Jan 20, 2025

I found a way to solve this issue with a minimal patch to the SDK.

By setting the Amazon bid response in localExtraParameters on preloadNativeUIComponentAdView it is ensured that each position receives the correct bid.

I just needed to make AppLovinMAXAdView public on Android (no changes were required on iOS), so that it can be accessed from native code after fetching the bid.

It would be great if this class could be made public in a future release. Meanwhile, I hotfixed it using patch-package in my project.

@alhiwatan
Copy link
Collaborator

@christianrank, we've released version 8.1.1 to support your Amazon integration and hope you won't need a patch to modify our package. Actually I'm curious about how you integrated Amazon with our APIs in #291 using localExtraParameters on preloadNativeUIComponentAdView. Would you mind sharing a brief overview of your integration with us?

You can either email it to me at [email protected] or post it here. Thank you!

@christianrank
Copy link
Author

christianrank commented Jan 22, 2025

Hi, thanks for the release.

Here is an overview of the integration I did for Banner and MREC:

React Native:

import React, { useEffect, useState } from 'react'
import ReactNative from 'react-native'
import { AdFormat, AdView, destroyNativeUIComponentAdView } from 'react-native-applovin-max'

const { RNTAdsAppLovin } = ReactNative.NativeModules

interface Props {
  adFormat: AdFormat
  adUnitId: string
  amazonAdUnitId: string
}

export const AppLovinBannerPosition = (props: Props) => {
  const {
    adFormat,
    adUnitId,
    amazonAdUnitId,
  } = props

  const [adViewId, setAdViewId] = useState<number>()

  useEffect(() => {
    // fetch bids and preload ad view on native side
    RNTAdsAppLovin.fetchBidsAndPreloadNativeUIComponentAdView(adFormat, amazonAdUnitId, adUnitId).then((newAdViewId: number) => {
      setAdViewId(newAdViewId)
    })
  }, [])

  useEffect(() => {
    return () => {
      if (!adViewId) {
        return
      }

      // destroy ad view on unmount
      destroyNativeUIComponentAdView(adViewId)
    }
  }, [adViewId])

  if (!adViewId) {
    return null
  }

  return (
    <AdView
      adViewId={adViewId}
      adUnitId={adUnitId}
      adFormat={adFormat}
      // ...
    />
  )
}

Android (Java):

// ...

public class RNTAdsAppLovin extends ReactContextBaseJavaModule {
    private final ReactApplicationContext context;

    public RNTAdsAppLovin(ReactApplicationContext reactContext) {
        super(reactContext);
        context = reactContext;
    }

    @Override
    public String getName() {
        return "RNTAdsAppLovin";
    }

    private void preloadNativeUIComponentAdView(String adUnitId, MaxAdFormat maxAdFormat, Object amazonResponse, Promise promise) {
        Map<String, Object> localExtraParameters = new HashMap<String, Object>();

        if (amazonResponse != null) {
            // from getLocalExtraParameterKeyForAmazonResult
            String className = amazonResponse.getClass().getSimpleName();
            String localExtraParameterKeyForAmazon = "DTBAdResponse".equalsIgnoreCase( className ) ? "amazon_ad_response" : "amazon_ad_error";
            localExtraParameters.put(localExtraParameterKeyForAmazon, amazonResponse);
        }

        // initialize adView with localExtraParameters and resolve promise on react native with the id
        AppLovinMAXAdView.preloadNativeUIComponentAdView(adUnitId, maxAdFormat, null, null, null, localExtraParameters, promise, this.context);
    }

    private void fetchBids(MaxAdFormat maxAdFormat, String amazonSlotUuid, Consumer<Object> callback) {
        AppLovinSdkUtils.Size rawSize = maxAdFormat.getSize();
        DTBAdSize dtbSize = new DTBAdSize(rawSize.getWidth(), rawSize.getHeight(), amazonSlotUuid);

        DTBAdRequest adLoader = new DTBAdRequest();
        adLoader.setSizes(dtbSize);
        adLoader.loadAd(new DTBAdCallback()
        {
            @Override
            public void onSuccess(@NonNull final DTBAdResponse dtbAdResponse) {
                callback.accept(dtbAdResponse);
            }

            @Override
            public void onFailure(@NonNull final AdError adError) {
                callback.accept(adError);
            }
        } );
    }

    @ReactMethod
    public void fetchBidsAndPreloadNativeUIComponentAdView(String format, String amazonSlotUuid, String adUnitId, Promise promise) {
        MaxAdFormat maxAdFormat = format.equals("BANNER") ? MaxAdFormat.BANNER : MaxAdFormat.MREC;

        if (amazonSlotUuid == null) {
            // no amazon slot provided, directly preload adView
            this.preloadNativeUIComponentAdView(adUnitId, maxAdFormat, null, promise);
            return;
        }

        // callback to run after fetchBids
        Consumer<Object> callback = (Object amazonResult) -> {
            this.preloadNativeUIComponentAdView(adUnitId, maxAdFormat, amazonResult, promise);
        };

        // fetchBids and forward amazonResult to callback
        this.fetchBids(maxAdFormat, amazonSlotUuid, callback);
    }
}

iOS (Swift):

// ...

@objc(RNTAdsAppLovin)
class RNTAdsAppLovin: NSObject {
  @objc
  static func requiresMainQueueSetup() -> Bool {
    return true
  }

  func preloadNativeUIComponentAdView(_ adUnitId: String, _ maxAdFormat: MAAdFormat, _ amazonResponse: NSObject?, _ resolve: @escaping RCTPromiseResolveBlock, _ reject: @escaping RCTPromiseRejectBlock) {
    var extraParameters: [String: Any] = [:]
    var localExtraParameters: [String: Any] = [:]
    
    if (amazonResponse != nil) {
      // from localExtraParameterKeyForAmazonResult
      let className = String(describing: type(of: amazonResponse))
      let localExtraParameterKeyForAmazon = className == "DTBAdResponse" ? "amazon_ad_response" : "amazon_ad_error"
      localExtraParameters[localExtraParameterKeyForAmazon] = amazonResponse
    }

    // initialize adView with localExtraParameters and resolve promise on react native with the id
    AppLovinMAXAdView.preloadNativeUIComponentAdView(adUnitId, adFormat: maxAdFormat, placement: nil, customData: nil, extraParameters: extraParameters, localExtraParameters: localExtraParameters, withPromiseResolver: resolve, withPromiseRejecter: reject)
  }
  
  func fetchBids(_ format: String, _ maxAdFormat: MAAdFormat, _ amazonSlotUuid: String, _ callback: @escaping (_ amazonResponse: NSObject?) -> Void) {
    let rawSize = maxAdFormat.size
    let dtbSize: DTBAdSize = DTBAdSize(bannerAdSizeWithWidth: Int(rawSize.width), height: Int(rawSize.height), andSlotUUID: amazonSlotUuid)!
    
    let amazonCallback: DTBAdCallback = AmazonAdsCallback() { (_ amazonResponse: NSObject) in
      callback(amazonResponse)
    }

    let adLoader = DTBAdLoader()
    adLoader.setAdSizes([dtbSize])
    adLoader.loadAd(amazonCallback)
  }

  @objc
  func fetchBidsAndPreloadNativeUIComponentAdView(_ format: String, amazonSlotUuid: String?, adUnitId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    let maxAdFormat: MAAdFormat = format == "BANNER" ? MAAdFormat.banner : MAAdFormat.mrec

    guard (amazonSlotUuid != nil) else {
      // no amazon slot provided, directly preload adView
      self.preloadNativeUIComponentAdView(adUnitId, maxAdFormat, nil, resolve, reject)
      return
    }
    
    // fetchBids and forward amazonResult to callback
    self.fetchBids(format, maxAdFormat, amazonSlotUuid!) { (_ amazonResponse: NSObject?) in
      self.preloadNativeUIComponentAdView(adUnitId, maxAdFormat, amazonResponse, resolve, reject)
    }
  }
}

class AmazonAdsCallback: DTBAdCallback {
  var callback: (_ amazonResponse: NSObject) -> Void

  init(_ callback: @escaping (_ amazonResponse: NSObject) -> Void) {
    self.callback = callback
  }

  func onSuccess(_ adResponse: DTBAdResponse!) {
    self.callback(adResponse)
  }
  
  func onFailure(_ error: DTBAdError, dtbAdErrorInfo: DTBAdErrorInfo!) {
    self.callback(dtbAdErrorInfo)
  }
}

@alhiwatan
Copy link
Collaborator

@christianrank, thanks!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants