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

# 보상형 광고

> Android 앱에서 보상형 동영상 광고를 구현하는 방법을 안내합니다.

## 개요

보상형 광고는 사용자가 동영상 광고를 끝까지 시청하면 게임 내 재화, 생명, 힌트 등의 보상을 제공하는 전면 동영상 광고입니다.

### 주요 특징

* 전면 화면을 차지하는 동영상 광고
* 사용자가 광고를 완전히 시청한 경우에만 보상 제공
* 사용자가 직접 광고 시청을 선택 (예: "동영상 보고 생명 받기" 버튼)
* 보상 타입과 수량을 설정 가능

<Note>
  개발 환경에서는 테스트 유닛 ID를 사용하세요: `PUBLIC_TEST_UNIT_ID_REWARDED`
</Note>

***

## 구현 방법

보상형 광고는 다음과 같은 단계로 구현합니다:

1. **AdropRewardedAd 인스턴스 생성** - 유닛 ID로 광고 객체 생성
2. **Listener 설정** - 광고 이벤트 처리를 위한 리스너 지정
3. **광고 로드** - `load()` 메서드로 광고 요청
4. **광고 표시** - `show()` 메서드로 광고 표시 및 보상 처리

***

## 기본 구현

### Kotlin 예제

```kotlin theme={null}
import io.adrop.ads.rewardedAd.AdropRewardedAd
import io.adrop.ads.rewardedAd.AdropRewardedAdListener
import io.adrop.ads.model.AdropErrorCode

class RewardedActivity : AppCompatActivity() {
    private var rewardedAd: AdropRewardedAd? = null

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

        // 1. AdropRewardedAd 인스턴스 생성
        rewardedAd = AdropRewardedAd(this, "YOUR_UNIT_ID")

        // 2. Listener 설정
        rewardedAd?.rewardedAdListener = object : AdropRewardedAdListener {
            // 필수: 광고 수신 성공
            override fun onAdReceived(ad: AdropRewardedAd) {
                Log.d(TAG, "광고 수신 완료")
                // 광고 시청 버튼 활성화 등
            }

            // 필수: 광고 수신 실패
            override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
                Log.d(TAG, "광고 수신 실패: $errorCode")
                // 광고 시청 버튼 비활성화 유지 등
            }

            // 선택: 광고 노출
            override fun onAdImpression(ad: AdropRewardedAd) {
                Log.d(TAG, "광고 노출됨")
            }

            // 선택: 광고 클릭
            override fun onAdClicked(ad: AdropRewardedAd) {
                Log.d(TAG, "광고 클릭됨")
            }

            // 선택: 광고 화면 표시 직전
            override fun onAdWillPresentFullScreen(ad: AdropRewardedAd) {
                Log.d(TAG, "광고 화면 표시 예정")
                // 배경 음악 일시정지 등
            }

            // 선택: 광고 화면 표시 완료
            override fun onAdDidPresentFullScreen(ad: AdropRewardedAd) {
                Log.d(TAG, "광고 화면 표시 완료")
            }

            // 선택: 광고 화면 닫기 직전
            override fun onAdWillDismissFullScreen(ad: AdropRewardedAd) {
                Log.d(TAG, "광고 화면 닫기 예정")
            }

            // 선택: 광고 화면 닫기 완료
            override fun onAdDidDismissFullScreen(ad: AdropRewardedAd) {
                Log.d(TAG, "광고 화면 닫힘")
                // 배경 음악 재개 등

                // 다음 광고 미리 로드
                rewardedAd?.load()
            }

            // 선택: 광고 표시 실패
            override fun onAdFailedToShowFullScreen(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
                Log.d(TAG, "광고 표시 실패: $errorCode")
            }
        }

        // 3. 광고 로드
        rewardedAd?.load()

        // 광고 시청 버튼 클릭 처리
        findViewById<Button>(R.id.btnShowRewardedAd).setOnClickListener {
            showRewardedAd()
        }
    }

    private fun showRewardedAd() {
        // 광고가 로드되었는지 확인
        if (rewardedAd?.isLoaded != true) {
            Log.d(TAG, "광고가 아직 로드되지 않았습니다")
            return
        }

        // 4. 광고 표시 및 보상 처리
        rewardedAd?.show(this) { type, amount ->
            // 보상 지급 처리
            Log.d(TAG, "보상 획득 - 타입: $type, 수량: $amount")
            grantReward(type, amount)
        }
    }

    private fun grantReward(type: Int, amount: Int) {
        // 사용자에게 보상 지급
        // 예: 게임 재화 추가, 생명 회복 등

        // ⚠️ 중요: 서버에서 보상을 검증하는 것을 권장합니다
        // 클라이언트에서만 보상을 처리하면 부정행위가 가능합니다
    }

    override fun onDestroy() {
        super.onDestroy()
        rewardedAd?.destroy()
        rewardedAd = null
    }

    companion object {
        private const val TAG = "RewardedActivity"
    }
}
```

### Java 예제

```java theme={null}
import io.adrop.ads.rewardedAd.AdropRewardedAd;
import io.adrop.ads.rewardedAd.AdropRewardedAdListener;
import io.adrop.ads.model.AdropErrorCode;

public class RewardedActivity extends AppCompatActivity {
    private static final String TAG = "RewardedActivity";
    private AdropRewardedAd rewardedAd;

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

        // 1. AdropRewardedAd 인스턴스 생성
        rewardedAd = new AdropRewardedAd(this, "YOUR_UNIT_ID");

        // 2. Listener 설정
        rewardedAd.setRewardedAdListener(new AdropRewardedAdListener() {
            // 필수: 광고 수신 성공
            @Override
            public void onAdReceived(@NonNull AdropRewardedAd ad) {
                Log.d(TAG, "광고 수신 완료");
                // 광고 시청 버튼 활성화 등
            }

            // 필수: 광고 수신 실패
            @Override
            public void onAdFailedToReceive(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
                Log.d(TAG, "광고 수신 실패: " + errorCode);
                // 광고 시청 버튼 비활성화 유지 등
            }

            // 선택: 광고 노출
            @Override
            public void onAdImpression(@NonNull AdropRewardedAd ad) {
                Log.d(TAG, "광고 노출됨");
            }

            // 선택: 광고 클릭
            @Override
            public void onAdClicked(@NonNull AdropRewardedAd ad) {
                Log.d(TAG, "광고 클릭됨");
            }

            // 선택: 광고 화면 표시 직전
            @Override
            public void onAdWillPresentFullScreen(@NonNull AdropRewardedAd ad) {
                Log.d(TAG, "광고 화면 표시 예정");
                // 배경 음악 일시정지 등
            }

            // 선택: 광고 화면 표시 완료
            @Override
            public void onAdDidPresentFullScreen(@NonNull AdropRewardedAd ad) {
                Log.d(TAG, "광고 화면 표시 완료");
            }

            // 선택: 광고 화면 닫기 직전
            @Override
            public void onAdWillDismissFullScreen(@NonNull AdropRewardedAd ad) {
                Log.d(TAG, "광고 화면 닫기 예정");
            }

            // 선택: 광고 화면 닫기 완료
            @Override
            public void onAdDidDismissFullScreen(@NonNull AdropRewardedAd ad) {
                Log.d(TAG, "광고 화면 닫힘");
                // 배경 음악 재개 등

                // 다음 광고 미리 로드
                rewardedAd.load();
            }

            // 선택: 광고 표시 실패
            @Override
            public void onAdFailedToShowFullScreen(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
                Log.d(TAG, "광고 표시 실패: " + errorCode);
            }
        });

        // 3. 광고 로드
        rewardedAd.load();

        // 광고 시청 버튼 클릭 처리
        findViewById(R.id.btnShowRewardedAd).setOnClickListener(v -> showRewardedAd());
    }

    private void showRewardedAd() {
        // 광고가 로드되었는지 확인
        if (!rewardedAd.isLoaded()) {
            Log.d(TAG, "광고가 아직 로드되지 않았습니다");
            return;
        }

        // 4. 광고 표시 및 보상 처리
        rewardedAd.show(this, (type, amount) -> {
            // 보상 지급 처리
            Log.d(TAG, "보상 획득 - 타입: " + type + ", 수량: " + amount);
            grantReward(type, amount);
        });
    }

    private void grantReward(int type, int amount) {
        // 사용자에게 보상 지급
        // 예: 게임 재화 추가, 생명 회복 등

        // ⚠️ 중요: 서버에서 보상을 검증하는 것을 권장합니다
        // 클라이언트에서만 보상을 처리하면 부정행위가 가능합니다
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (rewardedAd != null) {
            rewardedAd.destroy();
            rewardedAd = null;
        }
    }
}
```

***

## AdropRewardedAd 클래스

### 생성자

```kotlin theme={null}
AdropRewardedAd(context: Context, unitId: String)
```

<ParamField path="context" type="Context">
  Activity 또는 Application의 Context
</ParamField>

<ParamField path="unitId" type="String">
  애드컨트롤 콘솔에서 발급받은 광고 유닛 ID
</ParamField>

### 속성

<ParamField path="unitId" type="String">
  광고 유닛 ID (읽기 전용)
</ParamField>

<ParamField path="isLoaded" type="Boolean">
  광고가 로드되어 표시 가능한 상태인지 여부 (읽기 전용)
</ParamField>

<ParamField path="creativeId" type="String">
  로드된 광고의 크리에이티브 ID (읽기 전용)
</ParamField>

<ParamField path="txId" type="String">
  광고 트랜잭션 ID (읽기 전용)
</ParamField>

<ParamField path="campaignId" type="String">
  광고 캠페인 ID (읽기 전용)
</ParamField>

<ParamField path="isBackfilled" type="Boolean">
  백필 광고 여부 (읽기 전용)
</ParamField>

<ParamField path="rewardedAdListener" type="AdropRewardedAdListener?">
  광고 이벤트를 수신할 리스너
</ParamField>

### 메서드

<ParamField path="load()" type="Unit">
  광고를 요청합니다. 광고 로드 결과는 리스너의 `onAdReceived` 또는 `onAdFailedToReceive`로 전달됩니다.
</ParamField>

<ParamField path="show(activity, userDidEarnRewardHandler)" type="Unit">
  광고를 표시합니다.

  * `activity`: 광고를 표시할 Activity
  * `userDidEarnRewardHandler`: 보상 지급 콜백 `(type: Int, amount: Int) -> Unit`
</ParamField>

<ParamField path="destroy()" type="Unit">
  광고 리소스를 해제합니다. Activity의 `onDestroy()`에서 호출해야 합니다.
</ParamField>

***

## AdropRewardedAdListener 인터페이스

### 필수 메서드

<ParamField path="onAdReceived(ad: AdropRewardedAd)" type="void">
  광고 수신에 성공했을 때 호출됩니다. 이 시점부터 `show()` 메서드로 광고를 표시할 수 있습니다.
</ParamField>

<ParamField path="onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode)" type="void">
  광고 수신에 실패했을 때 호출됩니다. 에러 코드를 통해 실패 원인을 확인할 수 있습니다.
</ParamField>

### 선택 메서드

<ParamField path="onAdImpression(ad: AdropRewardedAd)" type="void">
  광고가 노출되어 노출 기록이 전송되었을 때 호출됩니다.
</ParamField>

<ParamField path="onAdClicked(ad: AdropRewardedAd)" type="void">
  사용자가 광고를 클릭했을 때 호출됩니다.
</ParamField>

<ParamField path="onAdWillPresentFullScreen(ad: AdropRewardedAd)" type="void">
  광고 화면이 표시되기 직전에 호출됩니다. 배경 음악을 일시정지하는 등의 처리를 할 수 있습니다.
</ParamField>

<ParamField path="onAdDidPresentFullScreen(ad: AdropRewardedAd)" type="void">
  광고 화면이 완전히 표시된 후 호출됩니다.
</ParamField>

<ParamField path="onAdWillDismissFullScreen(ad: AdropRewardedAd)" type="void">
  광고 화면이 닫히기 직전에 호출됩니다.
</ParamField>

<ParamField path="onAdDidDismissFullScreen(ad: AdropRewardedAd)" type="void">
  광고 화면이 완전히 닫힌 후 호출됩니다. 배경 음악을 재개하거나 다음 광고를 미리 로드할 수 있습니다.
</ParamField>

<ParamField path="onAdFailedToShowFullScreen(ad: AdropRewardedAd, errorCode: AdropErrorCode)" type="void">
  광고 표시에 실패했을 때 호출됩니다. 에러 코드를 통해 실패 원인을 확인할 수 있습니다.
</ParamField>

***

## 보상 처리

### 보상 콜백

`show()` 메서드의 `userDidEarnRewardHandler` 파라미터로 보상 콜백을 전달합니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  rewardedAd?.show(this) { type, amount ->
      // type: 보상 타입 (Int)
      // amount: 보상 수량 (Int)
      Log.d(TAG, "보상 획득 - 타입: $type, 수량: $amount")
      grantReward(type, amount)
  }
  ```

  ```java Java theme={null}
  rewardedAd.show(this, (type, amount) -> {
      // type: 보상 타입 (Int)
      // amount: 보상 수량 (Int)
      Log.d(TAG, "보상 획득 - 타입: " + type + ", 수량: " + amount);
      grantReward(type, amount);
  });
  ```
</CodeGroup>

### 보상 타입과 수량

* **type**: 애드컨트롤 콘솔에서 설정한 보상 타입 (정수 값)
* **amount**: 애드컨트롤 콘솔에서 설정한 보상 수량 (정수 값)

## 모범 사례

### 1. 광고 미리 로드하기

사용자 경험을 위해 광고를 미리 로드해두는 것이 좋습니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  class GameActivity : AppCompatActivity() {
      private var rewardedAd: AdropRewardedAd? = null

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

          // 화면 진입 시 광고 미리 로드
          loadRewardedAd()
      }

      private fun loadRewardedAd() {
          rewardedAd = AdropRewardedAd(this, "YOUR_UNIT_ID")
          rewardedAd?.rewardedAdListener = object : AdropRewardedAdListener {
              override fun onAdReceived(ad: AdropRewardedAd) {
                  Log.d(TAG, "광고 준비 완료")
              }

              override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
                  Log.d(TAG, "광고 로드 실패: $errorCode")
              }

              override fun onAdDidDismissFullScreen(ad: AdropRewardedAd) {
                  // 광고가 닫힌 후 다음 광고 미리 로드
                  loadRewardedAd()
              }
          }
          rewardedAd?.load()
      }

      private fun showRewardedAd() {
          rewardedAd?.show(this) { type, amount ->
              grantReward(type, amount)
          }
      }
  }
  ```

  ```java Java theme={null}
  public class GameActivity extends AppCompatActivity {
      private AdropRewardedAd rewardedAd;

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

          // 화면 진입 시 광고 미리 로드
          loadRewardedAd();
      }

      private void loadRewardedAd() {
          rewardedAd = new AdropRewardedAd(this, "YOUR_UNIT_ID");
          rewardedAd.setRewardedAdListener(new AdropRewardedAdListener() {
              @Override
              public void onAdReceived(@NonNull AdropRewardedAd ad) {
                  Log.d(TAG, "광고 준비 완료");
              }

              @Override
              public void onAdFailedToReceive(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
                  Log.d(TAG, "광고 로드 실패: " + errorCode);
              }

              @Override
              public void onAdDidDismissFullScreen(@NonNull AdropRewardedAd ad) {
                  // 광고가 닫힌 후 다음 광고 미리 로드
                  loadRewardedAd();
              }
          });
          rewardedAd.load();
      }

      private void showRewardedAd() {
          rewardedAd.show(this, (type, amount) -> {
              grantReward(type, amount);
          });
      }
  }
  ```
</CodeGroup>

### 2. 광고 준비 상태 확인

광고가 로드되었는지 확인하고 UI를 업데이트합니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  class GameActivity : AppCompatActivity() {
      private var rewardedAd: AdropRewardedAd? = null
      private var isAdReady = false

      private fun updateButtonState() {
          findViewById<Button>(R.id.btnWatchAd).apply {
              isEnabled = isAdReady
              alpha = if (isAdReady) 1.0f else 0.5f
              text = if (isAdReady) "동영상 보고 생명 받기" else "광고 로딩 중..."
          }
      }

      private val adListener = object : AdropRewardedAdListener {
          override fun onAdReceived(ad: AdropRewardedAd) {
              isAdReady = true
              updateButtonState()
          }

          override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
              isAdReady = false
              updateButtonState()
          }

          override fun onAdDidDismissFullScreen(ad: AdropRewardedAd) {
              isAdReady = false
              updateButtonState()

              // 다음 광고 로드
              rewardedAd?.load()
          }
      }
  }
  ```

  ```java Java theme={null}
  public class GameActivity extends AppCompatActivity {
      private AdropRewardedAd rewardedAd;
      private boolean isAdReady = false;

      private void updateButtonState() {
          Button btnWatchAd = findViewById(R.id.btnWatchAd);
          btnWatchAd.setEnabled(isAdReady);
          btnWatchAd.setAlpha(isAdReady ? 1.0f : 0.5f);
          btnWatchAd.setText(isAdReady ? "동영상 보고 생명 받기" : "광고 로딩 중...");
      }

      private final AdropRewardedAdListener adListener = new AdropRewardedAdListener() {
          @Override
          public void onAdReceived(@NonNull AdropRewardedAd ad) {
              isAdReady = true;
              updateButtonState();
          }

          @Override
          public void onAdFailedToReceive(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
              isAdReady = false;
              updateButtonState();
          }

          @Override
          public void onAdDidDismissFullScreen(@NonNull AdropRewardedAd ad) {
              isAdReady = false;
              updateButtonState();

              // 다음 광고 로드
              rewardedAd.load();
          }
      };
  }
  ```
</CodeGroup>

### 3. 배경 음악 처리

광고 표시 전후로 배경 음악을 적절히 제어합니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  private val adListener = object : AdropRewardedAdListener {
      override fun onAdReceived(ad: AdropRewardedAd) {
          // 필수 구현
      }

      override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
          // 필수 구현
      }

      override fun onAdWillPresentFullScreen(ad: AdropRewardedAd) {
          // 광고 표시 직전 배경 음악 일시정지
          AudioManager.pauseBackgroundMusic()
      }

      override fun onAdDidDismissFullScreen(ad: AdropRewardedAd) {
          // 광고 종료 후 배경 음악 재개
          AudioManager.resumeBackgroundMusic()
      }
  }
  ```

  ```java Java theme={null}
  private final AdropRewardedAdListener adListener = new AdropRewardedAdListener() {
      @Override
      public void onAdReceived(@NonNull AdropRewardedAd ad) {
          // 필수 구현
      }

      @Override
      public void onAdFailedToReceive(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
          // 필수 구현
      }

      @Override
      public void onAdWillPresentFullScreen(@NonNull AdropRewardedAd ad) {
          // 광고 표시 직전 배경 음악 일시정지
          AudioManager.pauseBackgroundMusic();
      }

      @Override
      public void onAdDidDismissFullScreen(@NonNull AdropRewardedAd ad) {
          // 광고 종료 후 배경 음악 재개
          AudioManager.resumeBackgroundMusic();
      }
  };
  ```
</CodeGroup>

### 4. 에러 처리

광고 로드 실패 시 재시도 로직을 구현합니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  private val adListener = object : AdropRewardedAdListener {
      override fun onAdReceived(ad: AdropRewardedAd) {
          Log.d(TAG, "광고 로드 성공")
      }

      override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
          Log.d(TAG, "광고 로드 실패: $errorCode")

          // 네트워크 에러인 경우 재시도
          if (errorCode == AdropErrorCode.ERROR_CODE_NETWORK) {
              Handler(Looper.getMainLooper()).postDelayed({
                  rewardedAd?.load()
              }, 3000)
          }
      }

      override fun onAdFailedToShowFullScreen(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
          Log.d(TAG, "광고 표시 실패: $errorCode")

          // 사용자에게 알림
          Toast.makeText(
              this@GameActivity,
              "광고를 표시할 수 없습니다. 나중에 다시 시도해주세요.",
              Toast.LENGTH_SHORT
          ).show()
      }
  }
  ```

  ```java Java theme={null}
  private final AdropRewardedAdListener adListener = new AdropRewardedAdListener() {
      @Override
      public void onAdReceived(@NonNull AdropRewardedAd ad) {
          Log.d(TAG, "광고 로드 성공");
      }

      @Override
      public void onAdFailedToReceive(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
          Log.d(TAG, "광고 로드 실패: " + errorCode);

          // 네트워크 에러인 경우 재시도
          if (errorCode == AdropErrorCode.ERROR_CODE_NETWORK) {
              new Handler(Looper.getMainLooper()).postDelayed(() -> {
                  rewardedAd.load();
              }, 3000);
          }
      }

      @Override
      public void onAdFailedToShowFullScreen(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
          Log.d(TAG, "광고 표시 실패: " + errorCode);

          // 사용자에게 알림
          Toast.makeText(
              GameActivity.this,
              "광고를 표시할 수 없습니다. 나중에 다시 시도해주세요.",
              Toast.LENGTH_SHORT
          ).show();
      }
  };
  ```
</CodeGroup>

### 5. 생명주기 관리

Activity 종료 시 광고 리소스를 정리합니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  class GameActivity : AppCompatActivity() {
      private var rewardedAd: AdropRewardedAd? = null

      override fun onDestroy() {
          super.onDestroy()
          rewardedAd?.destroy()
          rewardedAd = null
      }
  }
  ```

  ```java Java theme={null}
  public class GameActivity extends AppCompatActivity {
      private AdropRewardedAd rewardedAd;

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

***

## 완전한 예제

<CodeGroup>
  ```kotlin Kotlin theme={null}
  import android.os.Bundle
  import android.os.Handler
  import android.os.Looper
  import android.util.Log
  import android.widget.Button
  import android.widget.Toast
  import androidx.appcompat.app.AppCompatActivity
  import io.adrop.ads.rewardedAd.AdropRewardedAd
  import io.adrop.ads.rewardedAd.AdropRewardedAdListener
  import io.adrop.ads.model.AdropErrorCode

  class GameActivity : AppCompatActivity() {
      private var rewardedAd: AdropRewardedAd? = null
      private var isAdReady = false
      private lateinit var btnWatchAd: Button

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

          btnWatchAd = findViewById(R.id.btnWatchAd)
          btnWatchAd.setOnClickListener {
              showRewardedAd()
          }

          // 광고 미리 로드
          loadRewardedAd()
      }

      private fun loadRewardedAd() {
          rewardedAd = AdropRewardedAd(this, "PUBLIC_TEST_UNIT_ID_REWARDED")
          rewardedAd?.rewardedAdListener = object : AdropRewardedAdListener {
              override fun onAdReceived(ad: AdropRewardedAd) {
                  Log.d(TAG, "광고 수신 완료")
                  isAdReady = true
                  updateButtonState()
              }

              override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
                  Log.d(TAG, "광고 수신 실패: $errorCode")
                  isAdReady = false
                  updateButtonState()

                  // 네트워크 에러인 경우 3초 후 재시도
                  if (errorCode == AdropErrorCode.ERROR_CODE_NETWORK) {
                      Handler(Looper.getMainLooper()).postDelayed({
                          rewardedAd?.load()
                      }, 3000)
                  }
              }

              override fun onAdImpression(ad: AdropRewardedAd) {
                  Log.d(TAG, "광고 노출됨")
              }

              override fun onAdClicked(ad: AdropRewardedAd) {
                  Log.d(TAG, "광고 클릭됨")
              }

              override fun onAdWillPresentFullScreen(ad: AdropRewardedAd) {
                  Log.d(TAG, "광고 화면 표시 예정")
                  // 배경 음악 일시정지
                  pauseBackgroundMusic()
              }

              override fun onAdDidPresentFullScreen(ad: AdropRewardedAd) {
                  Log.d(TAG, "광고 화면 표시 완료")
              }

              override fun onAdWillDismissFullScreen(ad: AdropRewardedAd) {
                  Log.d(TAG, "광고 화면 닫기 예정")
              }

              override fun onAdDidDismissFullScreen(ad: AdropRewardedAd) {
                  Log.d(TAG, "광고 화면 닫힘")
                  isAdReady = false
                  updateButtonState()

                  // 배경 음악 재개
                  resumeBackgroundMusic()

                  // 다음 광고 미리 로드
                  loadRewardedAd()
              }

              override fun onAdFailedToShowFullScreen(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
                  Log.d(TAG, "광고 표시 실패: $errorCode")
                  Toast.makeText(
                      this@GameActivity,
                      "광고를 표시할 수 없습니다. 나중에 다시 시도해주세요.",
                      Toast.LENGTH_SHORT
                  ).show()
              }
          }
          rewardedAd?.load()
      }

      private fun showRewardedAd() {
          if (!isAdReady) {
              Toast.makeText(this, "광고가 아직 로드되지 않았습니다", Toast.LENGTH_SHORT).show()
              return
          }

          rewardedAd?.show(this) { type, amount ->
              Log.d(TAG, "보상 획득 - 타입: $type, 수량: $amount")
              grantReward(type, amount)
          }
      }

      private fun grantReward(type: Int, amount: Int) {
          // 사용자에게 보상 지급
          runOnUiThread {
              Toast.makeText(
                  this,
                  "보상 획득! 타입: $type, 수량: $amount",
                  Toast.LENGTH_LONG
              ).show()
          }

          // 실제 보상 지급 로직 구현
          // 예: 게임 재화 추가, 생명 회복 등
      }

      private fun updateButtonState() {
          btnWatchAd.isEnabled = isAdReady
          btnWatchAd.alpha = if (isAdReady) 1.0f else 0.5f
          btnWatchAd.text = if (isAdReady) "동영상 보고 생명 받기" else "광고 로딩 중..."
      }

      private fun pauseBackgroundMusic() {
          // 배경 음악 일시정지 로직
      }

      private fun resumeBackgroundMusic() {
          // 배경 음악 재개 로직
      }

      override fun onDestroy() {
          super.onDestroy()
          rewardedAd?.destroy()
          rewardedAd = null
      }

      companion object {
          private const val TAG = "GameActivity"
      }
  }
  ```

  ```java Java theme={null}
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.Looper;
  import android.util.Log;
  import android.widget.Button;
  import android.widget.Toast;
  import androidx.annotation.NonNull;
  import androidx.appcompat.app.AppCompatActivity;
  import io.adrop.ads.rewardedAd.AdropRewardedAd;
  import io.adrop.ads.rewardedAd.AdropRewardedAdListener;
  import io.adrop.ads.model.AdropErrorCode;

  public class GameActivity extends AppCompatActivity {
      private static final String TAG = "GameActivity";
      private AdropRewardedAd rewardedAd;
      private boolean isAdReady = false;
      private Button btnWatchAd;

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

          btnWatchAd = findViewById(R.id.btnWatchAd);
          btnWatchAd.setOnClickListener(v -> showRewardedAd());

          // 광고 미리 로드
          loadRewardedAd();
      }

      private void loadRewardedAd() {
          rewardedAd = new AdropRewardedAd(this, "PUBLIC_TEST_UNIT_ID_REWARDED");
          rewardedAd.setRewardedAdListener(new AdropRewardedAdListener() {
              @Override
              public void onAdReceived(@NonNull AdropRewardedAd ad) {
                  Log.d(TAG, "광고 수신 완료");
                  isAdReady = true;
                  updateButtonState();
              }

              @Override
              public void onAdFailedToReceive(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
                  Log.d(TAG, "광고 수신 실패: " + errorCode);
                  isAdReady = false;
                  updateButtonState();

                  // 네트워크 에러인 경우 3초 후 재시도
                  if (errorCode == AdropErrorCode.ERROR_CODE_NETWORK) {
                      new Handler(Looper.getMainLooper()).postDelayed(() -> {
                          rewardedAd.load();
                      }, 3000);
                  }
              }

              @Override
              public void onAdImpression(@NonNull AdropRewardedAd ad) {
                  Log.d(TAG, "광고 노출됨");
              }

              @Override
              public void onAdClicked(@NonNull AdropRewardedAd ad) {
                  Log.d(TAG, "광고 클릭됨");
              }

              @Override
              public void onAdWillPresentFullScreen(@NonNull AdropRewardedAd ad) {
                  Log.d(TAG, "광고 화면 표시 예정");
                  // 배경 음악 일시정지
                  pauseBackgroundMusic();
              }

              @Override
              public void onAdDidPresentFullScreen(@NonNull AdropRewardedAd ad) {
                  Log.d(TAG, "광고 화면 표시 완료");
              }

              @Override
              public void onAdWillDismissFullScreen(@NonNull AdropRewardedAd ad) {
                  Log.d(TAG, "광고 화면 닫기 예정");
              }

              @Override
              public void onAdDidDismissFullScreen(@NonNull AdropRewardedAd ad) {
                  Log.d(TAG, "광고 화면 닫힘");
                  isAdReady = false;
                  updateButtonState();

                  // 배경 음악 재개
                  resumeBackgroundMusic();

                  // 다음 광고 미리 로드
                  loadRewardedAd();
              }

              @Override
              public void onAdFailedToShowFullScreen(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
                  Log.d(TAG, "광고 표시 실패: " + errorCode);
                  Toast.makeText(
                      GameActivity.this,
                      "광고를 표시할 수 없습니다. 나중에 다시 시도해주세요.",
                      Toast.LENGTH_SHORT
                  ).show();
              }
          });
          rewardedAd.load();
      }

      private void showRewardedAd() {
          if (!isAdReady) {
              Toast.makeText(this, "광고가 아직 로드되지 않았습니다", Toast.LENGTH_SHORT).show();
              return;
          }

          rewardedAd.show(this, (type, amount) -> {
              Log.d(TAG, "보상 획득 - 타입: " + type + ", 수량: " + amount);
              grantReward(type, amount);
          });
      }

      private void grantReward(int type, int amount) {
          // 사용자에게 보상 지급
          runOnUiThread(() -> {
              Toast.makeText(
                  this,
                  "보상 획득! 타입: " + type + ", 수량: " + amount,
                  Toast.LENGTH_LONG
              ).show();
          });

          // 실제 보상 지급 로직 구현
          // 예: 게임 재화 추가, 생명 회복 등
      }

      private void updateButtonState() {
          btnWatchAd.setEnabled(isAdReady);
          btnWatchAd.setAlpha(isAdReady ? 1.0f : 0.5f);
          btnWatchAd.setText(isAdReady ? "동영상 보고 생명 받기" : "광고 로딩 중...");
      }

      private void pauseBackgroundMusic() {
          // 배경 음악 일시정지 로직
      }

      private void resumeBackgroundMusic() {
          // 배경 음악 재개 로직
      }

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

***

## 테스트

### 테스트 유닛 ID

개발 중에는 항상 테스트 유닛 ID를 사용하세요.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  // 개발 환경
  val rewardedAd = AdropRewardedAd(this, "PUBLIC_TEST_UNIT_ID_REWARDED")

  // 프로덕션 환경
  val rewardedAd = AdropRewardedAd(this, "YOUR_PRODUCTION_UNIT_ID")
  ```

  ```java Java theme={null}
  // 개발 환경
  AdropRewardedAd rewardedAd = new AdropRewardedAd(this, "PUBLIC_TEST_UNIT_ID_REWARDED");

  // 프로덕션 환경
  AdropRewardedAd rewardedAd = new AdropRewardedAd(this, "YOUR_PRODUCTION_UNIT_ID");
  ```
</CodeGroup>

<Warning>
  실제 유닛 ID로 테스트하면 무효 트래픽으로 간주되어 계정이 정지될 수 있습니다. 반드시 테스트 유닛 ID를 사용하세요.
</Warning>

### 환경별 유닛 ID 관리

빌드 타입에 따라 자동으로 유닛 ID를 변경하는 방법입니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  object AdUnitID {
      val REWARDED: String
          get() = if (BuildConfig.DEBUG) {
              "PUBLIC_TEST_UNIT_ID_REWARDED"
          } else {
              "YOUR_PRODUCTION_UNIT_ID"
          }
  }

  // 사용
  val rewardedAd = AdropRewardedAd(this, AdUnitID.REWARDED)
  ```

  ```java Java theme={null}
  public class AdUnitID {
      public static String getREWARDED() {
          if (BuildConfig.DEBUG) {
              return "PUBLIC_TEST_UNIT_ID_REWARDED";
          } else {
              return "YOUR_PRODUCTION_UNIT_ID";
          }
      }
  }

  // 사용
  AdropRewardedAd rewardedAd = new AdropRewardedAd(this, AdUnitID.getREWARDED());
  ```
</CodeGroup>

***

## 백필 광고

백필 광고가 활성화된 경우, 직광고가 없을 때 자동으로 백필 광고가 로드됩니다. `isBackfilled` 프로퍼티로 백필 광고 여부를 확인할 수 있습니다.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  rewardedAd.rewardedAdListener = object : AdropRewardedAdListener {
      override fun onAdReceived(ad: AdropRewardedAd) {
          if (ad.isBackfilled) {
              println("백필 광고가 로드되었습니다")
          } else {
              println("직광고가 로드되었습니다")
          }
      }

      override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
          when (errorCode) {
              AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
                  println("직광고 없음, 백필 광고 요청 중...")
              }
              AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL -> {
                  println("백필 광고도 없음")
              }
              else -> {
                  println("광고 로드 실패: $errorCode")
              }
          }
      }
  }
  ```

  ```java Java theme={null}
  rewardedAd.setRewardedAdListener(new AdropRewardedAdListener() {
      @Override
      public void onAdReceived(AdropRewardedAd ad) {
          if (ad.isBackfilled()) {
              System.out.println("백필 광고가 로드되었습니다");
          } else {
              System.out.println("직광고가 로드되었습니다");
          }
      }

      @Override
      public void onAdFailedToReceive(AdropRewardedAd ad, AdropErrorCode errorCode) {
          if (errorCode == AdropErrorCode.ERROR_CODE_AD_NO_FILL) {
              System.out.println("직광고 없음, 백필 광고 요청 중...");
          } else if (errorCode == AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL) {
              System.out.println("백필 광고도 없음");
          } else {
              System.out.println("광고 로드 실패: " + errorCode);
          }
      }
  });
  ```
</CodeGroup>

<Note>
  백필 광고를 사용하려면 `io.adrop:adrop-ads-backfill` 의존성을 추가해야 합니다. [시작하기](/ko/sdk/android/overview)를 참고하세요.
</Note>

***

## 서버 사이드 검증

서버 사이드 검증(SSV)을 사용하면 리워드 지급을 서버에서 안전하게 검증할 수 있습니다. `ServerSideVerificationOptions`를 사용하여 사용자 식별 정보와 커스텀 데이터를 서버 콜백에 포함시킬 수 있습니다.

<Note>
  서버에서 SSV 콜백을 수신하려면 콘솔 **\[관리] > \[연동] > 리워드 광고 SSV**에서 콜백 URL을 등록해야 해요. 여기서 설정한 `userId`와 `customData`는 해당 URL로 전송되는 암호화 페이로드에 포함됩니다. 페이로드 복호화 및 설정 방법은 [리워드 광고 SSV 가이드](/ko/integrations/ssv)를 참고하세요.
</Note>

### SSV 설정

<CodeGroup>
  ```kotlin Kotlin theme={null}
  val rewardedAd = AdropRewardedAd(this, "YOUR_UNIT_ID")

  // 광고 로드 전에 SSV 옵션 설정
  rewardedAd.serverSideVerificationOptions = ServerSideVerificationOptions(
      userId = "user_12345",
      customData = "extra_info"
  )

  rewardedAd.rewardedAdListener = object : AdropRewardedAdListener {
      override fun onAdReceived(ad: AdropRewardedAd) {
          ad.show(this@MainActivity) { type, amount ->
              Log.d("Reward", "Type: $type, Amount: $amount")
          }
      }

      override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
          // Handle error
      }
  }

  rewardedAd.load()
  ```

  ```java Java theme={null}
  AdropRewardedAd rewardedAd = new AdropRewardedAd(this, "YOUR_UNIT_ID");

  // 광고 로드 전에 SSV 옵션 설정
  rewardedAd.setServerSideVerificationOptions(
      new ServerSideVerificationOptions("user_12345", "extra_info")
  );

  rewardedAd.setRewardedAdListener(new AdropRewardedAdListener() {
      @Override
      public void onAdReceived(AdropRewardedAd ad) {
          ad.show(this, (type, amount) -> {
              Log.d("Reward", "Type: " + type + ", Amount: " + amount);
          });
      }
  });

  rewardedAd.load();
  ```
</CodeGroup>

### ServerSideVerificationOptions

| 프로퍼티         | 타입        | 설명                     |
| ------------ | --------- | ---------------------- |
| `userId`     | `String?` | 서버 사이드 검증에 사용할 사용자 식별자 |
| `customData` | `String?` | 서버 콜백에 전달할 커스텀 데이터 문자열 |

<Note>
  `serverSideVerificationOptions`는 `load()` 호출 전에 설정해야 합니다. `userId`와 `customData` 값은 사용자가 보상을 획득할 때 서버 콜백 URL로 전송됩니다.
</Note>

***

## 관련 문서

<CardGroup cols={2}>
  <Card title="전면 광고" href="/ko/sdk/android/interstitial" icon="rectangle-ad">
    전면 광고 구현 가이드
  </Card>

  <Card title="에러 코드" href="/ko/sdk/android/reference#error-codes" icon="triangle-exclamation">
    AdropErrorCode 레퍼런스
  </Card>

  <Card title="예제 프로젝트" href="/ko/sdk/android/examples" icon="code">
    전체 예제 코드 보기
  </Card>
</CardGroup>
