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

Third party rendering #697

Open
YuriyVelichkoPI opened this issue Oct 28, 2022 · 4 comments
Open

Third party rendering #697

YuriyVelichkoPI opened this issue Oct 28, 2022 · 4 comments
Assignees
Milestone

Comments

@YuriyVelichkoPI
Copy link
Contributor

Antoine Barrault posted the next feature request in the Slack channel

Hello Prebid Mobile Team,

We would like to make a proposition about the development of a new feature we are going to make.

Context

We are planning of implement a third-party renderer feature (called as rendering delegation on prebid documentation). As part of this process, we need to pass an extra information inside the Bid Request in order to inform the Bid apdaters, that a custom renderer is available on the publisher app.

The information

The Bid adapter need to have a way to know if custom renderers are available in order to be able to deliver specials creatives. A simple representation for it, is an array of strings something like: [“custom_renderer_1”, “custom_renderer_2”].
We can call it third_party_renderers inside the openRTB representation.

Solution

To pass the information to the bid adpaters we can simply use the context data and passing a new key/value tuple.
Example:

TargetingParams.addContextData("third_party_renderers", "[teads]")

A more robust alternative can be to implement a new global targeting parameter like User Keywords that will allow the publisher or third party SDKs to pass new strings (the supported custom renderers) to this object.

TargetingParams.addCustomRenderer("custom_renderer_1")
@github-antoine-barrault

I made a full proposal on Android repository : prebid/prebid-mobile-android#507

@github-antoine-barrault

In addition to the android proposal we add some implementations details on iOS side.

ThirdPartyCustomRenderer: a new protocol for the third-party renderer

For doing third-party rendering, SDKs will have to implement a protocol specially created for it: ThirdPartyCustomRenderer.

@objc public protocol ThirdPartyCustomRenderer: NSObjectProtocol {
    /// Call this to setup the renderer and register it as "third party renderer"
    @objc static func setup()

    @objc init()

    @objc func loadAd(with frame: CGRect, bid: PrebidMobile.Bid, configId: String, adViewDelegate: PBMAdViewDelegate?)
}

An important point of this protocol is the PBMAdViewDelegate passed on the loadAd method. It is a protocol typealias between PBMAdViewManagerDelegate (that already exists today, that is used to pass ads events) and a new protocol PBMThirdPartyAdViewLoader that has to be called when the third-party renderer did load the ad with success.

public typealias PBMAdViewDelegate = PBMThirdPartyAdViewLoader & PBMAdViewManagerDelegate

Extend DisplayViewLoadingDelegate protocol

We need to extend DisplayViewLoadingDelegate because we need to pass the third-party ad view to the adapters (PrebidAdMobBannerAdapter, PrebidMAXMediationAdapter, PBMBannerAdLoader).

@objc public protocol DisplayViewLoadingDelegate: NSObjectProtocol {

    func displayViewDidLoadAd(_ displayView: PBMDisplayView)

    func displayView(_ displayView: PBMDisplayView,
                     didFailWithError error: Error)

+    func customRenderDisplayViewDidLoadAd(_ displayView: UIView, adSize: CGSize)
    
}

Updates to be done inside the PBMDisplayView

To make it works we need to conform to the new protocol PBMThirdPartyAdViewLoader implementing the function:

- (void)adViewLoaded:(UIView *)adView adSize:(CGSize)adSize

That will just passed the view to the next delegate (DisplayViewLoadingDelegate).

Display a custom ad when it is needed

We choose to put the call of the third-party renderer logic inside the function - (void)displayAd of the PBMDisplayView as it is the class responsible for the ad displaying in all ad placement cases.
We decide to add a new function called before the initialization of the transactionFactory. In this case the third-party renderer will be called the transactionFactory will not be created.

 - (void)displayAd {
    if (self.transactionFactory) {
        return;
    }

    self.adConfiguration.adConfiguration.winningBidAdFormat = self.bid.adFormat;

+    if ([self loadThirdPartyRendererIfNeeded]) {
+        return;
+    }

    @weakify(self);
    self.transactionFactory = [[PBMTransactionFactory alloc] initWithBid:self.bid
                                                         adConfiguration:self.adConfiguration
    }];
}

This new function will return a boolean that will be false if no custom renderers were found.

- (BOOL)loadThirdPartyRendererIfNeeded {
    NSString *bidderClass = [[self.bid targetingInfo] valueForKey:@"hb_bidder"];
    self.customRenderer = [self customRendererWithClassName:bidderClass];
        if (self.customRenderer != nil) {
            CGRect const displayFrame = CGRectMake(0, 0, self.bid.size.width, self.bid.size.height);
            [self.customRenderer loadAdWith: displayFrame bid:self.bid configId: [_adConfiguration configId] adViewDelegate:self];
            return YES;
        }
    return NO;
}

- (nullable id<ThirdPartyCustomRenderer>) customRendererWithClassName:(NSString *) className {
    Class customRenderer = NSClassFromString(className);
    return [[customRenderer alloc] init];
}

Use a singleton to stock the customs renderers

Alternatively: we can use a singleton instead of loading the ThirdPartyCustomRenderer using NSClassFromString.

We design a Singleton instance called CustomRendererStore

@objc public class CustomRendererStore: NSObject {
    @objc public static let shared = CustomRendererStore()

    private override init() {
        super.init()
    }

    public func addAdapter(_ adapter: ThirdPartyCustomRenderer, for key: String) {
        adapters[key] = adapter
    }
    @objc public var adapters: [String: ThirdPartyCustomRenderer] = [String: ThirdPartyCustomRenderer]()
    }
}

On the loadThirdPartyRendererIfNeeded method, we just need to change the way we load the customRenderer.

 - (BOOL)loadThirdPartyRendererIfNeeded {
    NSString *bidderClass = [[self.bid targetingInfo] valueForKey:@"hb_bidder"];
-   self.customRenderer = [self customRendererWithClassName:bidderClass];
+   NSObject<CustomRenderer> *adapater = [[[CustomRendererStore shared] adapters] valueForKey: bidder];  
        if (self.customRenderer != nil) {
            CGRect const displayFrame = CGRectMake(0, 0, self.bid.size.width, self.bid.size.height);
            [self.customRenderer loadAdWith: displayFrame bid:self.bid configId: [_adConfiguration configId] adViewDelegate:self];
            return YES;
        }
    return NO;
}

Also we need to make sure that the publisher will declare the custom renderer at some point before using it:

CustomRendererStore.shared.addAdapter(TeadsPrebidAdapter(), for: "teads")

@jsligh
Copy link
Collaborator

jsligh commented Mar 6, 2024

Teads has finished the Android portion and it is already merged into the Android Repo. The iOS portion is to be handed off to myself and the committee/community to finish.

@jsligh
Copy link
Collaborator

jsligh commented Aug 26, 2024

Waiting for confirmation of docs PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Under Review
Development

No branches or pull requests

4 participants