> ## 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 your Flutter app.

## Overview

Native ads are customizable ads that blend naturally with your app's content. You can configure the ad to match your app's UI using `AdropNativeAd` and `AdropNativeAdView`.

### Key Features

* Customizable layout to match app design
* Provides various elements like headline, body, CTA button, profile, etc.
* Supports image and HTML creative
* Custom click handling support
* Backfill ad support

<Note>
  Use the test unit ID in development: `PUBLIC_TEST_UNIT_ID_NATIVE`
</Note>

***

## AdropNativeAd

### Constructor

```dart theme={null}
AdropNativeAd({
  required String unitId,
  bool useCustomClick = false,
  AdropAdChoicesPosition preferredAdChoicesPosition = AdropAdChoicesPosition.topRight,
  AdropNativeListener? listener,
})
```

**Parameters**

| Parameter                    | Type                     | Required | Description                                                                                   |
| ---------------------------- | ------------------------ | -------- | --------------------------------------------------------------------------------------------- |
| `unitId`                     | `String`                 | Y        | Unit ID created in Ad Control Console                                                         |
| `useCustomClick`             | `bool`                   | N        | Whether to use custom click handling (default: `false`)                                       |
| `preferredAdChoicesPosition` | `AdropAdChoicesPosition` | N        | AdChoices badge position for backfill native ads (default: `AdropAdChoicesPosition.topRight`) |
| `listener`                   | `AdropNativeListener`    | N        | Ad event listener                                                                             |

### Properties

| Property         | Type                    | Description                           |
| ---------------- | ----------------------- | ------------------------------------- |
| `isLoaded`       | `bool`                  | Whether the ad has finished loading   |
| `unitId`         | `String`                | Ad unit ID                            |
| `creativeId`     | `String`                | Creative ID                           |
| `txId`           | `String`                | Transaction ID                        |
| `campaignId`     | `String`                | Campaign ID                           |
| `destinationURL` | `String`                | Destination URL                       |
| `properties`     | `AdropNativeProperties` | Native ad properties                  |
| `creativeSize`   | `CreativeSize`          | Creative size                         |
| `isBackfilled`   | `bool`                  | Whether it's a backfill ad            |
| `browserTarget`  | `BrowserTarget?`        | Browser target (external or internal) |

### Methods

| Method   | Return Type    | Description  |
| -------- | -------------- | ------------ |
| `load()` | `Future<void>` | Loads the ad |

<Note>
  Unlike other ad types (`AdropInterstitialAd`, `AdropRewardedAd`, `AdropPopupAd`), `AdropNativeAd` does not provide a `dispose()` method.
  To load a new ad, create a new `AdropNativeAd` instance. Resources from previous instances are automatically released by the garbage collector.
</Note>

***

## AdropNativeAdView

A widget that displays native ads on screen.

### Constructor

```dart theme={null}
AdropNativeAdView({
  required AdropNativeAd? ad,
  required Widget child,
})
```

**Parameters**

| Parameter | Type             | Required | Description                        |
| --------- | ---------------- | -------- | ---------------------------------- |
| `ad`      | `AdropNativeAd?` | Y        | Loaded native ad object            |
| `child`   | `Widget`         | Y        | Child widget to display ad content |

***

## AdropNativeProperties

Content properties of the native ad.

### Properties

| Property         | Type                  | Description                    |
| ---------------- | --------------------- | ------------------------------ |
| `headline`       | `String?`             | Ad headline                    |
| `body`           | `String?`             | Ad body text                   |
| `creative`       | `String?`             | HTML creative content          |
| `asset`          | `String?`             | Image asset URL                |
| `destinationURL` | `String?`             | URL to navigate on click       |
| `callToAction`   | `String?`             | CTA button text                |
| `profile`        | `AdropNativeProfile?` | Advertiser profile information |
| `extra`          | `Map<String, String>` | Additional custom fields       |
| `isBackfilled`   | `bool`                | Whether it's a backfill ad     |

### AdropNativeProfile

| Property      | Type      | Description               |
| ------------- | --------- | ------------------------- |
| `displayName` | `String?` | Advertiser name           |
| `displayLogo` | `String?` | Advertiser logo image URL |

***

## Basic Usage

```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';

class NativeAdExample extends StatefulWidget {
  const NativeAdExample({super.key});

  @override
  State<NativeAdExample> createState() => _NativeAdExampleState();
}

class _NativeAdExampleState extends State<NativeAdExample> {
  bool isLoaded = false;
  AdropNativeAd? nativeAd;

  @override
  void initState() {
    super.initState();
    _createNativeAd();
  }

  void _createNativeAd() {
    nativeAd = AdropNativeAd(
      unitId: 'YOUR_UNIT_ID',
      listener: AdropNativeListener(
        onAdReceived: (ad) {
          debugPrint('Native ad received successfully: ${ad.creativeId}');
          setState(() {
            isLoaded = true;
          });
        },
        onAdClicked: (ad) {
          debugPrint('Native ad clicked: ${ad.creativeId}');
        },
        onAdImpression: (ad) {
          debugPrint('Native ad impression: ${ad.creativeId}');
        },
        onAdFailedToReceive: (ad, errorCode) {
          debugPrint('Native ad failed to receive: $errorCode');
          setState(() {
            isLoaded = false;
          });
        },
      ),
    );

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Native Ad Example')),
      body: SingleChildScrollView(
        child: Column(
          children: [
            // Main content
            const Padding(
              padding: EdgeInsets.all(16),
              child: Text('Main Content'),
            ),
            // Native ad
            if (isLoaded) _buildNativeAdView(),
          ],
        ),
      ),
    );
  }

  Widget _buildNativeAdView() {
    return AdropNativeAdView(
      ad: nativeAd,
      child: Container(
        padding: const EdgeInsets.all(16),
        margin: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          border: Border.all(color: Colors.grey),
          borderRadius: BorderRadius.circular(8),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Advertiser profile
            if (nativeAd?.properties.profile != null)
              Row(
                children: [
                  if (nativeAd?.properties.profile?.displayLogo != null)
                    Image.network(
                      nativeAd!.properties.profile!.displayLogo!,
                      width: 24,
                      height: 24,
                    ),
                  const SizedBox(width: 8),
                  Text(nativeAd?.properties.profile?.displayName ?? ''),
                ],
              ),
            const SizedBox(height: 8),
            // Headline
            if (nativeAd?.properties.headline != null)
              Text(
                nativeAd!.properties.headline!,
                style: const TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
            const SizedBox(height: 4),
            // Body
            if (nativeAd?.properties.body != null)
              Text(nativeAd!.properties.body!),
            const SizedBox(height: 8),
            // Image asset
            if (nativeAd?.properties.asset != null)
              Image.network(
                nativeAd!.properties.asset!,
                width: double.infinity,
                fit: BoxFit.cover,
              ),
            const SizedBox(height: 8),
            // CTA button
            if (nativeAd?.properties.callToAction != null)
              ElevatedButton(
                onPressed: () {},
                child: Text(nativeAd!.properties.callToAction!),
              ),
          ],
        ),
      ),
    );
  }
}
```

***

## AdropNativeListener

Listener to handle native ad events.

### Callbacks

```dart theme={null}
AdropNativeListener(
  onAdReceived: (AdropNativeAd ad) {
    // Ad received successfully
  },
  onAdClicked: (AdropNativeAd ad) {
    // Ad clicked
  },
  onAdImpression: (AdropNativeAd ad) {
    // Ad impression
  },
  onAdFailedToReceive: (AdropNativeAd ad, AdropErrorCode errorCode) {
    // Ad failed to receive
  },
  onAdVideoStart: (AdropNativeAd ad) {
    // Video ad started playing
  },
  onAdVideoEnd: (AdropNativeAd ad) {
    // Video ad finished playing
  },
)
```

### Callback Descriptions

| Callback              | Description                             |
| --------------------- | --------------------------------------- |
| `onAdReceived`        | Called when ad is received successfully |
| `onAdClicked`         | Called when ad is clicked               |
| `onAdImpression`      | Called when ad impression occurs        |
| `onAdFailedToReceive` | Called when ad fails to receive         |
| `onAdVideoStart`      | Called when video ad starts playing     |
| `onAdVideoEnd`        | Called when video ad finishes playing   |

***

## Custom Click Handling

Use `useCustomClick` when video creative or custom click behavior is needed.

```dart theme={null}
nativeAd = AdropNativeAd(
  unitId: 'YOUR_UNIT_ID',
  useCustomClick: true, // Enable custom click
  listener: AdropNativeListener(
    onAdReceived: (ad) {
      setState(() {
        isLoaded = true;
      });
    },
    onAdClicked: (ad) {
      // Handle custom click action
      debugPrint('Ad clicked: ${ad.destinationURL}');
    },
  ),
);
```

When `useCustomClick` is `true`, child widget click events are handled as ad clicks.

***

## Displaying HTML Creative

If the native ad includes HTML creative, you can display it using WebView.

<Warning>
  For **video native creatives**, you must render `properties.creative` (the HTML payload) through a WebView. Passing `properties.asset` directly into `video_player` or any other custom player 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>

```dart theme={null}
import 'package:webview_flutter/webview_flutter.dart';

class NativeWithWebView extends StatefulWidget {
  const NativeWithWebView({super.key});

  @override
  State<NativeWithWebView> createState() => _NativeWithWebViewState();
}

class _NativeWithWebViewState extends State<NativeWithWebView> {
  bool isLoaded = false;
  AdropNativeAd? nativeAd;
  late final WebViewController webViewController;

  @override
  void initState() {
    super.initState();

    // Initialize WebView controller
    webViewController = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted);

    _createNativeAd();
  }

  void _createNativeAd() {
    nativeAd = AdropNativeAd(
      unitId: 'YOUR_UNIT_ID',
      useCustomClick: true,
      listener: AdropNativeListener(
        onAdReceived: (ad) {
          // Load HTML creative
          if (ad.properties.creative != null) {
            webViewController.loadHtmlString(ad.properties.creative!);
          }
          setState(() {
            isLoaded = true;
          });
        },
      ),
    );

    nativeAd?.load();
  }

  @override
  Widget build(BuildContext context) {
    if (!isLoaded) return const SizedBox.shrink();

    return AdropNativeAdView(
      ad: nativeAd,
      child: Container(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Profile
            Row(
              children: [
                if (nativeAd?.properties.profile?.displayLogo != null)
                  Image.network(
                    nativeAd!.properties.profile!.displayLogo!,
                    width: 24,
                    height: 24,
                  ),
                const SizedBox(width: 8),
                Text(nativeAd?.properties.profile?.displayName ?? ''),
              ],
            ),
            const SizedBox(height: 8),
            // Headline
            if (nativeAd?.properties.headline != null)
              Text(
                nativeAd!.properties.headline!,
                style: const TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
            const SizedBox(height: 8),
            // HTML creative
            SizedBox(
              width: MediaQuery.of(context).size.width,
              height: 300,
              child: WebViewWidget(controller: webViewController),
            ),
            // CTA button
            if (nativeAd?.properties.callToAction != null)
              Padding(
                padding: const EdgeInsets.only(top: 8),
                child: ElevatedButton(
                  onPressed: () {},
                  child: Text(nativeAd!.properties.callToAction!),
                ),
              ),
          ],
        ),
      ),
    );
  }
}
```

<Note>
  To use HTML creative, you need to add the `webview_flutter` package.

  ```bash theme={null}
  flutter pub add webview_flutter
  ```
</Note>

***

## Handling Backfill Ads

You can check and handle backfill ads using the `isBackfilled` property.

```dart theme={null}
Widget _buildCreativeView() {
  if (nativeAd?.isBackfilled == true) {
    // Backfill ad: Use image asset
    return Image.network(
      nativeAd?.properties.asset ?? '',
      width: double.infinity,
      fit: BoxFit.cover,
      loadingBuilder: (context, child, loadingProgress) {
        if (loadingProgress == null) return child;
        return const Center(child: CircularProgressIndicator());
      },
      errorBuilder: (context, error, stackTrace) {
        return const Icon(Icons.error);
      },
    );
  } else if (nativeAd?.properties.creative != null) {
    // Regular ad: Use HTML creative
    return SizedBox(
      width: double.infinity,
      height: 300,
      child: WebViewWidget(controller: webViewController),
    );
  } else {
    return const SizedBox.shrink();
  }
}
```

### AdChoices Position

For backfill native ads, you can choose which corner displays the AdChoices badge. Set `preferredAdChoicesPosition` in the `AdropNativeAd` constructor.

```dart theme={null}
final nativeAd = AdropNativeAd(
  unitId: 'YOUR_UNIT_ID',
  preferredAdChoicesPosition: AdropAdChoicesPosition.bottomRight,
  listener: AdropNativeListener(
    onAdReceived: (ad) => debugPrint('Received'),
    onAdFailedToReceive: (ad, errorCode) => debugPrint('Failed: $errorCode'),
  ),
);

await nativeAd.load();
```

| Value                                | Description                |
| ------------------------------------ | -------------------------- |
| `AdropAdChoicesPosition.topLeft`     | Top-left corner            |
| `AdropAdChoicesPosition.topRight`    | Top-right corner (default) |
| `AdropAdChoicesPosition.bottomLeft`  | Bottom-left corner         |
| `AdropAdChoicesPosition.bottomRight` | 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 backfill network may override the position depending on its policy.
</Note>

***

## 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. On Flutter, that means rendering the HTML creative (`properties.creative`) inside a WebView, as shown in [Displaying HTML Creative](#displaying-html-creative).

The `properties.asset` field 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 `properties.asset` URL into a custom video player (`video_player`, `chewie`, 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 `AdropNativeAdView`, but the video-specific signals are lost.
</Warning>

### Recommended pattern

For video creatives, follow the WebView + HTML creative pattern:

```dart theme={null}
if (ad.properties.creative != null) {
  webViewController.loadHtmlString(ad.properties.creative!);
}
```

Bind the WebView inside `AdropNativeAdView` so that click and impression tracking stay attached to the ad container.

<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>

***

## Using Extra Fields

Additional fields defined by publishers can be accessed from the `extra` map.

```dart theme={null}
onAdReceived: (ad) {
  // Access extra fields
  final customField = ad.properties.extra['customFieldKey'];
  if (customField != null) {
    debugPrint('Custom field: $customField');
  }

  setState(() {
    isLoaded = true;
  });
}
```

***

## Error Handling

### Backfill-Related Errors

When backfill ads are configured, the `backfillNoFill` error code is returned when both direct ads and backfill ads are unavailable.

```dart theme={null}
onAdFailedToReceive: (ad, errorCode) {
  if (errorCode == AdropErrorCode.backfillNoFill) {
    debugPrint('No backfill ads available');
  }
}
```

### General Error Handling

```dart theme={null}
class _NativeAdState extends State<NativeAdWidget> {
  bool isLoaded = false;
  AdropErrorCode? errorCode;
  AdropNativeAd? nativeAd;

  @override
  void initState() {
    super.initState();

    nativeAd = AdropNativeAd(
      unitId: 'YOUR_UNIT_ID',
      listener: AdropNativeListener(
        onAdReceived: (ad) {
          setState(() {
            isLoaded = true;
            errorCode = null;
          });
        },
        onAdFailedToReceive: (ad, error) {
          // Handle backfill-related errors
          if (error == AdropErrorCode.backfillNoFill) {
            debugPrint('No backfill ads available');
          }

          setState(() {
            isLoaded = false;
            errorCode = error;
          });
        },
      ),
    );

    nativeAd?.load();
  }

  @override
  Widget build(BuildContext context) {
    if (isLoaded) {
      return _buildNativeAdView();
    } else if (errorCode != null) {
      return Text('Ad load failed: ${errorCode?.code}');
    } else {
      return const SizedBox.shrink();
    }
  }

  Widget _buildNativeAdView() {
    // Native ad UI composition
    return AdropNativeAdView(
      ad: nativeAd,
      child: Container(
        // ...
      ),
    );
  }
}
```

***

## Best Practices

### 1. Recreating Ads

To load a new ad, create a new `AdropNativeAd` instance.

```dart theme={null}
void resetAd() {
  setState(() {
    isLoaded = false;
  });

  nativeAd = AdropNativeAd(
    unitId: 'YOUR_UNIT_ID',
    listener: AdropNativeListener(
      onAdReceived: (ad) {
        setState(() {
          isLoaded = true;
        });
      },
    ),
  );

  nativeAd?.load();
}
```

### 2. Conditional Rendering

Display an appropriate placeholder until the ad loads.

```dart theme={null}
Widget buildNativeAd() {
  if (isLoaded && nativeAd != null) {
    return _buildNativeAdView();
  } else if (isLoading) {
    return const SizedBox(
      height: 200,
      child: Center(child: CircularProgressIndicator()),
    );
  } else {
    return const SizedBox.shrink();
  }
}
```

### 3. Responsive Layout

Configure layout to adapt to various screen sizes.

```dart theme={null}
Widget _buildNativeAdView() {
  return AdropNativeAdView(
    ad: nativeAd,
    child: LayoutBuilder(
      builder: (context, constraints) {
        return Container(
          width: constraints.maxWidth,
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // Ad content
            ],
          ),
        );
      },
    ),
  );
}
```

### 4. Null Check for Ad Properties

Native ad properties can be null, so always check them.

```dart theme={null}
Widget _buildHeadline() {
  final headline = nativeAd?.properties.headline;
  if (headline == null || headline.isEmpty) {
    return const SizedBox.shrink();
  }
  return Text(
    headline,
    style: const TextStyle(
      fontSize: 18,
      fontWeight: FontWeight.bold,
    ),
  );
}
```

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Interstitial Ads" href="/sdk/flutter/interstitial">
    Implement full-screen interstitial ads
  </Card>

  <Card title="Rewarded Ads" href="/sdk/flutter/rewarded">
    Implement rewarded ads that provide rewards
  </Card>

  <Card title="Popup Ads" href="/sdk/flutter/popup">
    Implement popup-style ads
  </Card>

  <Card title="Reference" href="/sdk/flutter/reference">
    Reference types, methods, and error codes
  </Card>
</CardGroup>
