> ## 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アプリでリワード広告を実装する方法をご案内します。

## 概要

リワード広告は、ユーザーが広告を最後まで視聴すると報酬を受け取れるフルスクリーン広告です。ゲーム内アイテムや追加コンテンツなどの報酬を提供する際に使用します。

### 主な特徴

* 画面全体を覆うフルスクリーン広告
* 動画広告対応
* 報酬イベントコールバック提供
* 報酬タイプと数量情報の伝達
* ロードと表示を分離した柔軟なタイミング制御

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

***

## AdropRewardedAd

### コンストラクタ

```dart theme={null}
AdropRewardedAd({
  required String unitId,
  AdropRewardedListener? listener,
})
```

**パラメータ**

| パラメータ      | タイプ                     | 必須 | 説明                         |
| ---------- | ----------------------- | -- | -------------------------- |
| `unitId`   | `String`                | Y  | Ad Controlコンソールで作成したユニットID |
| `listener` | `AdropRewardedListener` | N  | 広告イベントリスナー                 |

### プロパティ

| プロパティ                           | タイプ                              | 説明                                |
| ------------------------------- | -------------------------------- | --------------------------------- |
| `isLoaded`                      | `bool`                           | 広告のロード完了状態                        |
| `unitId`                        | `String`                         | 広告ユニットID                          |
| `creativeId`                    | `String`                         | クリエイティブID                         |
| `txId`                          | `String`                         | トランザクションID                        |
| `campaignId`                    | `String`                         | キャンペーンID                          |
| `destinationURL`                | `String`                         | 遷移先URL                            |
| `browserTarget`                 | `BrowserTarget?`                 | ブラウザターゲット（外部または内部）                |
| `serverSideVerificationOptions` | `ServerSideVerificationOptions?` | サーバーサイド検証オプション（userId、customData） |

### メソッド

| メソッド        | 戻り値の型          | 説明          |
| ----------- | -------------- | ----------- |
| `load()`    | `Future<void>` | 広告をロードします   |
| `show()`    | `Future<void>` | 広告を画面に表示します |
| `dispose()` | `Future<void>` | リソースを解放します  |

***

## 基本的な使い方

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

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

  @override
  State<RewardedExample> createState() => _RewardedExampleState();
}

class _RewardedExampleState extends State<RewardedExample> {
  bool isLoaded = false;
  AdropRewardedAd? rewardedAd;

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

  void _createRewardedAd() {
    rewardedAd = AdropRewardedAd(
      unitId: 'YOUR_UNIT_ID',
      listener: AdropRewardedListener(
        onAdReceived: (ad) {
          debugPrint('リワード広告ロード成功: ${ad.creativeId}');
          setState(() {
            isLoaded = true;
          });
        },
        onAdFailedToReceive: (ad, errorCode) {
          debugPrint('リワード広告ロード失敗: $errorCode');
        },
        onAdClicked: (ad) {
          debugPrint('リワード広告クリック');
        },
        onAdDidPresentFullScreen: (ad) {
          debugPrint('リワード広告表示');
        },
        onAdDidDismissFullScreen: (ad) {
          debugPrint('リワード広告閉じる');
        },
        onAdFailedToShowFullScreen: (ad, errorCode) {
          debugPrint('リワード広告表示失敗: $errorCode');
        },
        onAdEarnRewardHandler: (ad, type, amount) {
          debugPrint('報酬獲得: タイプ=$type, 数量=$amount');
          _giveReward(type, amount);
        },
      ),
    );
  }

  void _giveReward(int type, int amount) {
    // 報酬付与ロジックを実装
    // 例：コイン、アイテム、追加ライフなど
  }

  void loadAd() {
    rewardedAd?.load();
  }

  void showAd() {
    if (isLoaded) {
      rewardedAd?.show();
    }
  }

  @override
  void dispose() {
    rewardedAd?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('リワード広告サンプル')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: loadAd,
              child: const Text('広告をロード'),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: isLoaded ? showAd : null,
              child: const Text('広告を見て報酬を受け取る'),
            ),
          ],
        ),
      ),
    );
  }
}
```

***

## AdropRewardedListener

リワード広告イベントを処理するリスナーです。

### コールバック関数

```dart theme={null}
AdropRewardedListener(
  onAdReceived: (AdropAd ad) {
    // 広告ロード成功
  },
  onAdClicked: (AdropAd ad) {
    // 広告クリック
  },
  onAdImpression: (AdropAd ad) {
    // 広告表示
  },
  onAdWillPresentFullScreen: (AdropAd ad) {
    // 広告表示直前（iOS専用）
  },
  onAdDidPresentFullScreen: (AdropAd ad) {
    // 広告表示完了
  },
  onAdWillDismissFullScreen: (AdropAd ad) {
    // 広告閉じる直前（iOS専用）
  },
  onAdDidDismissFullScreen: (AdropAd ad) {
    // 広告閉じる完了
  },
  onAdFailedToReceive: (AdropAd ad, AdropErrorCode errorCode) {
    // 広告ロード失敗
  },
  onAdFailedToShowFullScreen: (AdropAd ad, AdropErrorCode errorCode) {
    // 広告表示失敗
  },
  onAdEarnRewardHandler: (AdropAd ad, int type, int amount) {
    // 報酬獲得
  },
)
```

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

| コールバック                       | 説明                  |
| ---------------------------- | ------------------- |
| `onAdReceived`               | 広告ロード成功時に呼び出し       |
| `onAdClicked`                | 広告クリック時に呼び出し        |
| `onAdImpression`             | 広告表示時に呼び出し          |
| `onAdWillPresentFullScreen`  | 広告表示直前に呼び出し（iOS専用）  |
| `onAdDidPresentFullScreen`   | 広告表示後に呼び出し          |
| `onAdWillDismissFullScreen`  | 広告閉じる直前に呼び出し（iOS専用） |
| `onAdDidDismissFullScreen`   | 広告閉じた後に呼び出し         |
| `onAdFailedToReceive`        | 広告ロード失敗時に呼び出し       |
| `onAdFailedToShowFullScreen` | 広告表示失敗時に呼び出し        |
| `onAdEarnRewardHandler`      | 報酬獲得時に呼び出し          |

***

## 報酬処理

### 報酬コールバック

`onAdEarnRewardHandler`コールバックで報酬情報を受け取り処理します。

```dart theme={null}
onAdEarnRewardHandler: (ad, type, amount) {
  debugPrint('報酬獲得: タイプ=$type, 数量=$amount');

  // 報酬タイプに応じた処理
  switch (type) {
    case 1:
      // コイン報酬
      _addCoins(amount);
      break;
    case 2:
      // アイテム報酬
      _addItem(amount);
      break;
    default:
      // デフォルト報酬
      _giveDefaultReward(amount);
  }
}
```

### 報酬付与タイミング

<Warning>
  報酬は必ず`onAdEarnRewardHandler`コールバックで付与してください。広告が完全に視聴される前に閉じられた場合、このコールバックは呼び出されません。
</Warning>

```dart theme={null}
class RewardManager {
  bool _rewardEarned = false;

  void setupRewardedAd() {
    rewardedAd = AdropRewardedAd(
      unitId: 'YOUR_UNIT_ID',
      listener: AdropRewardedListener(
        onAdEarnRewardHandler: (ad, type, amount) {
          // 報酬フラグ設定
          _rewardEarned = true;
        },
        onAdDidDismissFullScreen: (ad) {
          // 広告閉じた後に報酬付与確認
          if (_rewardEarned) {
            _giveReward();
            _rewardEarned = false;
          }
        },
      ),
    );
  }
}
```

***

## 広告の再作成

リワード広告は一回限りです。一度表示された広告は再表示できないため、新しい広告をロードするにはインスタンスを再作成する必要があります。

```dart theme={null}
class _RewardedState extends State<RewardedWidget> {
  bool isLoaded = false;
  bool isShown = false;
  AdropRewardedAd? rewardedAd;

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

  void _createRewardedAd() {
    rewardedAd?.dispose();
    rewardedAd = AdropRewardedAd(
      unitId: 'YOUR_UNIT_ID',
      listener: AdropRewardedListener(
        onAdReceived: (ad) {
          setState(() {
            isLoaded = true;
          });
        },
        onAdDidPresentFullScreen: (ad) {
          setState(() {
            isShown = true;
          });
        },
        onAdEarnRewardHandler: (ad, type, amount) {
          _giveReward(type, amount);
        },
        onAdDidDismissFullScreen: (ad) {
          // 広告閉じた後に新しい広告を準備
          _createRewardedAd();
          rewardedAd?.load();
        },
      ),
    );

    setState(() {
      isLoaded = false;
      isShown = false;
    });
  }

  @override
  void dispose() {
    rewardedAd?.dispose();
    super.dispose();
  }
}
```

***

## エラー処理

```dart theme={null}
class _RewardedState extends State<RewardedWidget> {
  bool isLoaded = false;
  AdropErrorCode? errorCode;
  AdropRewardedAd? rewardedAd;

  void _createRewardedAd() {
    rewardedAd = AdropRewardedAd(
      unitId: 'YOUR_UNIT_ID',
      listener: AdropRewardedListener(
        onAdReceived: (ad) {
          setState(() {
            isLoaded = true;
            errorCode = null;
          });
        },
        onAdFailedToReceive: (ad, error) {
          setState(() {
            isLoaded = false;
            errorCode = error;
          });
          _handleError(error);
        },
        onAdFailedToShowFullScreen: (ad, error) {
          setState(() {
            errorCode = error;
          });
          _handleError(error);
        },
      ),
    );
  }

  void _handleError(AdropErrorCode error) {
    switch (error) {
      case AdropErrorCode.network:
        debugPrint('ネットワークエラーが発生しました。');
        break;
      case AdropErrorCode.adNoFill:
        debugPrint('表示する広告がありません。');
        break;
      case AdropErrorCode.adShown:
        debugPrint('すでに表示された広告です。');
        break;
      default:
        debugPrint('エラー発生: ${error.code}');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: rewardedAd?.load,
          child: const Text('広告をロード'),
        ),
        ElevatedButton(
          onPressed: isLoaded ? rewardedAd?.show : null,
          child: const Text('広告を見て報酬を受け取る'),
        ),
        if (errorCode != null)
          Text(
            'エラー: ${errorCode?.code}',
            style: const TextStyle(color: Colors.red),
          ),
      ],
    );
  }
}
```

***

## ベストプラクティス

### 1. 事前ロード

ユーザーが報酬を要求した時にすぐに広告を表示できるよう事前にロードします。

```dart theme={null}
class GameScreen extends StatefulWidget {
  @override
  State<GameScreen> createState() => _GameScreenState();
}

class _GameScreenState extends State<GameScreen> {
  AdropRewardedAd? rewardedAd;
  bool isAdReady = false;

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

  void _preloadAd() {
    rewardedAd = AdropRewardedAd(
      unitId: 'YOUR_UNIT_ID',
      listener: AdropRewardedListener(
        onAdReceived: (ad) {
          setState(() {
            isAdReady = true;
          });
        },
        onAdEarnRewardHandler: (ad, type, amount) {
          _giveReward(type, amount);
        },
        onAdDidDismissFullScreen: (ad) {
          // 次の広告を事前ロード
          _preloadAd();
        },
      ),
    );
    rewardedAd?.load();
  }

  void showRewardedAd() {
    if (isAdReady) {
      rewardedAd?.show();
      setState(() {
        isAdReady = false;
      });
    }
  }

  @override
  void dispose() {
    rewardedAd?.dispose();
    super.dispose();
  }
}
```

### 2. ユーザーへの報酬案内

広告視聴前に受け取れる報酬を明確に案内します。

```dart theme={null}
void showRewardDialog() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('報酬を受け取る'),
      content: const Text('広告を視聴すると100コインを受け取れます！'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('キャンセル'),
        ),
        ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
            showRewardedAd();
          },
          child: const Text('広告を見る'),
        ),
      ],
    ),
  );
}
```

### 3. 広告の準備状態表示

広告が準備できた時のみ報酬ボタンを有効にします。

```dart theme={null}
Widget buildRewardButton() {
  return ElevatedButton.icon(
    onPressed: isAdReady ? showRewardedAd : null,
    icon: const Icon(Icons.play_circle_outline),
    label: Text(isAdReady ? '広告を見て報酬を受け取る' : '広告ロード中...'),
    style: ElevatedButton.styleFrom(
      backgroundColor: isAdReady ? Colors.green : Colors.grey,
    ),
  );
}
```

### 4. 報酬付与の信頼性

報酬付与はサーバーで検証することをお勧めします。

```dart theme={null}
onAdEarnRewardHandler: (ad, type, amount) async {
  try {
    // サーバーに報酬付与をリクエスト
    await _api.grantReward(
      userId: currentUserId,
      transactionId: ad.txId,
      rewardType: type,
      amount: amount,
    );

    // ローカル状態を更新
    _updateLocalBalance(amount);
  } catch (e) {
    debugPrint('報酬付与失敗: $e');
    // リトライまたはエラー処理
  }
}
```

### 5. リソース管理

使用しない広告インスタンスは必ず解放してください。

```dart theme={null}
@override
void dispose() {
  rewardedAd?.dispose();
  super.dispose();
}
```

***

## 完全な例

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

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

  @override
  State<RewardedAdPage> createState() => _RewardedAdPageState();
}

class _RewardedAdPageState extends State<RewardedAdPage> {
  bool isLoaded = false;
  bool isLoading = false;
  bool isShown = false;
  int coins = 0;
  AdropErrorCode? errorCode;
  AdropRewardedAd? rewardedAd;

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

  void _createRewardedAd() {
    rewardedAd?.dispose();
    rewardedAd = AdropRewardedAd(
      unitId: 'PUBLIC_TEST_UNIT_ID_REWARDED', // テストユニットID
      listener: AdropRewardedListener(
        onAdReceived: (ad) {
          debugPrint('広告ロード成功');
          debugPrint('クリエイティブID: ${ad.creativeId}');
          debugPrint('キャンペーンID: ${ad.campaignId}');
          setState(() {
            isLoaded = true;
            isLoading = false;
            errorCode = null;
          });
        },
        onAdClicked: (ad) {
          debugPrint('広告クリック: ${ad.destinationURL}');
        },
        onAdImpression: (ad) {
          debugPrint('広告表示: ${ad.creativeId}');
        },
        onAdWillPresentFullScreen: (ad) {
          debugPrint('広告表示予定');
        },
        onAdDidPresentFullScreen: (ad) {
          debugPrint('広告表示完了');
          setState(() {
            isShown = true;
          });
        },
        onAdWillDismissFullScreen: (ad) {
          debugPrint('広告閉じる予定');
        },
        onAdDidDismissFullScreen: (ad) {
          debugPrint('広告閉じる完了');
          // 新しい広告を準備
          _createRewardedAd();
        },
        onAdFailedToReceive: (ad, error) {
          debugPrint('広告ロード失敗: $error');
          setState(() {
            isLoaded = false;
            isLoading = false;
            errorCode = error;
          });
        },
        onAdFailedToShowFullScreen: (ad, error) {
          debugPrint('広告表示失敗: $error');
          setState(() {
            errorCode = error;
          });
        },
        onAdEarnRewardHandler: (ad, type, amount) {
          debugPrint('報酬獲得: タイプ=$type, 数量=$amount');
          setState(() {
            coins += amount;
          });
          _showRewardNotification(amount);
        },
      ),
    );

    setState(() {
      isLoaded = false;
      isShown = false;
      errorCode = null;
    });
  }

  void loadAd() {
    setState(() {
      isLoading = true;
      errorCode = null;
    });
    rewardedAd?.load();
  }

  void showAd() {
    if (isLoaded) {
      rewardedAd?.show();
    }
  }

  void _showRewardNotification(int amount) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('$amountコインを獲得しました！'),
        backgroundColor: Colors.green,
        duration: const Duration(seconds: 2),
      ),
    );
  }

  @override
  void dispose() {
    rewardedAd?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('リワード広告サンプル'),
      ),
      body: SafeArea(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // コイン表示
              Container(
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  color: Colors.amber.shade100,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const Icon(Icons.monetization_on, color: Colors.amber),
                    const SizedBox(width: 8),
                    Text(
                      '$coinsコイン',
                      style: const TextStyle(
                        fontSize: 24,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ],
                ),
              ),
              const SizedBox(height: 32),

              // ロードボタン
              ElevatedButton(
                onPressed: isLoading ? null : loadAd,
                child: Text(isLoading ? 'ロード中...' : '広告をロード'),
              ),
              const SizedBox(height: 16),

              // 表示ボタン
              ElevatedButton.icon(
                onPressed: isLoaded ? showAd : null,
                icon: const Icon(Icons.play_circle_outline),
                label: const Text('広告を見て報酬を受け取る'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: isLoaded ? Colors.green : Colors.grey,
                ),
              ),
              const SizedBox(height: 16),

              // リセットボタン
              TextButton(
                onPressed: (isShown || errorCode != null)
                    ? () => _createRewardedAd()
                    : null,
                child: const Text('リセット'),
              ),
              const SizedBox(height: 24),

              // 状態表示
              Text('ロード済み: ${isLoaded ? "はい" : "いいえ"}'),
              Text('表示済み: ${isShown ? "はい" : "いいえ"}'),

              // エラー表示
              if (errorCode != null) ...[
                const SizedBox(height: 16),
                Text(
                  'エラー: ${errorCode?.code}',
                  style: const TextStyle(color: Colors.red),
                ),
              ],
            ],
          ),
        ),
      ),
    );
  }
}
```

***

## サーバーサイド検証（SSV）

サーバーサイド検証を使用すると、サーバーで報酬の付与を安全に検証できます。広告がロードされる際にサーバー間コールバックに`userId`と`customData`を含めるには、`ServerSideVerificationOptions`を設定してください。

<Note>
  サーバーでSSVコールバックを受信するには、コンソールの\*\*\[管理] > \[連携] > リワード広告SSV\*\*でコールバックURLを登録してください。ここで設定した`userId`と`customData`は、そのURLへ送信される暗号化ペイロードに含まれます。ペイロードの復号と設定方法は[リワード広告SSVガイド](/ja/integrations/ssv)をご参照ください。
</Note>

### セットアップ

`load()`を呼び出す前に`serverSideVerificationOptions`を設定してください。

```dart theme={null}
final rewardedAd = AdropRewardedAd(
  unitId: 'YOUR_UNIT_ID',
  listener: AdropRewardedListener(
    onAdReceived: (ad) {
      setState(() {
        isLoaded = true;
      });
    },
    onAdEarnRewardHandler: (ad, type, amount) {
      debugPrint('報酬獲得: type=$type, amount=$amount');
      // サーバーで報酬を検証
    },
  ),
);

// load()を呼び出す前にSSVオプションを設定
rewardedAd.serverSideVerificationOptions = ServerSideVerificationOptions(
  userId: 'user_123',
  customData: 'level_5_reward',
);

await rewardedAd.load();
```

### ServerSideVerificationOptions

| プロパティ        | タイプ       | 説明                       |
| ------------ | --------- | ------------------------ |
| `userId`     | `String?` | サーバーサイド検証用のユーザー識別子       |
| `customData` | `String?` | サーバーコールバックに含めるカスタムデータ文字列 |

### 完全な例

```dart theme={null}
class _RewardedSSVState extends State<RewardedSSVWidget> {
  bool isLoaded = false;
  AdropRewardedAd? rewardedAd;

  void _createRewardedAd() {
    rewardedAd?.dispose();
    rewardedAd = AdropRewardedAd(
      unitId: 'YOUR_UNIT_ID',
      listener: AdropRewardedListener(
        onAdReceived: (ad) {
          setState(() {
            isLoaded = true;
          });
        },
        onAdEarnRewardHandler: (ad, type, amount) {
          // サーバーで報酬を検証
          _verifyRewardOnServer(
            transactionId: ad.txId,
            rewardType: type,
            amount: amount,
          );
        },
        onAdDidDismissFullScreen: (ad) {
          // 次の広告を準備
          _createRewardedAd();
          rewardedAd?.load();
        },
      ),
    );

    // SSVオプションを設定
    rewardedAd!.serverSideVerificationOptions = ServerSideVerificationOptions(
      userId: currentUserId,
      customData: 'reward_context_data',
    );
  }

  Future<void> _verifyRewardOnServer({
    required String transactionId,
    required int rewardType,
    required int amount,
  }) async {
    // サーバーに報酬検証リクエストを送信
    try {
      await api.verifyReward(
        transactionId: transactionId,
        rewardType: rewardType,
        amount: amount,
      );
      _updateLocalBalance(amount);
    } catch (e) {
      debugPrint('報酬検証失敗: $e');
    }
  }

  @override
  void dispose() {
    rewardedAd?.dispose();
    super.dispose();
  }
}
```

<Warning>
  `serverSideVerificationOptions`は`load()`を呼び出す前に設定する必要があります。`load()`の後に設定したオプションは、その広告リクエストには適用されません。
</Warning>

***

## バックフィル広告

バックフィル広告が有効な場合、ダイレクト広告がない時に自動的にバックフィル広告がロードされます。エラーコードを使ってバックフィル関連の状態を処理できます。

```dart theme={null}
rewardedAd = AdropRewardedAd(
  unitId: 'YOUR_UNIT_ID',
  listener: AdropRewardedListener(
    onAdReceived: (ad) {
      debugPrint('広告ロード完了: ${ad.creativeId}');
    },
    onAdFailedToReceive: (ad, errorCode) {
      switch (errorCode) {
        case AdropErrorCode.adNoFill:
          debugPrint('ダイレクト広告なし');
          break;
        case AdropErrorCode.backfillNoFill:
          debugPrint('バックフィル広告もありません');
          break;
        default:
          debugPrint('広告ロード失敗: ${errorCode.code}');
      }
    },
  ),
);
```

<Note>
  バックフィル広告を使用するには、ネイティブプラットフォームにバックフィル依存性を追加する必要があります。[はじめに](/ja/sdk/flutter/overview)を参照してください。
</Note>

***

## 次のステップ

<CardGroup cols={2}>
  <Card title="ポップアップ広告" href="/ja/sdk/flutter/popup">
    ポップアップ形式で表示される広告の実装
  </Card>

  <Card title="ターゲティング" href="/ja/sdk/flutter/targeting">
    パーソナライズ広告のためのターゲティング設定
  </Card>

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