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:
- Bind your layout’s UI components.
- Create a loader and load or render the native ad.
- 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.
<?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()CloudXNativeAdViewBinder binder = new 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()
}
}public class YourActivity extends AppCompatActivity implements CloudXNativeAdListener, CloudXAdRevenueListener {
private CloudXNativeAdLoader nativeAdLoader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
nativeAdLoader = CloudX.createNativeAdLoader(this, "your-native-ad-unit-id");
nativeAdLoader.setNativeAdListener(this);
nativeAdLoader.setRevenueListener(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (nativeAdLoader != null) {
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)CloudXNativeAdView adView = new 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)
}nativeAdLoader.loadAd();
@Override
public void onNativeAdLoaded(CloudXNativeAdView adView, CloudXAd ad) {
CloudXNativeAdView newAdView = new CloudXNativeAdView(YourActivity.this, 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}")
}// Required callbacks
@Override
public void onNativeAdLoaded(CloudXNativeAdView adView, CloudXAd ad) {
Log.d("CloudX", "Native ad loaded from " + ad.getNetworkName());
CloudXNativeAd nativeAd = ad.getNativeAd();
if (nativeAd != null && nativeAd.isVideoContent()) {
Log.d("CloudX", "Video duration: " + nativeAd.getVideoDuration() + "s");
}
if (adView != null) {
container.addView(adView);
}
}
@Override
public void onNativeAdLoadFailed(@NonNull String adUnitId, @NonNull CloudXError error) {
Log.e("CloudX", "Native ad failed to load: " + error.getMessage());
}
@Override
public void onNativeAdClicked(@NonNull CloudXAd ad) {
Log.d("CloudX", "Native ad clicked");
}
// Optional callbacks (default no-op)
@Override
public void onNativeAdExpired(@NonNull CloudXAd ad) {
Log.d("CloudX", "Native ad expired — destroy and reload");
nativeAdLoader.destroy(ad);
nativeAdLoader.loadAd();
}
@Override
public void onNativeAdClosed(@NonNull CloudXAd ad) {
Log.d("CloudX", "User dismissed the ad via AdChoices");
nativeAdLoader.destroy(ad);
}
// Revenue callback
@Override
public void onAdRevenuePaid(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Native ad revenue: " + cloudXAd.getRevenue() + " from " + cloudXAd.getNetworkName());
}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()// 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:
| Property | Type | Description |
|---|---|---|
title | String? | Headline text |
body | String? | Body / description text |
callToAction | String? | CTA button text (e.g., “Install Now”) |
advertiser | String? | Advertiser name |
icon | CloudXNativeAdImage? | App icon (as Drawable or Uri) |
mainImage | CloudXNativeAdImage? | Main image (static creatives) |
mediaView | View? | Video/media player view (adapter-provided) |
optionsView | View? | AdChoices or options view (adapter-provided) |
mediaContentAspectRatio | Float | Aspect ratio of the media content |
starRating | Double? | App store rating (0–5) |
isVideoContent | Boolean | Whether the creative is a video |
videoDuration | Double | Video length in seconds (0.0 if unknown) |
isExpired | Boolean | Whether 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.