개요
보상형 광고는 사용자가 광고를 끝까지 시청하면 보상을 받는 형태의 풀스크린 광고입니다. 게임 내 아이템, 추가 콘텐츠 등의 보상을 제공할 때 사용합니다.주요 특징
- 화면 전체를 덮는 풀스크린 광고
- 동영상 광고 지원
- 보상 이벤트 콜백 제공
- 보상 타입과 수량 정보 전달
- 로드와 표시를 분리하여 유연한 타이밍 제어
개발 환경에서는 테스트 유닛 ID를 사용하세요:
PUBLIC_TEST_UNIT_ID_REWARDEDAdropRewardedAd
생성자
AdropRewardedAd({
required String unitId,
AdropRewardedListener? listener,
})
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
unitId | String | Y | 애드컨트롤 콘솔에서 생성한 유닛 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> | 리소스를 해제합니다 |
기본 사용법
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
보상형 광고 이벤트를 처리하는 리스너입니다.콜백 함수
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 콜백에서 보상 정보를 받아 처리합니다.
onAdEarnRewardHandler: (ad, type, amount) {
debugPrint('보상 획득: 타입=$type, 수량=$amount');
// 보상 타입에 따른 처리
switch (type) {
case 1:
// 코인 보상
_addCoins(amount);
break;
case 2:
// 아이템 보상
_addItem(amount);
break;
default:
// 기본 보상
_giveDefaultReward(amount);
}
}
보상 지급 시점
보상은 반드시
onAdEarnRewardHandler 콜백에서 지급해야 합니다. 광고가 완전히 시청되기 전에 닫히면 이 콜백이 호출되지 않습니다.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;
}
},
),
);
}
}
광고 재생성
보상형 광고는 일회성입니다. 한 번 표시된 광고는 다시 표시할 수 없으므로, 새로운 광고를 로드하려면 인스턴스를 재생성해야 합니다.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();
}
}
에러 처리
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. 미리 로드하기
사용자가 보상을 요청할 때 즉시 광고를 보여줄 수 있도록 미리 로드합니다.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. 사용자에게 보상 안내
광고 시청 전 받을 수 있는 보상을 명확히 안내합니다.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. 광고 가용성 표시
광고가 준비되었을 때만 보상 버튼을 활성화합니다.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. 보상 지급 신뢰성
보상 지급은 서버에서 검증하는 것이 좋습니다.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. 리소스 관리
사용하지 않는 광고 인스턴스는 반드시 해제하세요.@override
void dispose() {
rewardedAd?.dispose();
super.dispose();
}
전체 예제
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를 설정하세요.
설정
load()를 호출하기 전에 serverSideVerificationOptions를 설정하세요.
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? | 서버 콜백에 포함할 커스텀 데이터 문자열 |
전체 예제
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();
}
}
serverSideVerificationOptions는 load() 호출 전에 설정해야 합니다. load() 이후에 설정한 옵션은 해당 광고 요청에 적용되지 않습니다.백필 광고
백필 광고가 활성화된 경우, 직광고가 없을 때 자동으로 백필 광고가 로드됩니다. 에러 코드를 통해 백필 관련 상태를 처리할 수 있습니다.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}');
}
},
),
);
백필 광고를 사용하려면 네이티브 플랫폼에 백필 의존성을 추가해야 합니다. 시작하기를 참고하세요.
다음 단계
팝업 광고
팝업 형태로 표시되는 광고 구현하기
타겟팅
맞춤형 광고를 위한 타겟팅 설정하기
레퍼런스
타입, 메서드, 에러 코드 참고하기