메인 콘텐츠로 건너뛰기

개요

네이티브 광고는 앱의 콘텐츠와 자연스럽게 어우러지도록 커스터마이징할 수 있는 광고입니다. AdropNativeAdAdropNativeAdView를 사용하여 앱의 UI에 맞게 광고를 구성할 수 있습니다.

주요 특징

  • 앱 디자인에 맞춤 레이아웃 구성 가능
  • 헤드라인, 본문, CTA 버튼, 프로필 등 다양한 요소 제공
  • 이미지 및 HTML 크리에이티브 지원
  • 커스텀 클릭 핸들링 지원
  • 백필 광고 지원
개발 환경에서는 테스트 유닛 ID를 사용하세요: PUBLIC_TEST_UNIT_ID_NATIVE

AdropNativeAd

생성자

AdropNativeAd({
  required String unitId,
  bool useCustomClick = false,
  AdropNativeListener? listener,
})
파라미터
파라미터타입필수설명
unitIdStringY애드컨트롤 콘솔에서 생성한 유닛 ID
useCustomClickboolN커스텀 클릭 핸들링 사용 여부 (기본값: false)
listenerAdropNativeListenerN광고 이벤트 리스너

속성

속성타입설명
isLoadedbool광고 로드 완료 여부
unitIdString광고 유닛 ID
creativeIdString크리에이티브 ID
txIdString트랜잭션 ID
campaignIdString캠페인 ID
destinationURLString목적지 URL
propertiesAdropNativeProperties네이티브 광고 속성
creativeSizeCreativeSize크리에이티브 크기
isBackfilledbool백필 광고 여부

메서드

메서드반환 타입설명
load()Future<void>광고를 로드합니다

AdropNativeAdView

네이티브 광고를 화면에 표시하는 위젯입니다.

생성자

AdropNativeAdView({
  required AdropNativeAd? ad,
  required Widget child,
})
파라미터
파라미터타입필수설명
adAdropNativeAd?Y로드된 네이티브 광고 객체
childWidgetY광고 콘텐츠를 표시할 자식 위젯

AdropNativeProperties

네이티브 광고의 콘텐츠 속성입니다.

속성

속성타입설명
headlineString?광고 제목
bodyString?광고 본문
creativeString?HTML 크리에이티브 콘텐츠
assetString?이미지 에셋 URL
destinationURLString?클릭 시 이동할 URL
callToActionString?CTA 버튼 텍스트
profileAdropNativeProfile?광고주 프로필 정보
extraMap<String, String>추가 커스텀 필드
isBackfilledbool백필 광고 여부

AdropNativeProfile

속성타입설명
displayNameString?광고주 이름
displayLogoString?광고주 로고 이미지 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) {
    // 광고 수신 실패
  },
)

콜백 설명

콜백설명
onAdReceived광고 수신 성공 시 호출
onAdClicked광고 클릭 시 호출
onAdImpression광고 노출 시 호출
onAdFailedToReceive광고 수신 실패 시 호출

커스텀 클릭 핸들링

동영상 크리에이티브나 커스텀 클릭 동작이 필요한 경우 useCustomClick을 사용합니다.
nativeAd = AdropNativeAd(
  unitId: 'YOUR_UNIT_ID',
  useCustomClick: true, // 커스텀 클릭 활성화
  listener: AdropNativeListener(
    onAdReceived: (ad) {
      setState(() {
        isLoaded = true;
      });
    },
    onAdClicked: (ad) {
      // 커스텀 클릭 동작 처리
      debugPrint('광고 클릭됨: ${ad.destinationURL}');
    },
  ),
);
useCustomClicktrue인 경우, 자식 위젯의 클릭 이벤트가 광고 클릭으로 처리됩니다.

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

에러 처리

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

다음 단계