Native Ads

Load and render CloudX native ads in custom Android layouts

Native ads expose individual creative assets—such as the title, body, icon, media, and call to action—so you can render them in a layout that matches your app. CloudX populates the bound views and delegates impression and click tracking to the winning network adapter.

The integration has three steps:

  1. Bind your layout’s UI components.
  2. Create a loader and load or render the native ad.
  3. Destroy the ad and loader when they are no longer needed.

Supported networks

Per-network Native support — including the Native Banner and Native MREC variants — is maintained in the Native ad format matrix. Adapter-specific dependencies and setup notes are documented on each adapter’s overview page.

1. Bind UI components

Create a layout containing the native assets you want to display, then map each view ID with CloudXNativeAdViewBinder. A network may omit optional assets, so make sure your layout can collapse or hide empty views.

The options container is required: CloudX uses it to display the network-provided AdChoices or privacy control.

res/layout/native_ad_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp">

        <FrameLayout
            android:id="@+id/native_ad_options"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:gravity="center_vertical">

            <ImageView
                android:id="@+id/native_ad_icon"
                android:layout_width="48dp"
                android:layout_height="48dp" />

            <TextView
                android:id="@+id/native_ad_title"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:layout_marginStart="8dp"
                android:textSize="16sp"
                android:textStyle="bold" />
        </LinearLayout>

        <TextView
            android:id="@+id/native_ad_body"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp" />

        <FrameLayout
            android:id="@+id/native_ad_media_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:gravity="center_vertical">

            <TextView
                android:id="@+id/native_ad_advertiser"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:textColor="#888" />

            <FrameLayout
                android:id="@+id/native_ad_star_rating"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <Button
                android:id="@+id/native_ad_cta"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>

    </LinearLayout>
</FrameLayout>
val binder = CloudXNativeAdViewBinder.Builder(R.layout.native_ad_layout)
    .setTitleTextViewId(R.id.native_ad_title)
    .setBodyTextViewId(R.id.native_ad_body)
    .setIconImageViewId(R.id.native_ad_icon)
    .setMediaContentViewGroupId(R.id.native_ad_media_container)
    .setCallToActionButtonId(R.id.native_ad_cta)
    .setOptionsContentViewGroupId(R.id.native_ad_options)
    .setAdvertiserTextViewId(R.id.native_ad_advertiser)
    .setStarRatingContentViewGroupId(R.id.native_ad_star_rating)
    .build()

Star rating

CloudX fills the star rating container only when the network provides a rating of 3.0 or higher; otherwise the container is left empty. Design your layout so an empty star rating container collapses or is hidden.

2. Create a loader

class YourActivity : AppCompatActivity(), CloudXNativeAdListener, CloudXAdRevenueListener {
    private lateinit var nativeAdLoader: CloudXNativeAdLoader

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        nativeAdLoader = CloudX.createNativeAdLoader(this, "your-native-ad-unit-id")
        nativeAdLoader.nativeAdListener = this
        nativeAdLoader.revenueListener = this
    }

    override fun onDestroy() {
        super.onDestroy()
        nativeAdLoader.destroy()
    }
}

Load the ad

Choose one loading flow.

Load into a pre-built view

Use this flow when the destination layout is already available. CloudX populates and registers the view before returning it through onNativeAdLoaded.

val adView = CloudXNativeAdView(this, binder)
nativeAdLoader.loadAd(adView)

Load and render separately

Use deferred rendering when you want to load the ad before its destination view exists. Call render(...) before adding the view to your hierarchy.

nativeAdLoader.loadAd()

override fun onNativeAdLoaded(adView: CloudXNativeAdView?, ad: CloudXAd) {
    val newAdView = CloudXNativeAdView(this@YourActivity, binder)
    nativeAdLoader.render(newAdView, ad)
    container.addView(newAdView)
}

Handle callbacks

Loaded native ads expire one hour after load; onNativeAdExpired fires so you can destroy the expired ad and load a fresh one. In onNativeAdLoadFailed, avoid immediately retrying in a tight loop — retry after a delay or at the next natural display opportunity.

// Required callbacks

override fun onNativeAdLoaded(adView: CloudXNativeAdView?, ad: CloudXAd) {
    Log.d("CloudX", "Native ad loaded from ${ad.networkName}")

    ad.nativeAd?.let { nativeAd ->
        if (nativeAd.isVideoContent) {
            Log.d("CloudX", "Video duration: ${nativeAd.videoDuration}s")
        }
    }

    adView?.let { container.addView(it) }
}

override fun onNativeAdLoadFailed(adUnitId: String, error: CloudXError) {
    Log.e("CloudX", "Native ad failed to load: ${error.message}")
}

override fun onNativeAdClicked(ad: CloudXAd) {
    Log.d("CloudX", "Native ad clicked")
}

// Optional callbacks (default no-op)

override fun onNativeAdExpired(ad: CloudXAd) {
    Log.d("CloudX", "Native ad expired — destroy and reload")
    nativeAdLoader.destroy(ad)
    nativeAdLoader.loadAd()
}

override fun onNativeAdClosed(ad: CloudXAd) {
    Log.d("CloudX", "User dismissed the ad via AdChoices")
    nativeAdLoader.destroy(ad)
}

// Revenue callback

override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
    Log.d("CloudX", "Native ad revenue: ${cloudXAd.revenue} from ${cloudXAd.networkName}")
}

3. Destroy native ads

Destroy an individual ad when it is replaced or expires. Destroy the loader when its owning screen or component is finished. This releases network media views and prevents resources from accumulating over time.

// Destroy a specific loaded ad
nativeAdLoader.destroy(ad)

// Destroy the loader and all associated resources
nativeAdLoader.destroy()

Native ad assets

The CloudXNativeAd interface is available via ad.nativeAd in listener callbacks:

PropertyTypeDescription
titleString?Headline text
bodyString?Body / description text
callToActionString?CTA button text (e.g., “Install Now”)
advertiserString?Advertiser name
iconCloudXNativeAdImage?App icon (as Drawable or Uri)
mainImageCloudXNativeAdImage?Main image (static creatives)
mediaViewView?Video/media player view (adapter-provided)
optionsViewView?AdChoices or options view (adapter-provided)
mediaContentAspectRatioFloatAspect ratio of the media content
starRatingDouble?App store rating (0–5)
isVideoContentBooleanWhether the creative is a video
videoDurationDoubleVideo length in seconds (0.0 if unknown)
isExpiredBooleanWhether the ad has expired

Assets vary by network and creative. Treat nullable values as optional and hide the corresponding UI when an asset is unavailable. A mediaContentAspectRatio or videoDuration value of 0 means the network did not provide a usable value yet.

Native video metadata

Use isVideoContent to distinguish video from static native creatives. When it is true, videoDuration contains the duration in seconds if the network has made it available. Query duration after rendering for the most accurate value.