> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adrop.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Native Ads

> Learn how to implement native ads in Android.

## Overview

Native ads are an ad format that allows you to freely customize the ad UI to match your app's design. You can provide a natural user experience by individually placing elements such as ad creatives (images, videos), headline, body, and CTA buttons.

### Key Features

* **Complete UI Customization**: Freely configure ad layout to match your app's design system
* **Various Media Support**: Image and video ad creative support
* **Flexible Click Area Settings**: Full click or individual element click handling
* **Profile Information Display**: Advertiser profile logo, name, and link support

***

## Classes and Interfaces

### AdropNativeAd

Main class for loading and managing native ads.

<ParamField path="unitId" type="String" required>
  Ad unit ID (issued from console)
</ParamField>

<ParamField path="contextId" type="String">
  ID for contextual targeting (optional)
</ParamField>

<ParamField path="listener" type="AdropNativeAdListener">
  Listener to handle ad events
</ParamField>

<ParamField path="useCustomClick" type="Boolean" default="false">
  Whether to use custom click handling
</ParamField>

#### Key Properties

| Property        | Type                   | Description                                        |
| --------------- | ---------------------- | -------------------------------------------------- |
| `headline`      | `String`               | Ad headline                                        |
| `body`          | `String`               | Ad body text                                       |
| `icon`          | `String`               | Icon image URL                                     |
| `cover`         | `String`               | Cover image URL                                    |
| `advertiser`    | `String`               | **Deprecated.** Use `profile.displayName` instead. |
| `advertiserURL` | `String`               | **Deprecated.** Use `profile.link` instead.        |
| `accountTag`    | `JSONObject`           | **Deprecated.** No longer supported.               |
| `creativeTag`   | `JSONObject`           | **Deprecated.** No longer supported.               |
| `callToAction`  | `String`               | Call-to-action text (e.g., "Learn More")           |
| `profile`       | `AdropNativeAdProfile` | Advertiser profile information                     |
| `isLoaded`      | `Boolean`              | Whether ad loading is complete                     |
| `isDestroyed`   | `Boolean`              | Whether ad is destroyed                            |

### AdropNativeAdView

Container view for displaying native ads.

<ParamField path="isEntireClick" type="Boolean" default="false">
  Whether to enable entire area click. When set to `true`, click events occur on the entire ad view area.
</ParamField>

#### Key Methods

| Method                               | Description                                    |
| ------------------------------------ | ---------------------------------------------- |
| `setIconView(view, listener)`        | Set icon image view (ImageView type)           |
| `setHeadLineView(view, listener)`    | Set headline text view (TextView type)         |
| `setBodyView(view)`                  | Set body text view (TextView type)             |
| `setMediaView(view)`                 | Set media container view (AdropMediaView type) |
| `setAdvertiserView(view, listener)`  | Set advertiser text view (TextView type)       |
| `setCallToActionView(view)`          | Set CTA button or text view                    |
| `setProfileLogoView(view, listener)` | Set profile logo image view (ImageView type)   |
| `setProfileNameView(view, listener)` | Set profile name text view (TextView type)     |
| `setNativeAd(ad)`                    | Bind ad data to view                           |
| `destroy()`                          | Release resources                              |

### AdropMediaView

Media container view for displaying ad images or videos.

```xml theme={null}
<io.adrop.ads.nativeAd.AdropMediaView
    android:id="@+id/ad_media"
    android:layout_width="match_parent"
    android:layout_height="200dp" />
```

<Warning>
  For video creatives, `AdropMediaView` must be bound through `AdropNativeAdView.setMediaView(...)`. Reading `ad.asset` and playing the URL with your own player (`ExoPlayer`, `MediaPlayer`, or any third-party library) bypasses the SDK's video tracking pipeline, so **VTR is not measured** and `onAdVideoStart` / `onAdVideoEnd` never fire. See [Video Tracking and VTR](#video-tracking-and-vtr).
</Warning>

### AdropNativeAdListener

Interface for handling ad events.

```kotlin theme={null}
interface AdropNativeAdListener {
    fun onAdReceived(ad: AdropNativeAd)
    fun onAdClicked(ad: AdropNativeAd)
    fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode)
    fun onAdImpression(ad: AdropNativeAd) // Optional implementation
    fun onAdVideoStart(ad: AdropNativeAd) // Optional implementation
    fun onAdVideoEnd(ad: AdropNativeAd) // Optional implementation
}
```

### AdropNativeAdProfile

Data class containing advertiser profile information.

| Property      | Type     | Description            |
| ------------- | -------- | ---------------------- |
| `displayLogo` | `String` | Profile logo image URL |
| `displayName` | `String` | Profile display name   |
| `link`        | `String` | Profile link URL       |

***

## Implementation Guide

### 1. XML Layout Setup

Define the layout to display native ads. Use `AdropNativeAdView` as the root and place ad elements inside.

```xml res/layout/native_ad_layout.xml theme={null}
<?xml version="1.0" encoding="utf-8"?>
<io.adrop.ads.nativeAd.AdropNativeAdView
    android:id="@+id/native_ad_view"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp">

        <!-- Profile area -->
        <ImageView
            android:id="@+id/ad_profile_logo"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:scaleType="centerCrop"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/ad_profile_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:textSize="16sp"
            android:textStyle="bold"
            app:layout_constraintStart_toEndOf="@id/ad_profile_logo"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/ad_badge"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginTop="4dp"
            android:text="Ad"
            android:textSize="12sp"
            app:layout_constraintStart_toEndOf="@id/ad_profile_logo"
            app:layout_constraintTop_toBottomOf="@id/ad_profile_name" />

        <!-- Media area -->
        <io.adrop.ads.nativeAd.AdropMediaView
            android:id="@+id/ad_media"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:layout_marginTop="12dp"
            app:layout_constraintTop_toBottomOf="@id/ad_profile_logo" />

        <!-- Ad content -->
        <TextView
            android:id="@+id/ad_headline"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:textSize="18sp"
            android:textStyle="bold"
            app:layout_constraintTop_toBottomOf="@id/ad_media" />

        <TextView
            android:id="@+id/ad_body"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:textSize="14sp"
            app:layout_constraintTop_toBottomOf="@id/ad_headline" />

        <!-- Bottom area -->
        <TextView
            android:id="@+id/ad_advertiser"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:textSize="14sp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/ad_body" />

        <Button
            android:id="@+id/ad_call_to_action"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toBottomOf="@id/ad_body" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</io.adrop.ads.nativeAd.AdropNativeAdView>
```

### 2. Load and Display Ad (Kotlin)

<CodeGroup>
  ```kotlin Activity theme={null}
  import android.os.Bundle
  import android.util.Log
  import android.widget.Button
  import android.widget.ImageView
  import android.widget.TextView
  import androidx.appcompat.app.AppCompatActivity
  import com.bumptech.glide.Glide
  import io.adrop.ads.model.AdropErrorCode
  import io.adrop.ads.nativeAd.AdropMediaView
  import io.adrop.ads.nativeAd.AdropNativeAd
  import io.adrop.ads.nativeAd.AdropNativeAdListener
  import io.adrop.ads.nativeAd.AdropNativeAdView

  class NativeAdActivity : AppCompatActivity() {

      private lateinit var nativeAd: AdropNativeAd
      private lateinit var nativeAdView: AdropNativeAdView

      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_native_ad)

          nativeAdView = findViewById(R.id.native_ad_view)
          loadNativeAd()
      }

      private fun loadNativeAd() {
          // 1. Create native ad instance
          nativeAd = AdropNativeAd(
              context = this,
              unitId = "YOUR_UNIT_ID"  // Replace with actual unit ID
          )

          // 2. Set listener
          nativeAd.listener = object : AdropNativeAdListener {
              override fun onAdReceived(ad: AdropNativeAd) {
                  Log.d("Adrop", "Ad received successfully")
                  populateNativeAdView(ad)
              }

              override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
                  Log.e("Adrop", "Ad failed to receive: $errorCode")
              }

              override fun onAdClicked(ad: AdropNativeAd) {
                  Log.d("Adrop", "Ad clicked")
              }

              override fun onAdImpression(ad: AdropNativeAd) {
                  Log.d("Adrop", "Ad impression")
              }

              override fun onAdVideoStart(ad: AdropNativeAd) {
                  Log.d("Adrop", "Ad video started")
              }

              override fun onAdVideoEnd(ad: AdropNativeAd) {
                  Log.d("Adrop", "Ad video ended")
              }
          }

          // 3. Load ad
          nativeAd.load()
      }

      private fun populateNativeAdView(ad: AdropNativeAd) {
          // Set profile
          val profileLogoView = findViewById<ImageView>(R.id.ad_profile_logo)
          Glide.with(this).load(ad.profile.displayLogo).into(profileLogoView)
          nativeAdView.setProfileLogoView(profileLogoView)

          val profileNameView = findViewById<TextView>(R.id.ad_profile_name)
          profileNameView.text = ad.profile.displayName
          nativeAdView.setProfileNameView(profileNameView)

          // Set headline
          val headlineView = findViewById<TextView>(R.id.ad_headline)
          headlineView.text = ad.headline
          nativeAdView.setHeadLineView(headlineView)

          // Set media
          val mediaView = findViewById<AdropMediaView>(R.id.ad_media)
          nativeAdView.setMediaView(mediaView)

          // Set body
          val bodyView = findViewById<TextView>(R.id.ad_body)
          bodyView.text = ad.body
          nativeAdView.setBodyView(bodyView)

          // Set advertiser
          val advertiserView = findViewById<TextView>(R.id.ad_advertiser)
          advertiserView.text = ad.profile.displayName
          nativeAdView.setAdvertiserView(advertiserView)

          // Set CTA button
          val ctaView = findViewById<Button>(R.id.ad_call_to_action)
          ctaView.text = ad.callToAction
          nativeAdView.setCallToActionView(ctaView)

          // Bind ad data
          nativeAdView.setNativeAd(ad)
      }

      override fun onDestroy() {
          // Release resources
          nativeAdView.destroy()
          nativeAd.destroy()
          super.onDestroy()
      }
  }
  ```

  ```kotlin Fragment theme={null}
  import android.os.Bundle
  import android.util.Log
  import android.view.LayoutInflater
  import android.view.View
  import android.view.ViewGroup
  import android.widget.Button
  import android.widget.ImageView
  import android.widget.TextView
  import androidx.fragment.app.Fragment
  import com.bumptech.glide.Glide
  import io.adrop.ads.model.AdropErrorCode
  import io.adrop.ads.nativeAd.AdropMediaView
  import io.adrop.ads.nativeAd.AdropNativeAd
  import io.adrop.ads.nativeAd.AdropNativeAdListener
  import io.adrop.ads.nativeAd.AdropNativeAdView

  class NativeAdFragment : Fragment() {

      private var nativeAd: AdropNativeAd? = null
      private var nativeAdView: AdropNativeAdView? = null

      override fun onCreateView(
          inflater: LayoutInflater,
          container: ViewGroup?,
          savedInstanceState: Bundle?
      ): View? {
          return inflater.inflate(R.layout.fragment_native_ad, container, false)
      }

      override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
          super.onViewCreated(view, savedInstanceState)

          nativeAdView = view.findViewById(R.id.native_ad_view)
          loadNativeAd()
      }

      private fun loadNativeAd() {
          nativeAd = AdropNativeAd(
              context = requireContext(),
              unitId = "YOUR_UNIT_ID"
          )

          nativeAd?.listener = object : AdropNativeAdListener {
              override fun onAdReceived(ad: AdropNativeAd) {
                  populateNativeAdView(ad)
              }

              override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
                  Log.e("Adrop", "Ad load failed: $errorCode")
              }

              override fun onAdClicked(ad: AdropNativeAd) {
                  Log.d("Adrop", "Ad clicked")
              }
          }

          nativeAd?.load()
      }

      private fun populateNativeAdView(ad: AdropNativeAd) {
          val view = requireView()

          // Profile logo
          val profileLogo = view.findViewById<ImageView>(R.id.ad_profile_logo)
          Glide.with(this).load(ad.profile.displayLogo).into(profileLogo)
          nativeAdView?.setProfileLogoView(profileLogo)

          // Profile name
          val profileName = view.findViewById<TextView>(R.id.ad_profile_name)
          profileName.text = ad.profile.displayName
          nativeAdView?.setProfileNameView(profileName)

          // Headline
          val headline = view.findViewById<TextView>(R.id.ad_headline)
          headline.text = ad.headline
          nativeAdView?.setHeadLineView(headline)

          // Media
          val media = view.findViewById<AdropMediaView>(R.id.ad_media)
          nativeAdView?.setMediaView(media)

          // Body
          val body = view.findViewById<TextView>(R.id.ad_body)
          body.text = ad.body
          nativeAdView?.setBodyView(body)

          // Advertiser
          val advertiser = view.findViewById<TextView>(R.id.ad_advertiser)
          advertiser.text = ad.profile.displayName
          nativeAdView?.setAdvertiserView(advertiser)

          // CTA
          val cta = view.findViewById<Button>(R.id.ad_call_to_action)
          cta.text = ad.callToAction
          nativeAdView?.setCallToActionView(cta)

          // Binding
          nativeAdView?.setNativeAd(ad)
      }

      override fun onDestroyView() {
          nativeAdView?.destroy()
          nativeAd?.destroy()
          super.onDestroyView()
      }
  }
  ```
</CodeGroup>

### 3. Load and Display Ad (Java)

```java Java theme={null}
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.bumptech.glide.Glide;
import io.adrop.ads.model.AdropErrorCode;
import io.adrop.ads.nativeAd.AdropMediaView;
import io.adrop.ads.nativeAd.AdropNativeAd;
import io.adrop.ads.nativeAd.AdropNativeAdListener;
import io.adrop.ads.nativeAd.AdropNativeAdView;

public class NativeAdActivity extends AppCompatActivity {

    private AdropNativeAd nativeAd;
    private AdropNativeAdView nativeAdView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_native_ad);

        nativeAdView = findViewById(R.id.native_ad_view);
        loadNativeAd();
    }

    private void loadNativeAd() {
        // 1. Create native ad instance
        nativeAd = new AdropNativeAd(
            this,
            "YOUR_UNIT_ID",  // Replace with actual unit ID
            null  // contextId (optional)
        );

        // 2. Set listener
        nativeAd.setListener(new AdropNativeAdListener() {
            @Override
            public void onAdReceived(AdropNativeAd ad) {
                Log.d("Adrop", "Ad received successfully");
                populateNativeAdView(ad);
            }

            @Override
            public void onAdFailedToReceive(AdropNativeAd ad, AdropErrorCode errorCode) {
                Log.e("Adrop", "Ad failed to receive: " + errorCode);
            }

            @Override
            public void onAdClicked(AdropNativeAd ad) {
                Log.d("Adrop", "Ad clicked");
            }

            @Override
            public void onAdImpression(AdropNativeAd ad) {
                Log.d("Adrop", "Ad impression");
            }

            @Override
            public void onAdVideoStart(AdropNativeAd ad) {
                Log.d("Adrop", "Ad video started");
            }

            @Override
            public void onAdVideoEnd(AdropNativeAd ad) {
                Log.d("Adrop", "Ad video ended");
            }
        });

        // 3. Load ad
        nativeAd.load();
    }

    private void populateNativeAdView(AdropNativeAd ad) {
        // Set profile logo
        ImageView profileLogoView = findViewById(R.id.ad_profile_logo);
        Glide.with(this).load(ad.getProfile().getDisplayLogo()).into(profileLogoView);
        nativeAdView.setProfileLogoView(profileLogoView, null);

        // Set profile name
        TextView profileNameView = findViewById(R.id.ad_profile_name);
        profileNameView.setText(ad.getProfile().getDisplayName());
        nativeAdView.setProfileNameView(profileNameView, null);

        // Set headline
        TextView headlineView = findViewById(R.id.ad_headline);
        headlineView.setText(ad.getHeadline());
        nativeAdView.setHeadLineView(headlineView, null);

        // Set media
        AdropMediaView mediaView = findViewById(R.id.ad_media);
        nativeAdView.setMediaView(mediaView);

        // Set body
        TextView bodyView = findViewById(R.id.ad_body);
        bodyView.setText(ad.getBody());
        nativeAdView.setBodyView(bodyView);

        // Set advertiser
        TextView advertiserView = findViewById(R.id.ad_advertiser);
        advertiserView.setText(ad.getProfile().getDisplayName());
        nativeAdView.setAdvertiserView(advertiserView, null);

        // Set CTA button
        Button ctaView = findViewById(R.id.ad_call_to_action);
        ctaView.setText(ad.getCallToAction());
        nativeAdView.setCallToActionView(ctaView);

        // Bind ad data
        nativeAdView.setNativeAd(ad);
    }

    @Override
    protected void onDestroy() {
        // Release resources
        if (nativeAdView != null) {
            nativeAdView.destroy();
        }
        if (nativeAd != null) {
            nativeAd.destroy();
        }
        super.onDestroy();
    }
}
```

***

## Advanced Features

### Entire Click Area Setting

You can set the entire ad view area to trigger click events.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  nativeAdView.isEntireClick = true
  ```

  ```java Java theme={null}
  nativeAdView.setEntireClick(true);
  ```
</CodeGroup>

<Note>
  When `isEntireClick` is set to `true`, the entire ad view becomes a clickable area. Individual element click listeners will not work.
</Note>

### Individual Element Click Listeners

You can set custom click listeners on specific ad elements.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  // Custom action on advertiser click
  nativeAdView.setAdvertiserView(advertiserView) {
      Log.d("Adrop", "Advertiser clicked")
      // Implement custom action
  }

  // Custom action on profile logo click
  nativeAdView.setProfileLogoView(profileLogoView) {
      Log.d("Adrop", "Profile logo clicked")
      // Implement custom action
  }
  ```

  ```java Java theme={null}
  // Custom action on advertiser click
  nativeAdView.setAdvertiserView(advertiserView, new View.OnClickListener() {
      @Override
      public void onClick(View v) {
          Log.d("Adrop", "Advertiser clicked");
          // Implement custom action
      }
  });

  // Custom action on profile logo click
  nativeAdView.setProfileLogoView(profileLogoView, new View.OnClickListener() {
      @Override
      public void onClick(View v) {
          Log.d("Adrop", "Profile logo clicked");
          // Implement custom action
      }
  });
  ```
</CodeGroup>

### Contextual Targeting

You can request ads matching a specific context.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  val nativeAd = AdropNativeAd(
      context = this,
      unitId = "YOUR_UNIT_ID",
      contextId = "SPORTS_NEWS"  // Context ID
  )
  ```

  ```java Java theme={null}
  AdropNativeAd nativeAd = new AdropNativeAd(
      this,
      "YOUR_UNIT_ID",
      "SPORTS_NEWS"  // Context ID
  );
  ```
</CodeGroup>

### Using in RecyclerView

You can display native ads as items in a RecyclerView.

```kotlin RecyclerView Adapter theme={null}
class ContentAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    companion object {
        const val VIEW_TYPE_CONTENT = 0
        const val VIEW_TYPE_AD = 1
        const val AD_INTERVAL = 5  // Show ad every 5 items
    }

    private val items = mutableListOf<Any>()
    private val nativeAds = mutableMapOf<Int, AdropNativeAd>()

    override fun getItemViewType(position: Int): Int {
        return if (position % (AD_INTERVAL + 1) == AD_INTERVAL) {
            VIEW_TYPE_AD
        } else {
            VIEW_TYPE_CONTENT
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            VIEW_TYPE_AD -> {
                val view = LayoutInflater.from(parent.context)
                    .inflate(R.layout.item_native_ad, parent, false)
                NativeAdViewHolder(view)
            }
            else -> {
                val view = LayoutInflater.from(parent.context)
                    .inflate(R.layout.item_content, parent, false)
                ContentViewHolder(view)
            }
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is NativeAdViewHolder -> loadNativeAd(holder, position)
            is ContentViewHolder -> holder.bind(items[position])
        }
    }

    private fun loadNativeAd(holder: NativeAdViewHolder, position: Int) {
        // Reuse already loaded ad
        nativeAds[position]?.let { ad ->
            if (ad.isLoaded) {
                holder.bind(ad)
                return
            }
        }

        // Load new ad
        val nativeAd = AdropNativeAd(holder.itemView.context, "YOUR_UNIT_ID")
        nativeAd.listener = object : AdropNativeAdListener {
            override fun onAdReceived(ad: AdropNativeAd) {
                nativeAds[position] = ad
                holder.bind(ad)
            }

            override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
                Log.e("Adrop", "Ad load failed: $errorCode")
            }

            override fun onAdClicked(ad: AdropNativeAd) {
                Log.d("Adrop", "Ad clicked")
            }
        }
        nativeAd.load()
    }

    class NativeAdViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val nativeAdView: AdropNativeAdView = itemView.findViewById(R.id.native_ad_view)

        fun bind(ad: AdropNativeAd) {
            val profileLogo = itemView.findViewById<ImageView>(R.id.ad_profile_logo)
            Glide.with(itemView.context).load(ad.profile.displayLogo).into(profileLogo)
            nativeAdView.setProfileLogoView(profileLogo)

            val profileName = itemView.findViewById<TextView>(R.id.ad_profile_name)
            profileName.text = ad.profile.displayName
            nativeAdView.setProfileNameView(profileName)

            val headline = itemView.findViewById<TextView>(R.id.ad_headline)
            headline.text = ad.headline
            nativeAdView.setHeadLineView(headline)

            val media = itemView.findViewById<AdropMediaView>(R.id.ad_media)
            nativeAdView.setMediaView(media)

            val body = itemView.findViewById<TextView>(R.id.ad_body)
            body.text = ad.body
            nativeAdView.setBodyView(body)

            val advertiser = itemView.findViewById<TextView>(R.id.ad_advertiser)
            advertiser.text = ad.profile.displayName
            nativeAdView.setAdvertiserView(advertiser)

            val cta = itemView.findViewById<Button>(R.id.ad_call_to_action)
            cta.text = ad.callToAction
            nativeAdView.setCallToActionView(cta)

            nativeAdView.setNativeAd(ad)
        }
    }

    fun destroy() {
        nativeAds.values.forEach { it.destroy() }
        nativeAds.clear()
    }
}
```

***

## Test Unit IDs

Use the following test unit IDs during development and testing.

| Format              | Test Unit ID                            |
| ------------------- | --------------------------------------- |
| Native (Image)      | `PUBLIC_TEST_UNIT_ID_NATIVE`            |
| Native Video (16:9) | `PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_16_9` |
| Native Video (9:16) | `PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_9_16` |

<Warning>
  Be sure to replace with actual unit IDs before production deployment.
</Warning>

***

## Lifecycle Management

### Resource Release in Activity/Fragment

Native ads must be released when no longer in use to prevent memory leaks.

<CodeGroup>
  ```kotlin Activity theme={null}
  override fun onDestroy() {
      nativeAdView.destroy()
      nativeAd.destroy()
      super.onDestroy()
  }
  ```

  ```kotlin Fragment theme={null}
  override fun onDestroyView() {
      nativeAdView?.destroy()
      nativeAd?.destroy()
      super.onDestroyView()
  }
  ```

  ```java Java theme={null}
  @Override
  protected void onDestroy() {
      if (nativeAdView != null) {
          nativeAdView.destroy();
      }
      if (nativeAd != null) {
          nativeAd.destroy();
      }
      super.onDestroy();
  }
  ```
</CodeGroup>

### Resource Release in RecyclerView

When using ads in a RecyclerView Adapter, all ads must be released when the Activity or Fragment is destroyed.

```kotlin theme={null}
override fun onDestroy() {
    adapter.destroy()
    super.onDestroy()
}
```

***

## Video Tracking and VTR

Adrop measures video metrics for native creatives — including impression-to-completion (VTR), `onAdVideoStart`, and `onAdVideoEnd` — only when the video is rendered through the SDK's media surface (`AdropMediaView` bound via `AdropNativeAdView.setMediaView(...)`).

The `AdropNativeAd.asset` property returns a URL pointing to the underlying image or video file. It is exposed as supplementary metadata (for example, for thumbnails or your own analytics); it is **not** intended to drive playback.

<Warning>
  Do not feed the `asset` URL into a custom video player (`ExoPlayer`, `MediaPlayer`, or any third-party player) for a video native ad. When the SDK's media surface is bypassed:

  * **VTR (Video Through Rate) is not collected** for that placement.
  * `onAdVideoStart` / `onAdVideoEnd` callbacks **never fire**.
  * Aggregate video performance for the unit will under-report or report zero.

  Click (`onAdClicked`) and impression (`onAdImpression`) tracking still work because they are wired to the container view, but the video-specific signals are lost.
</Warning>

### Recommended pattern

Bind the SDK media surface for every video native:

<CodeGroup>
  ```kotlin Kotlin theme={null}
  val mediaView = findViewById<AdropMediaView>(R.id.ad_media)
  nativeAdView.setMediaView(mediaView)   // Required for video tracking
  nativeAdView.setNativeAd(ad)
  ```

  ```java Java theme={null}
  AdropMediaView mediaView = findViewById(R.id.ad_media);
  nativeAdView.setMediaView(mediaView);   // Required for video tracking
  nativeAdView.setNativeAd(ad);
  ```
</CodeGroup>

<Note>
  If a placement absolutely requires a custom player and you accept that VTR will not be measured, you can still surface click and impression attribution by keeping the ad bound to `AdropNativeAdView`. In that case, treat the placement as **non-VTR inventory** in your internal reporting and avoid mixing it with SDK-measured video performance.
</Note>

***

## Best Practices

### 1. Display Only Required Elements

You don't need to display all ad elements. Selectively use only the elements needed to match your app's design.

```kotlin theme={null}
// Minimal configuration example (media + CTA only)
nativeAdView.setMediaView(mediaView)
nativeAdView.setCallToActionView(ctaView)
nativeAdView.setNativeAd(ad)
```

### 2. Use Image Loading Libraries

Use image loading libraries like Glide or Coil when displaying icon, cover, and profile images.

```kotlin theme={null}
// Glide example
Glide.with(context)
    .load(ad.icon)
    .placeholder(R.drawable.placeholder)
    .error(R.drawable.error)
    .into(iconView)
```

### 3. Reuse Ad Views

When displaying ads in a RecyclerView, cache and reuse ad instances.

### 4. Appropriate Ad Placement

Place ads naturally without degrading user experience.

* Content feed: 1 ad every 5-10 items
* Detail screen: Place at bottom of content
* Scroll view: Place to match natural content flow

### 5. Error Handling

Implement appropriate fallback handling for ad load failures.

```kotlin theme={null}
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
    when (errorCode) {
        AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
            // No ad available - hide ad area
            nativeAdView.visibility = View.GONE
        }
        AdropErrorCode.ERROR_CODE_NETWORK -> {
            // Network error - retry logic
            Handler(Looper.getMainLooper()).postDelayed({
                nativeAd.load()
            }, 3000)
        }
        else -> {
            Log.e("Adrop", "Ad load failed: $errorCode")
        }
    }
}
```

***

## Complete Example

<CodeGroup>
  ```kotlin Activity theme={null}
  import android.os.Bundle
  import android.util.Log
  import android.view.View
  import android.widget.Button
  import android.widget.ImageView
  import android.widget.TextView
  import androidx.appcompat.app.AppCompatActivity
  import com.bumptech.glide.Glide
  import io.adrop.ads.model.AdropErrorCode
  import io.adrop.ads.nativeAd.AdropMediaView
  import io.adrop.ads.nativeAd.AdropNativeAd
  import io.adrop.ads.nativeAd.AdropNativeAdListener
  import io.adrop.ads.nativeAd.AdropNativeAdView

  class NativeAdExampleActivity : AppCompatActivity() {

      private lateinit var nativeAd: AdropNativeAd
      private lateinit var nativeAdView: AdropNativeAdView
      private lateinit var loadingView: View
      private lateinit var errorView: View

      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_native_ad_example)

          nativeAdView = findViewById(R.id.native_ad_view)
          loadingView = findViewById(R.id.loading_view)
          errorView = findViewById(R.id.error_view)

          findViewById<Button>(R.id.retry_button).setOnClickListener {
              loadNativeAd()
          }

          loadNativeAd()
      }

      private fun loadNativeAd() {
          showLoading()

          nativeAd = AdropNativeAd(
              context = this,
              unitId = "PUBLIC_TEST_UNIT_ID_NATIVE"
          )

          nativeAd.listener = object : AdropNativeAdListener {
              override fun onAdReceived(ad: AdropNativeAd) {
                  Log.d("Adrop", "Ad received: unitId=${ad.unitId}, txId=${ad.txId}")
                  populateNativeAdView(ad)
                  showAd()
              }

              override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
                  Log.e("Adrop", "Ad failed to receive: $errorCode")
                  showError()
              }

              override fun onAdClicked(ad: AdropNativeAd) {
                  Log.d("Adrop", "Ad clicked: txId=${ad.txId}")
              }

              override fun onAdImpression(ad: AdropNativeAd) {
                  Log.d("Adrop", "Ad impression: txId=${ad.txId}")
              }
          }

          nativeAd.load()
      }

      private fun populateNativeAdView(ad: AdropNativeAd) {
          // Enable entire click area (optional)
          // nativeAdView.isEntireClick = true

          // Profile logo
          val profileLogoView = findViewById<ImageView>(R.id.ad_profile_logo)
          Glide.with(this)
              .load(ad.profile.displayLogo)
              .circleCrop()
              .into(profileLogoView)
          nativeAdView.setProfileLogoView(profileLogoView)

          // Profile name
          val profileNameView = findViewById<TextView>(R.id.ad_profile_name)
          profileNameView.text = ad.profile.displayName
          nativeAdView.setProfileNameView(profileNameView)

          // Headline
          val headlineView = findViewById<TextView>(R.id.ad_headline)
          headlineView.text = ad.headline
          nativeAdView.setHeadLineView(headlineView)

          // Media (image or video)
          val mediaView = findViewById<AdropMediaView>(R.id.ad_media)
          nativeAdView.setMediaView(mediaView)

          // Body
          val bodyView = findViewById<TextView>(R.id.ad_body)
          bodyView.text = ad.body
          nativeAdView.setBodyView(bodyView)

          // Advertiser
          val advertiserView = findViewById<TextView>(R.id.ad_advertiser)
          advertiserView.text = ad.profile.displayName
          nativeAdView.setAdvertiserView(advertiserView) {
              Log.d("Adrop", "Advertiser clicked: ${ad.profile.displayName}")
          }

          // CTA button
          val ctaView = findViewById<Button>(R.id.ad_call_to_action)
          ctaView.text = ad.callToAction
          nativeAdView.setCallToActionView(ctaView)

          // Bind ad data
          nativeAdView.setNativeAd(ad)
      }

      private fun showLoading() {
          loadingView.visibility = View.VISIBLE
          nativeAdView.visibility = View.GONE
          errorView.visibility = View.GONE
      }

      private fun showAd() {
          loadingView.visibility = View.GONE
          nativeAdView.visibility = View.VISIBLE
          errorView.visibility = View.GONE
      }

      private fun showError() {
          loadingView.visibility = View.GONE
          nativeAdView.visibility = View.GONE
          errorView.visibility = View.VISIBLE
      }

      override fun onDestroy() {
          nativeAdView.destroy()
          nativeAd.destroy()
          super.onDestroy()
      }
  }
  ```

  ```java Java theme={null}
  import android.os.Bundle;
  import android.util.Log;
  import android.view.View;
  import android.widget.Button;
  import android.widget.ImageView;
  import android.widget.TextView;
  import androidx.appcompat.app.AppCompatActivity;
  import com.bumptech.glide.Glide;
  import io.adrop.ads.model.AdropErrorCode;
  import io.adrop.ads.nativeAd.AdropMediaView;
  import io.adrop.ads.nativeAd.AdropNativeAd;
  import io.adrop.ads.nativeAd.AdropNativeAdListener;
  import io.adrop.ads.nativeAd.AdropNativeAdView;

  public class NativeAdExampleActivity extends AppCompatActivity {

      private AdropNativeAd nativeAd;
      private AdropNativeAdView nativeAdView;
      private View loadingView;
      private View errorView;

      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_native_ad_example);

          nativeAdView = findViewById(R.id.native_ad_view);
          loadingView = findViewById(R.id.loading_view);
          errorView = findViewById(R.id.error_view);

          findViewById(R.id.retry_button).setOnClickListener(v -> loadNativeAd());

          loadNativeAd();
      }

      private void loadNativeAd() {
          showLoading();

          nativeAd = new AdropNativeAd(
              this,
              "PUBLIC_TEST_UNIT_ID_NATIVE",
              null
          );

          nativeAd.setListener(new AdropNativeAdListener() {
              @Override
              public void onAdReceived(AdropNativeAd ad) {
                  Log.d("Adrop", "Ad received: unitId=" + ad.getUnitId() + ", txId=" + ad.getTxId());
                  populateNativeAdView(ad);
                  showAd();
              }

              @Override
              public void onAdFailedToReceive(AdropNativeAd ad, AdropErrorCode errorCode) {
                  Log.e("Adrop", "Ad failed to receive: " + errorCode);
                  showError();
              }

              @Override
              public void onAdClicked(AdropNativeAd ad) {
                  Log.d("Adrop", "Ad clicked: txId=" + ad.getTxId());
              }

              @Override
              public void onAdImpression(AdropNativeAd ad) {
                  Log.d("Adrop", "Ad impression: txId=" + ad.getTxId());
              }
          });

          nativeAd.load();
      }

      private void populateNativeAdView(AdropNativeAd ad) {
          // Profile logo
          ImageView profileLogoView = findViewById(R.id.ad_profile_logo);
          Glide.with(this)
              .load(ad.getProfile().getDisplayLogo())
              .circleCrop()
              .into(profileLogoView);
          nativeAdView.setProfileLogoView(profileLogoView, null);

          // Profile name
          TextView profileNameView = findViewById(R.id.ad_profile_name);
          profileNameView.setText(ad.getProfile().getDisplayName());
          nativeAdView.setProfileNameView(profileNameView, null);

          // Headline
          TextView headlineView = findViewById(R.id.ad_headline);
          headlineView.setText(ad.getHeadline());
          nativeAdView.setHeadLineView(headlineView, null);

          // Media
          AdropMediaView mediaView = findViewById(R.id.ad_media);
          nativeAdView.setMediaView(mediaView);

          // Body
          TextView bodyView = findViewById(R.id.ad_body);
          bodyView.setText(ad.getBody());
          nativeAdView.setBodyView(bodyView);

          // Advertiser
          TextView advertiserView = findViewById(R.id.ad_advertiser);
          advertiserView.setText(ad.getProfile().getDisplayName());
          nativeAdView.setAdvertiserView(advertiserView, v -> {
              Log.d("Adrop", "Advertiser clicked: " + ad.getProfile().getDisplayName());
          });

          // CTA button
          Button ctaView = findViewById(R.id.ad_call_to_action);
          ctaView.setText(ad.getCallToAction());
          nativeAdView.setCallToActionView(ctaView);

          // Bind ad data
          nativeAdView.setNativeAd(ad);
      }

      private void showLoading() {
          loadingView.setVisibility(View.VISIBLE);
          nativeAdView.setVisibility(View.GONE);
          errorView.setVisibility(View.GONE);
      }

      private void showAd() {
          loadingView.setVisibility(View.GONE);
          nativeAdView.setVisibility(View.VISIBLE);
          errorView.setVisibility(View.GONE);
      }

      private void showError() {
          loadingView.setVisibility(View.GONE);
          nativeAdView.setVisibility(View.GONE);
          errorView.setVisibility(View.VISIBLE);
      }

      @Override
      protected void onDestroy() {
          if (nativeAdView != null) {
              nativeAdView.destroy();
          }
          if (nativeAd != null) {
              nativeAd.destroy();
          }
          super.onDestroy();
      }
  }
  ```
</CodeGroup>

***

## Batch Loading (loads)

Use `AdropNativeAd.loads()` to request multiple native ads in a single batched call. This is useful for prefetching a pool of ads to insert into feeds, carousels, or paginated lists.

### Signature

<CodeGroup>
  ```kotlin Kotlin theme={null}
  AdropNativeAd.loads(
      context: Context,
      unitId: String,
      contextId: String? = null,
      listener: AdropNativeAdListener,
  )
  ```

  ```java Java theme={null}
  AdropNativeAd.loads(context, "YOUR_UNIT_ID", null, listener);
  ```
</CodeGroup>

### Usage

<CodeGroup>
  ```kotlin Kotlin theme={null}
  AdropNativeAd.loads(
      context = this,
      unitId = "YOUR_UNIT_ID",
      contextId = null,
      listener = object : AdropNativeAdListener {
          // Batch success — each ad is fully rendered and ready to bind.
          override fun onAdsReceived(ads: List<AdropNativeAd>) {
              ads.forEach { ad ->
                  // Bind to an AdropNativeAdView or store for later lifecycle.
                  bindToView(ad)
              }
          }

          // Batch failure
          override fun onAdsFailedToReceive(errorCode: AdropErrorCode) {
              Log.e("Adrop", "Batch load failed: $errorCode")
          }

          // Single-load callbacks are NOT invoked on this path.
          override fun onAdReceived(ad: AdropNativeAd) { /* batch path */ }
          override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {}

          // Click / impression / video callbacks still fire per instance.
          override fun onAdClicked(ad: AdropNativeAd) {
              Log.d("Adrop", "Native clicked: ${ad.creativeId}")
          }

          override fun onAdImpression(ad: AdropNativeAd) {
              Log.d("Adrop", "Native impression: ${ad.creativeId}")
          }
      },
  )
  ```

  ```java Java theme={null}
  AdropNativeAd.loads(this, "YOUR_UNIT_ID", null, new AdropNativeAdListener() {
      @Override
      public void onAdsReceived(@NotNull List<AdropNativeAd> ads) {
          for (AdropNativeAd ad : ads) {
              bindToView(ad);
          }
      }

      @Override
      public void onAdsFailedToReceive(@NotNull AdropErrorCode errorCode) {
          Log.e("Adrop", "Batch load failed: " + errorCode);
      }

      @Override
      public void onAdReceived(@NotNull AdropNativeAd ad) { }

      @Override
      public void onAdFailedToReceive(@NotNull AdropNativeAd ad, @NotNull AdropErrorCode errorCode) { }

      @Override
      public void onAdClicked(@NotNull AdropNativeAd ad) {
          Log.d("Adrop", "Native clicked: " + ad.getCreativeId());
      }

      @Override
      public void onAdImpression(@NotNull AdropNativeAd ad) {
          Log.d("Adrop", "Native impression: " + ad.getCreativeId());
      }
  });
  ```
</CodeGroup>

### Listener Callbacks

| Callback                                                             | When it fires                                                                                  |
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `onAdsReceived(ads)`                                                 | Batch delivered. **Every ad is fully rendered** — creative is ready to bind.                   |
| `onAdsFailedToReceive(errorCode)`                                    | Request rejected, network failure, or no fillable ads were returned (`ERROR_CODE_AD_NO_FILL`). |
| `onAdClicked` / `onAdImpression` / `onAdVideoStart` / `onAdVideoEnd` | Fire on each returned ad just like a single `load()`.                                          |

<Warning>
  The singular `onAdReceived` / `onAdFailedToReceive` callbacks are **not** invoked on the `loads()` path. Use `onAdsReceived` / `onAdsFailedToReceive` as the sole batch signals.
</Warning>

### Constraints

* **Maximum 5 ads per call.** If the server returns more, only the first 5 are delivered.
* **Backfill is not applied.** If no direct ads fill, `onAdsFailedToReceive` is called with `ERROR_CODE_AD_NO_FILL` regardless of backfill configuration.
* **Do not call `destroy()` inside `onAdsReceived`.** Bind each ad to an `AdropNativeAdView` via `setNativeAd(...)` or store for later lifecycle, then `destroy()` on the parent `Activity.onDestroy()` (or equivalent).
* Unlike `AdropBanner.loads()`, the native batch waits for every returned ad to finish rendering before firing `onAdsReceived`, so each instance is fully rendered on delivery.

***

## Backfill Ads

When backfill ads are enabled, backfill ads are automatically loaded when direct ads are unavailable. Use the `isBackfilled` property to check if the ad is a backfill ad.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  nativeAd.listener = object : AdropNativeAdListener {
      override fun onAdReceived(ad: AdropNativeAd) {
          if (ad.isBackfilled) {
              println("Backfill ad loaded")
          } else {
              println("Direct ad loaded")
          }
      }

      override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
          when (errorCode) {
              AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
                  println("No direct ad, requesting backfill...")
              }
              AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL -> {
                  println("Backfill ad also not available")
              }
              else -> {
                  println("Ad load failed: $errorCode")
              }
          }
      }
  }
  ```

  ```java Java theme={null}
  nativeAd.setListener(new AdropNativeAdListener() {
      @Override
      public void onAdReceived(AdropNativeAd ad) {
          if (ad.isBackfilled()) {
              System.out.println("Backfill ad loaded");
          } else {
              System.out.println("Direct ad loaded");
          }
      }

      @Override
      public void onAdFailedToReceive(AdropNativeAd ad, AdropErrorCode errorCode) {
          if (errorCode == AdropErrorCode.ERROR_CODE_AD_NO_FILL) {
              System.out.println("No direct ad, requesting backfill...");
          } else if (errorCode == AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL) {
              System.out.println("Backfill ad also not available");
          } else {
              System.out.println("Ad load failed: " + errorCode);
          }
      }
  });
  ```
</CodeGroup>

### AdChoices Position

For backfill native ads, you can choose which corner displays the AdChoices badge. Set `preferredAdChoicesPosition` on the `AdropNativeAd` instance before calling `load()`.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  import io.adrop.ads.nativeAd.AdropAdChoicesPosition
  import io.adrop.ads.nativeAd.AdropNativeAd

  val nativeAd = AdropNativeAd(this, "YOUR_UNIT_ID")
  nativeAd.preferredAdChoicesPosition = AdropAdChoicesPosition.BOTTOM_RIGHT
  nativeAd.load()
  ```

  ```java Java theme={null}
  import io.adrop.ads.nativeAd.AdropAdChoicesPosition;
  import io.adrop.ads.nativeAd.AdropNativeAd;

  AdropNativeAd nativeAd = new AdropNativeAd(this, "YOUR_UNIT_ID");
  nativeAd.setPreferredAdChoicesPosition(AdropAdChoicesPosition.BOTTOM_RIGHT);
  nativeAd.load();
  ```
</CodeGroup>

| Value                                 | Description                |
| ------------------------------------- | -------------------------- |
| `AdropAdChoicesPosition.TOP_LEFT`     | Top-left corner            |
| `AdropAdChoicesPosition.TOP_RIGHT`    | Top-right corner (default) |
| `AdropAdChoicesPosition.BOTTOM_LEFT`  | Bottom-left corner         |
| `AdropAdChoicesPosition.BOTTOM_RIGHT` | Bottom-right corner        |

<Note>
  This setting is a preferred hint for the backfill network and applies only to backfill native ads — direct ads are not affected. The value must be set before `load()` is called. The backfill network may override the position depending on its policy.
</Note>

<Note>
  To use backfill ads, add the `io.adrop:adrop-ads-backfill` dependency. See [Getting Started](/sdk/android/overview).
</Note>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Banner Ads" icon="rectangle-ad" href="/sdk/android/banner">
    Implement banner ads
  </Card>

  <Card title="Interstitial Ads" icon="window-maximize" href="/sdk/android/interstitial">
    Implement interstitial ads
  </Card>

  <Card title="Targeting Settings" icon="bullseye" href="/sdk/android/targeting">
    User properties and context targeting
  </Card>

  <Card title="Reference" icon="book" href="/sdk/android/reference">
    Classes, listeners, error codes
  </Card>
</CardGroup>
