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.
네이티브 광고는 앱의 콘텐츠와 자연스럽게 어우러지도록 커스터마이징할 수 있는 광고입니다. AdropNativeAd와 AdropNativeAdView를 사용하여 앱의 UI에 맞게 광고를 구성할 수 있습니다.
주요 특징
- 앱 디자인에 맞춤 레이아웃 구성 가능
- 헤드라인, 본문, CTA 버튼, 프로필 등 다양한 요소 제공
- 이미지 및 HTML 크리에이티브 지원
- 커스텀 클릭 핸들링 지원
- 백필 광고 지원
개발 환경에서는 테스트 유닛 ID를 사용하세요: PUBLIC_TEST_UNIT_ID_NATIVE
AdropNativeAd
생성자
AdropNativeAd({
required String unitId,
bool useCustomClick = false,
AdropNativeListener? listener,
})
파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|
unitId | String | Y | 애드컨트롤 콘솔에서 생성한 유닛 ID |
useCustomClick | bool | N | 커스텀 클릭 핸들링 사용 여부 (기본값: false) |
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> | 광고를 로드합니다 |
다른 광고 타입(AdropInterstitialAd, AdropRewardedAd, AdropPopupAd)과 달리 AdropNativeAd는 dispose() 메서드를 제공하지 않습니다.
새로운 광고를 로드하려면 새 AdropNativeAd 인스턴스를 생성하세요. 이전 인스턴스의 리소스는 가비지 컬렉터에 의해 자동으로 해제됩니다.
AdropNativeAdView
네이티브 광고를 화면에 표시하는 위젯입니다.
생성자
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 |
기본 사용법
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
네이티브 광고 이벤트를 처리하는 리스너입니다.
콜백 함수
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을 사용합니다.
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를 사용하여 표시할 수 있습니다.
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!),
),
),
],
),
),
);
}
}
HTML 크리에이티브를 사용하려면 webview_flutter 패키지를 추가해야 합니다.flutter pub add webview_flutter
백필 광고 처리
네이티브 광고가 백필 광고인 경우 isBackfilled 속성으로 확인하고 처리할 수 있습니다.
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();
}
}
추가 필드 사용
매체사가 정의한 추가 필드는 extra 맵에서 접근할 수 있습니다.
onAdReceived: (ad) {
// 추가 필드 접근
final customField = ad.properties.extra['customFieldKey'];
if (customField != null) {
debugPrint('커스텀 필드: $customField');
}
setState(() {
isLoaded = true;
});
}
에러 처리
백필 관련 에러
백필 광고가 설정된 경우, 직광고와 백필 광고 모두 없을 때 backfillNoFill 에러 코드가 반환됩니다.
onAdFailedToReceive: (ad, errorCode) {
if (errorCode == AdropErrorCode.backfillNoFill) {
debugPrint('백필 광고도 없습니다');
}
}
일반 에러 처리
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 인스턴스를 생성합니다.
void resetAd() {
setState(() {
isLoaded = false;
});
nativeAd = AdropNativeAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropNativeListener(
onAdReceived: (ad) {
setState(() {
isLoaded = true;
});
},
),
);
nativeAd?.load();
}
2. 조건부 렌더링
광고가 로드될 때까지 적절한 플레이스홀더를 표시합니다.
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. 반응형 레이아웃
다양한 화면 크기에 대응하도록 레이아웃을 구성합니다.
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일 수 있으므로 항상 확인합니다.
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,
),
);
}
다음 단계
전면 광고
화면 전체를 덮는 전면 광고 구현하기
보상형 광고
보상을 제공하는 보상형 광고 구현하기