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

# ネイティブ広告

> Flutterアプリでネイティブ広告を実装する方法をご案内します。

## 概要

ネイティブ広告は、アプリのコンテンツと自然に調和するようにカスタマイズできる広告です。`AdropNativeAd`と`AdropNativeAdView`を使用して、アプリのUIに合わせて広告を構成できます。

### 主な特徴

* アプリデザインに合わせたカスタムレイアウト構成が可能
* ヘッドライン、本文、CTAボタン、プロフィールなど様々な要素を提供
* 画像およびHTMLクリエイティブをサポート
* カスタムクリック処理をサポート
* バックフィル広告をサポート

<Note>
  開発環境ではテストユニットIDを使用してください: `PUBLIC_TEST_UNIT_ID_NATIVE`
</Note>

***

## AdropNativeAd

### コンストラクタ

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

**パラメータ**

| パラメータ                        | タイプ                      | 必須 | 説明                                                                       |
| ---------------------------- | ------------------------ | -- | ------------------------------------------------------------------------ |
| `unitId`                     | `String`                 | Y  | Ad Controlコンソールで作成したユニットID                                               |
| `useCustomClick`             | `bool`                   | N  | カスタムクリック処理を使用するかどうか（デフォルト: `false`）                                      |
| `preferredAdChoicesPosition` | `AdropAdChoicesPosition` | N  | バックフィルネイティブ広告のAdChoicesマーク表示位置（デフォルト: `AdropAdChoicesPosition.topRight`） |
| `listener`                   | `AdropNativeListener`    | N  | 広告イベントリスナー                                                               |

### プロパティ

| プロパティ            | タイプ                     | 説明                 |
| ---------------- | ----------------------- | ------------------ |
| `isLoaded`       | `bool`                  | 広告のロード完了状態         |
| `unitId`         | `String`                | 広告ユニットID           |
| `creativeId`     | `String`                | クリエイティブID          |
| `txId`           | `String`                | トランザクションID         |
| `campaignId`     | `String`                | キャンペーンID           |
| `destinationURL` | `String`                | 遷移先URL             |
| `properties`     | `AdropNativeProperties` | ネイティブ広告プロパティ       |
| `creativeSize`   | `CreativeSize`          | クリエイティブサイズ         |
| `isBackfilled`   | `bool`                  | バックフィル広告かどうか       |
| `browserTarget`  | `BrowserTarget?`        | ブラウザターゲット（外部または内部） |

### メソッド

| メソッド     | 戻り値の型          | 説明        |
| -------- | -------------- | --------- |
| `load()` | `Future<void>` | 広告をロードします |

<Note>
  他の広告タイプ（`AdropInterstitialAd`、`AdropRewardedAd`、`AdropPopupAd`）とは異なり、`AdropNativeAd`は`dispose()`メソッドを提供しません。
  新しい広告をロードするには、新しい`AdropNativeAd`インスタンスを作成してください。以前のインスタンスのリソースはガベージコレクターによって自動的に解放されます。
</Note>

***

## AdropNativeAdView

ネイティブ広告を画面に表示するウィジェットです。

### コンストラクタ

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

**パラメータ**

| パラメータ   | タイプ              | 必須 | 説明                  |
| ------- | ---------------- | -- | ------------------- |
| `ad`    | `AdropNativeAd?` | Y  | ロードされたネイティブ広告オブジェクト |
| `child` | `Widget`         | Y  | 広告コンテンツを表示する子ウィジェット |

***

## AdropNativeProperties

ネイティブ広告のコンテンツプロパティです。

### プロパティ

| プロパティ            | タイプ                   | 説明               |
| ---------------- | --------------------- | ---------------- |
| `headline`       | `String?`             | 広告タイトル           |
| `body`           | `String?`             | 広告本文             |
| `creative`       | `String?`             | HTMLクリエイティブコンテンツ |
| `asset`          | `String?`             | 画像アセットURL        |
| `destinationURL` | `String?`             | クリック時の遷移先URL     |
| `callToAction`   | `String?`             | CTAボタンテキスト       |
| `profile`        | `AdropNativeProfile?` | 広告主プロフィール情報      |
| `extra`          | `Map<String, String>` | 追加のカスタムフィールド     |
| `isBackfilled`   | `bool`                | バックフィル広告かどうか     |

### AdropNativeProfile

| プロパティ         | タイプ       | 説明         |
| ------------- | --------- | ---------- |
| `displayName` | `String?` | 広告主名       |
| `displayLogo` | `String?` | 広告主ロゴ画像URL |

***

## 基本的な使い方

```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('ネイティブ広告受信成功: ${ad.creativeId}');
          setState(() {
            isLoaded = true;
          });
        },
        onAdClicked: (ad) {
          debugPrint('ネイティブ広告クリック: ${ad.creativeId}');
        },
        onAdImpression: (ad) {
          debugPrint('ネイティブ広告表示: ${ad.creativeId}');
        },
        onAdFailedToReceive: (ad, errorCode) {
          debugPrint('ネイティブ広告受信失敗: $errorCode');
          setState(() {
            isLoaded = false;
          });
        },
      ),
    );

    // 広告をロード
    nativeAd?.load();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('ネイティブ広告サンプル')),
      body: SingleChildScrollView(
        child: Column(
          children: [
            // メインコンテンツ
            const Padding(
              padding: EdgeInsets.all(16),
              child: Text('メインコンテンツ'),
            ),
            // ネイティブ広告
            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: [
            // 広告主プロフィール
            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),
            // ヘッドライン
            if (nativeAd?.properties.headline != null)
              Text(
                nativeAd!.properties.headline!,
                style: const TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
            const SizedBox(height: 4),
            // 本文
            if (nativeAd?.properties.body != null)
              Text(nativeAd!.properties.body!),
            const SizedBox(height: 8),
            // 画像アセット
            if (nativeAd?.properties.asset != null)
              Image.network(
                nativeAd!.properties.asset!,
                width: double.infinity,
                fit: BoxFit.cover,
              ),
            const SizedBox(height: 8),
            // CTAボタン
            if (nativeAd?.properties.callToAction != null)
              ElevatedButton(
                onPressed: () {},
                child: Text(nativeAd!.properties.callToAction!),
              ),
          ],
        ),
      ),
    );
  }
}
```

***

## AdropNativeListener

ネイティブ広告イベントを処理するリスナーです。

### コールバック関数

```dart theme={null}
AdropNativeListener(
  onAdReceived: (AdropNativeAd ad) {
    // 広告受信成功
  },
  onAdClicked: (AdropNativeAd ad) {
    // 広告クリック
  },
  onAdImpression: (AdropNativeAd ad) {
    // 広告表示
  },
  onAdFailedToReceive: (AdropNativeAd ad, AdropErrorCode errorCode) {
    // 広告受信失敗
  },
  onAdVideoStart: (AdropNativeAd ad) {
    // 動画広告の再生開始
  },
  onAdVideoEnd: (AdropNativeAd ad) {
    // 動画広告の再生終了
  },
)
```

### コールバックの説明

| コールバック                | 説明              |
| --------------------- | --------------- |
| `onAdReceived`        | 広告受信成功時に呼び出し    |
| `onAdClicked`         | 広告クリック時に呼び出し    |
| `onAdImpression`      | 広告表示時に呼び出し      |
| `onAdFailedToReceive` | 広告受信失敗時に呼び出し    |
| `onAdVideoStart`      | 動画広告の再生開始時に呼び出し |
| `onAdVideoEnd`        | 動画広告の再生終了時に呼び出し |

***

## カスタムクリック処理

動画クリエイティブやカスタムクリック動作が必要な場合は`useCustomClick`を使用します。

```dart theme={null}
nativeAd = AdropNativeAd(
  unitId: 'YOUR_UNIT_ID',
  useCustomClick: true, // カスタムクリックを有効化
  listener: AdropNativeListener(
    onAdReceived: (ad) {
      setState(() {
        isLoaded = true;
      });
    },
    onAdClicked: (ad) {
      // カスタムクリック動作を処理
      debugPrint('広告クリック: ${ad.destinationURL}');
    },
  ),
);
```

`useCustomClick`が`true`の場合、子ウィジェットのクリックイベントが広告クリックとして処理されます。

***

## HTMLクリエイティブの表示

ネイティブ広告にHTMLクリエイティブが含まれている場合、WebViewを使用して表示できます。

<Warning>
  **ネイティブ広告の動画クリエイティブ**は、必ず`properties.creative`（HTMLペイロード）をWebViewでレンダリングする必要があります。`properties.asset`を`video_player`などの独自プレイヤーに直接渡すと、SDKの動画トラッキングパイプラインを経由しなくなり、**VTRが計測されず**、`onAdVideoStart` / `onAdVideoEnd`コールバックも呼び出されません。[動画トラッキングとVTR](#動画トラッキングと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();

    // WebViewコントローラーの初期化
    webViewController = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted);

    _createNativeAd();
  }

  void _createNativeAd() {
    nativeAd = AdropNativeAd(
      unitId: 'YOUR_UNIT_ID',
      useCustomClick: true,
      listener: AdropNativeListener(
        onAdReceived: (ad) {
          // HTMLクリエイティブをロード
          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: [
            // プロフィール
            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),
            // ヘッドライン
            if (nativeAd?.properties.headline != null)
              Text(
                nativeAd!.properties.headline!,
                style: const TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
            const SizedBox(height: 8),
            // HTMLクリエイティブ
            SizedBox(
              width: MediaQuery.of(context).size.width,
              height: 300,
              child: WebViewWidget(controller: webViewController),
            ),
            // CTAボタン
            if (nativeAd?.properties.callToAction != null)
              Padding(
                padding: const EdgeInsets.only(top: 8),
                child: ElevatedButton(
                  onPressed: () {},
                  child: Text(nativeAd!.properties.callToAction!),
                ),
              ),
          ],
        ),
      ),
    );
  }
}
```

<Note>
  HTMLクリエイティブを使用するには、`webview_flutter`パッケージを追加する必要があります。

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

***

## バックフィル広告の処理

ネイティブ広告がバックフィル広告の場合、`isBackfilled`プロパティで確認して処理できます。

```dart theme={null}
Widget _buildCreativeView() {
  if (nativeAd?.isBackfilled == true) {
    // バックフィル広告: 画像アセットを使用
    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) {
    // 通常広告: HTMLクリエイティブを使用
    return SizedBox(
      width: double.infinity,
      height: 300,
      child: WebViewWidget(controller: webViewController),
    );
  } else {
    return const SizedBox.shrink();
  }
}
```

### AdChoicesの位置調整

バックフィルネイティブ広告で、AdChoicesマークを表示するコーナーを指定できます。`AdropNativeAd`コンストラクタで`preferredAdChoicesPosition`を設定してください。

```dart theme={null}
final nativeAd = AdropNativeAd(
  unitId: 'YOUR_UNIT_ID',
  preferredAdChoicesPosition: AdropAdChoicesPosition.bottomRight,
  listener: AdropNativeListener(
    onAdReceived: (ad) => debugPrint('受信完了'),
    onAdFailedToReceive: (ad, errorCode) => debugPrint('失敗: $errorCode'),
  ),
);

await nativeAd.load();
```

| 値                                    | 説明        |
| ------------------------------------ | --------- |
| `AdropAdChoicesPosition.topLeft`     | 左上        |
| `AdropAdChoicesPosition.topRight`    | 右上（デフォルト） |
| `AdropAdChoicesPosition.bottomLeft`  | 左下        |
| `AdropAdChoicesPosition.bottomRight` | 右下        |

<Note>
  この設定はバックフィルネットワークに渡される希望位置のヒントで、バックフィルネイティブ広告にのみ適用されます — ダイレクト広告には影響しません。バックフィルネットワークがポリシー上別の位置に表示する場合があります。
</Note>

***

## 動画トラッキングとVTR

Adropはネイティブ広告の動画指標 — VTR（動画完了率）、`onAdVideoStart`、`onAdVideoEnd` — を、SDKメディアコンテナ内で動画がレンダリングされる場合にのみ収集します。Flutterでは[HTMLクリエイティブの表示](#htmlクリエイティブの表示)セクションのように、`properties.creative`（HTMLペイロード）をWebViewでレンダリングする方式がこれに該当します。

`properties.asset`フィールドは元の画像または動画ファイルのURLを返します。これはサムネイル表示や媒体社独自分析などの**補助メタデータ**として公開されており、再生を直接行うためのものではありません。

<Warning>
  動画ネイティブ広告の`properties.asset` URLを独自の動画プレイヤー（`video_player`、`chewie`またはサードパーティプレイヤー）に直接渡さないでください。SDKメディアコンテナを経由しないと以下の現象が発生します。

  * 該当面で**VTR（動画完了率）が収集されません。**
  * `onAdVideoStart` / `onAdVideoEnd`コールバックが**呼び出されません。**
  * 該当ユニットの動画パフォーマンス集計が欠落または0として報告されます。

  クリック（`onAdClicked`）とインプレッション（`onAdImpression`）トラッキングは`AdropNativeAdView`に接続されているため引き続き動作しますが、動画関連シグナルは失われます。
</Warning>

### 推奨パターン

動画クリエイティブはWebView + HTMLクリエイティブパターンを使用してください。

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

WebViewを`AdropNativeAdView`内部に配置し、クリックとインプレッショントラッキングが広告コンテナに接続されたまま維持されるようにしてください。

<Note>
  特定の面がどうしても独自プレイヤーを使う必要があり、VTRが計測されないことを許容する場合、広告を`AdropNativeAdView`にバインドしたままにすることで、クリックとインプレッションのアトリビューションは引き続き確保できます。ただしその面を社内レポートで**非VTRインベントリ**として分類し、SDKが計測した動画パフォーマンスと混在させないでください。
</Note>

***

## 追加フィールドの使用

媒体社が定義した追加フィールドは`extra`マップからアクセスできます。

```dart theme={null}
onAdReceived: (ad) {
  // 追加フィールドにアクセス
  final customField = ad.properties.extra['customFieldKey'];
  if (customField != null) {
    debugPrint('カスタムフィールド: $customField');
  }

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

***

## エラー処理

### バックフィル関連エラー

バックフィル広告が設定されている場合、直接広告とバックフィル広告の両方がない場合は`backfillNoFill`エラーコードが返されます。

```dart theme={null}
onAdFailedToReceive: (ad, errorCode) {
  if (errorCode == AdropErrorCode.backfillNoFill) {
    debugPrint('バックフィル広告もありません');
  }
}
```

### 一般的なエラー処理

```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) {
          // バックフィル関連エラー処理
          if (error == AdropErrorCode.backfillNoFill) {
            debugPrint('バックフィル広告もありません');
          }

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

    nativeAd?.load();
  }

  @override
  Widget build(BuildContext context) {
    if (isLoaded) {
      return _buildNativeAdView();
    } else if (errorCode != null) {
      return Text('広告ロード失敗: ${errorCode?.code}');
    } else {
      return const SizedBox.shrink();
    }
  }

  Widget _buildNativeAdView() {
    // ネイティブ広告UIの構成
    return AdropNativeAdView(
      ad: nativeAd,
      child: Container(
        // ...
      ),
    );
  }
}
```

***

## ベストプラクティス

### 1. 広告の再生成

新しい広告をロードするには、新しい`AdropNativeAd`インスタンスを生成します。

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

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

  nativeAd?.load();
}
```

### 2. 条件付きレンダリング

広告がロードされるまで適切なプレースホルダーを表示します。

```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. レスポンシブレイアウト

様々な画面サイズに対応できるようにレイアウトを構成します。

```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: [
              // 広告コンテンツ
            ],
          ),
        );
      },
    ),
  );
}
```

### 4. 広告プロパティのnullチェック

ネイティブ広告プロパティはnullの可能性があるため、常に確認します。

```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,
    ),
  );
}
```

***

## 次のステップ

<CardGroup cols={2}>
  <Card title="インタースティシャル広告" href="/ja/sdk/flutter/interstitial">
    画面全体を覆うインタースティシャル広告を実装する
  </Card>

  <Card title="リワード広告" href="/ja/sdk/flutter/rewarded">
    報酬を提供するリワード広告を実装する
  </Card>

  <Card title="ポップアップ広告" href="/ja/sdk/flutter/popup">
    ポップアップ形式で表示される広告を実装する
  </Card>

  <Card title="リファレンス" href="/ja/sdk/flutter/reference">
    タイプ、メソッド、エラーコードを参照する
  </Card>
</CardGroup>
