Skip to content

Commit

Permalink
Moloco Native Ad: Add support for native ads for the Moloco SDK
Browse files Browse the repository at this point in the history
  • Loading branch information
yamir-godil committed Jan 16, 2025
1 parent 0e18547 commit df7bb1d
Showing 1 changed file with 153 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,22 @@
package com.google.ads.mediation.moloco

import android.content.Context
import android.graphics.drawable.Drawable
import android.net.Uri
import android.view.View
import androidx.annotation.VisibleForTesting
import com.google.ads.mediation.moloco.AdmobAdapter.Companion.adErrorNoFill
import com.google.android.gms.ads.AdError
import com.google.android.gms.ads.mediation.MediationAdLoadCallback
import com.google.android.gms.ads.mediation.MediationNativeAdCallback
import com.google.android.gms.ads.mediation.MediationNativeAdConfiguration
import com.google.android.gms.ads.mediation.UnifiedNativeAdMapper
import com.google.android.gms.ads.nativead.NativeAdOptions
import com.moloco.sdk.publisher.AdLoad
import com.moloco.sdk.publisher.Moloco
import com.moloco.sdk.publisher.MolocoAd
import com.moloco.sdk.publisher.MolocoAdError
import com.moloco.sdk.publisher.NativeAd

/**
* Used to load Moloco native ads and mediate callbacks between Google Mobile Ads SDK and Moloco
Expand All @@ -27,28 +39,162 @@ import com.google.android.gms.ads.mediation.UnifiedNativeAdMapper
class MolocoNativeAd
private constructor(
private val context: Context,
private val adUnitId: String,
private val nativeAdOptions: NativeAdOptions, // TODO: Not sure where to use this?
private val bidResponse: String,
private val watermark: String,
private val mediationNativeAdLoadCallback:
MediationAdLoadCallback<UnifiedNativeAdMapper, MediationNativeAdCallback>,
// TODO: Add other parameters or remove unnecessary ones.
) : UnifiedNativeAdMapper() {
MediationAdLoadCallback<UnifiedNativeAdMapper, MediationNativeAdCallback>,
) : AdLoad.Listener, UnifiedNativeAdMapper() {
private var nativeAd: NativeAd? = null

fun loadAd() {
// TODO: Implement this method.
Moloco.createNativeAd(adUnitId, watermark) { returnedAd, adCreateError ->
if (returnedAd == null) {
// TODO: Use adCreateError to create an AdError object with proper error codes
// INVALID_AD_UNIT_ID, SDK_INIT_FAILED, SDK_INIT_WAS_NOT_COMPLETED should all have different error codes
val adError =
AdError(
MolocoMediationAdapter.ERROR_CODE_MISSING_AD_FAILED_TO_CREATE,
MolocoMediationAdapter.ERROR_MSG_MISSING_AD_FAILED_TO_CREATE,
MolocoMediationAdapter.SDK_ERROR_DOMAIN,
)
mediationNativeAdLoadCallback.onFailure(adError)
return@createNativeAd
}

nativeAd = returnedAd

// Now that the ad object is created, load the bid response
nativeAd?.load(bidResponse, this)
}
}

override fun onAdLoadSuccess(molocoAd: MolocoAd) {
overrideClickHandling = true
// If nativeAd is null here, then that means the ad was destroyed before load was successful or there is a bug
// in the adapter
nativeAd?.apply {
assets?.apply {
// Admob first uses rating, if not present the it uses sponsorText and if that is not present it will use the store.
rating?.let { starRating = it.toDouble() }
sponsorText?.let { advertiser = it }
store = "Google Play"
title?.let { headline = it }
description?.let { body = it }
callToActionText?.let { callToAction = it }
iconUri?.let {
Drawable.createFromPath(it.toString())?.apply {
icon = MolocoNativeMappedImage(this)
}
}

val mediaView = this.mediaView

mediaView?.let {
it.tag = MEDIA_VIEW_TAG
setMediaView(it)
}
}
}

val showCallback = mediationNativeAdLoadCallback.onSuccess(this)
nativeAd?.interactionListener = object : NativeAd.InteractionListener {
/**
* Not needed as Admob handles impressions on its own.
*/
override fun onImpressionHandled() {}

/**
* When this Moloco function gets triggered, we inform Admob that ad click has occurred
*/
override fun onGeneralClickHandled() = showCallback.reportAdClicked()
}
}

override fun onAdLoadFailed(molocoAdError: MolocoAdError) {
mediationNativeAdLoadCallback.onFailure(adErrorNoFill)
}

/**
* Admob informs us that a click has happened to the view(s) they create.
* This excludes the view set in [setMediaView]. Click from said view must be handled elsewhere
*/
override fun handleClick(view: View) {
nativeAd?.handleGeneralAdClick()
}

/**
* Admob informs us that an impression happened
*/
override fun recordImpression() {
nativeAd?.handleImpression()
}

override fun trackViews(
containerView: View,
clickableAssetViews: MutableMap<String, View>,
nonClickableAssetViews: MutableMap<String, View>,
) {
// set the listener to Moloco's NativeAd object. Then we read it from the Moloco's `NativeAd.interactionListener` callback
containerView.setOnClickListener { nativeAd?.handleGeneralAdClick() }
clickableAssetViews.values.forEach {
it.setOnClickListener { nativeAd?.handleGeneralAdClick() }
}
}

/**
* To be called by the medation to destroy the ad object and any underlying references in the Moloco SDK. This should be called
* when the ad is permanently "hidden" / never going to be visible again.
*/
fun destroy() {
nativeAd?.destroy()
nativeAd = null
}

companion object {
fun newInstance(
mediationNativeAdConfiguration: MediationNativeAdConfiguration,
mediationNativeAdLoadCallback:
MediationAdLoadCallback<UnifiedNativeAdMapper, MediationNativeAdCallback>,
MediationAdLoadCallback<UnifiedNativeAdMapper, MediationNativeAdCallback>,
): Result<MolocoNativeAd> {
val context = mediationNativeAdConfiguration.context
val serverParameters = mediationNativeAdConfiguration.serverParameters
val nativeAdOptions = mediationNativeAdConfiguration.nativeAdOptions

// TODO: Implement necessary initialization steps.
val adUnitId = serverParameters.getString(MolocoMediationAdapter.KEY_AD_UNIT_ID)
if (adUnitId.isNullOrEmpty()) {
val adError =
AdError(
MolocoMediationAdapter.ERROR_CODE_MISSING_AD_UNIT,
MolocoMediationAdapter.ERROR_MSG_MISSING_AD_UNIT,
MolocoMediationAdapter.ADAPTER_ERROR_DOMAIN,
)
mediationNativeAdLoadCallback.onFailure(adError)
return Result.failure(NoSuchElementException(adError.message))
}

val bidResponse = mediationNativeAdConfiguration.bidResponse
val watermark = mediationNativeAdConfiguration.watermark

return Result.success(MolocoNativeAd(context, mediationNativeAdLoadCallback))
return Result.success(MolocoNativeAd(context, adUnitId, nativeAdOptions, bidResponse, watermark, mediationNativeAdLoadCallback))
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
const val MEDIA_VIEW_TAG = "native_ad_media_view"
}

/**
* Admob adapter only needs the [drawable] parameter. The rest are optional
*/
@Suppress("DEPRECATION")
internal class MolocoNativeMappedImage(
private val drawable: Drawable,
private val uri: Uri = Uri.EMPTY,
private val scale: Double = 1.0,
) : com.google.android.gms.ads.formats.NativeAd.Image() { // Google deprecated the class, but didn't offer an alternative. So for now we *must* use the deprecated class.
override fun getScale() = scale
override fun getDrawable() = drawable
override fun getUri() = uri
}
}

0 comments on commit df7bb1d

Please sign in to comment.