@motiv-i/react-native-exelbid
v0.2.0
Published
React Native package wrapping the ExelBid iOS/Android SDKs (New Architecture / Fabric).
Downloads
282
Readme
@motiv-i/react-native-exelbid
ExelBid 광고 SDK(iOS/Android)를 React Native에서 사용하기 위한 패키지입니다. 배너 · 네이티브 · 전면(Interstitial) · 비디오 광고와 미디에이션(워터폴) 을 지원합니다.
플랫폼 지원
| 광고 유형 | iOS | Android | |---|:---:|:---:| | Banner / Native / Interstitial / Video | ✅ | ✅ | | Mediation (Banner / Native / Interstitial / Video) | ✅ | ✅ |
같은 JS/TS 코드로 두 플랫폼이 동작하며, 광고 단위 ID만 플랫폼별로 다르게 관리하면 됩니다. 최소 버전은 iOS 13.0+ / Android API 24+ 이고 New Architecture (Fabric / TurboModule) 전용입니다.
ExelBid 광고는 추가 설정 없이 바로 사용할 수 있습니다. 미디에이션으로 AdMob · FAN · AdFit 등 외부 네트워크를 함께 운영할 수 있습니다.
앱 호스트는 React만 작성합니다. Android/iOS 광고 로직은 이 패키지가 감싸는 컴파일된 네이티브 SDK 안에 있고, 이 패키지는 그 위의 얇은 브리지입니다.
@motiv-i/react-native-exelbid (JS/TS — 앱이 보는 유일한 부분)
├─ Android → com.onnuridmc.exelbid:exelbid (AAR, 컴파일됨) ★ 광고 로직
└─ iOS → ExelBid_iOS_Swift (XCFramework, 컴파일됨) ★ 광고 로직목차
- 요구 사항
- 설치
- iOS 프로젝트 설정
- Android 프로젝트 설정
- SDK 초기화 · ATT
- 배너 광고
- 네이티브 광고
- 전면 광고 (Interstitial)
- 비디오 광고
- 광고 옵션 (AdOptions)
- 미디에이션
- 에러 처리 (AdError)
- 공통 API 요약
- 샘플 앱
- 트러블슈팅
- 부록: AdMob 옵션 (선택)
요구 사항
| 항목 | 버전 |
|---|---|
| React Native | 0.76+ (New Architecture 활성화) |
| iOS | 13.0+ · ExelBid_iOS_Swift >= 3.0.8 |
| Android | API 24+ (RN 0.76 기준) · com.onnuridmc.exelbid:exelbid 2.0.2 · Java 17 |
ExelBid 광고 표시용 Activity와 ACCESS_NETWORK_STATE 권한은 이 패키지의
AndroidManifest.xml이 호스트 앱에 자동 머지하므로 별도 선언이 필요 없습니다.
설치
yarn add @motiv-i/react-native-exelbid
# 또는
npm install @motiv-i/react-native-exelbid이 패키지는 New Architecture 전용입니다. 아래처럼 New Arch를 켠 뒤 빌드하세요.
Android —
android/gradle.properties에newArchEnabled=trueiOS —
pod install시 New Arch 켜기:cd ios && RCT_NEW_ARCH_ENABLED=1 pod install
네이티브 SDK(Android AAR / iOS XCFramework)는 이 패키지가 오토링크로 함께 가져오므로 호스트 앱이 SDK 의존성을 직접 추가할 필요는 없습니다(미디에이션 3rd-party 네트워크만 예외 — 미디에이션 참고).
iOS 프로젝트 설정
네이티브 SDK는 CocoaPods 오토링크로 통합됩니다. pod install만 하면 되고
호스트 Podfile에 pod 'ExelBid_iOS_Swift'를 직접 추가할 필요는 없습니다.
cd ios && RCT_NEW_ARCH_ENABLED=1 pod installApp Tracking Transparency (필수)
iOS에서 광고 식별자(IDFA)를 사용하려면 ATT 권한 요청이 필요합니다. 두 가지를 호스트 앱이 직접 처리해야 합니다.
1. Info.plist에 권한 안내 문구 추가 (필수)
<key>NSUserTrackingUsageDescription</key>
<string>맞춤형 광고를 제공하기 위해 사용자 활동을 추적합니다.</string>⚠️ 이 키가 없으면
requestTrackingAuthorization()호출 순간 iOS가 앱을 **강제 종료(SIGKILL, TCC privacy violation)**합니다 — 앱이 켜자마자 죽은 것처럼 보입니다.
2. 적절한 시점(앱 활성화 직후 등)에 ATT 요청 (필수)
이 패키지는 ATT를 자동으로 띄우지 않습니다. 호출 시점은 앱이 결정합니다 (SDK 초기화 · ATT 참고).
SKAdNetwork (권장)
광고 성과 측정(어트리뷰션)을 위해 매체사에서 제공받은 SKAdNetwork ID 목록을
Info.plist에 등록하세요(선택, 정확한 성과 측정에 권장).
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>xxxxxxxxxx.skadnetwork</string>
</dict>
</array>AdMob 등 외부 네트워크를 미디에이션으로 함께 쓸 때는 각 네트워크의
GADApplicationIdentifier등 추가 설정이 필요합니다 — 미디에이션 참고.
Android 프로젝트 설정
ExelBid Android SDK와 필수 라이브러리·권한·광고 표시용 액티비티를 이 패키지가 모두 번들·머지하므로, 호스트 앱에서 별도 의존성/권한 추가는 필요 없습니다.
최소 SDK
minSdkVersion은 24 이상이어야 합니다(RN 0.76 기준). AndroidX가 활성화되어
있어야 합니다(최신 RN 프로젝트는 기본 활성화).
New Architecture
android/gradle.properties에 newArchEnabled=true가 있어야 합니다.
AdMob · FAN · AdFit 등 외부 네트워크를 함께 쓰려면 미디에이션 섹션의 설정을 따르세요. AdMob을 사용할 때만 AdMob 앱 ID 설정이 필요합니다.
SDK 초기화 · ATT
import { Exelbid } from '@motiv-i/react-native-exelbid';
Exelbid.setLogLevel('debug'); // 'verbose' | 'debug' | 'info' | 'warning' | 'error'
const version = await Exelbid.getSdkVersion();
await Exelbid.requestTrackingAuthorization(); // iOS ATT (Android은 항상 authorized)requestTrackingAuthorization()은 TrackingAuthorizationStatus
(notDetermined · restricted · denied · authorized)를 반환하며,
getTrackingAuthorizationStatus()로 프롬프트 없이 현재 상태만 조회할 수 있습니다.
const status = await Exelbid.requestTrackingAuthorization();
if (status === 'authorized') {
// IDFA 사용 가능
}iOS 14 미만에는 ATT 개념이 없어 항상
authorized를 반환합니다. Android에서도 항상authorized를 반환하므로, 위 코드를 두 플랫폼 공통으로 호출해도 안전합니다.iOS는
Info.plist에NSUserTrackingUsageDescription이 반드시 있어야 합니다 (없으면 요청 순간 앱이 SIGKILL로 종료). iOS 프로젝트 설정 참고.
배너 광고
배너는 진짜 네이티브 뷰입니다 — <View>처럼 아무 레이아웃에나 넣으면 됩니다.
size로 지정한 영역만큼 차지합니다(기본 320×50).
import { ExelbidBanner } from '@motiv-i/react-native-exelbid';
<ExelbidBanner
adUnitId="YOUR_BANNER_UNIT"
size={{ width: 320, height: 50 }}
options={{ testing: true }}
onLoad={() => {}}
onFail={(e) => console.warn(e.code, e.message)}
onClick={() => {}}
onLeaveApp={() => {}}
onClickFinish={() => {}}
/>동작 파라미터
| prop | 기본 | 설명 |
|---|:---:|---|
| autoLoad | true | 마운트 즉시 첫 광고를 요청. false면 ref의 load()로 직접 트리거(예: ATT 동의 이후로 미루기). |
| fullWebView | false | true면 크리에이티브가 배너 영역을 꽉 채움. |
| options | — | 타깃팅/테스트 옵션(AdOptions). |
ref로 수동 제어:
const ref = useRef(null);
<ExelbidBanner ref={ref} adUnitId="…" autoLoad={false} />;
// ref.current.load(); // 요청(또는 강제 재요청)
// ref.current.stop(); // 진행 중 요청 취소 + 자동 갱신 중지이벤트: onLoad · onFail · onClick · onLeaveApp · onClickFinish.
네이티브 광고
광고 레이아웃을 React로 자유롭게 구성합니다. 각 <ExelbidNativeAd.*> 슬롯은
역할 태그가 달린 실제 네이티브 뷰가 되고, 컨테이너가 그 뷰들을 SDK에 넘기면 SDK가
크리에이티브를 그려 넣고 임프레션/클릭 추적을 겁니다. 앱에는 네이티브 코드가 전혀
없습니다.
각 슬롯은 실제 네이티브 뷰라서 스크롤·레이아웃 변경·리마운트에도 광고가 어긋나지 않습니다. 좌표를 계산해 맞춰 줄 필요가 없습니다.
import { ExelbidNativeAd } from '@motiv-i/react-native-exelbid';
import { View } from 'react-native';
<ExelbidNativeAd
adUnitId="YOUR_NATIVE_UNIT"
options={{ testing: true }}
desiredAssets={['title', 'main', 'icon', 'ctatext']}
onLoad={(data) => console.log(data.title, data.rating)}
onFail={(e) => console.warn(e.message)}
onImpression={() => {}}
onClick={() => {}}
>
<View style={{ flexDirection: 'row' }}>
<ExelbidNativeAd.Icon style={{ width: 44, height: 44 }} />
<ExelbidNativeAd.Title style={{ flex: 1, height: 20 }} />
</View>
<ExelbidNativeAd.Media style={{ height: 160 }} />
<ExelbidNativeAd.CallToAction style={{ height: 44 }} />
</ExelbidNativeAd>;슬롯 컴포넌트
| 컴포넌트 | 자산 | 종류 |
|---|---|---|
| ExelbidNativeAd.Title | 제목 | 텍스트 |
| ExelbidNativeAd.Body | 본문 | 텍스트 |
| ExelbidNativeAd.CallToAction | CTA 버튼 텍스트 | 텍스트 |
| ExelbidNativeAd.Sponsored | 광고주 표기 (Sponsored) | 텍스트 |
| ExelbidNativeAd.DisplayUrl | 표시 URL | 텍스트 |
| ExelbidNativeAd.Media | 메인 크리에이티브 (이미지 또는 동영상) | 미디어 |
| ExelbidNativeAd.Icon | 아이콘 | 이미지 |
| ExelbidNativeAd.Logo | 로고 이미지 | 이미지 |
| ExelbidNativeAd.PrivacyIcon | 프라이버시 아이콘 | 이미지 |
- 범용 형태
<ExelbidNativeAsset type="title" />로도 쓸 수 있습니다. - 메인 크리에이티브는
Media하나로 통합되어 있습니다. 정적 이미지든 동영상이든 이 슬롯에 채워집니다(별도 메인 이미지 슬롯은 없습니다).
이벤트: onLoad(data) · onFail · onClick · onImpression · onImpression50 ·
onImpression100 · onLeaveApp · onClickFinish.
슬롯 스타일 — 하나의 style로
슬롯 텍스트/이미지는 SDK가 그리는 네이티브 뷰라서 RN style이 글자색·폰트까지
직접 닿지 못합니다. 그래도 호스트는 style 하나에 전부 적으면 됩니다 — 래퍼가
RN이 적용할 박스/레이아웃과 OS-native가 적용할 콘텐츠로 자동 분리합니다.
<ExelbidNativeAd.Title
style={{
// 레이아웃/박스 → RN이 슬롯 바깥 뷰에 적용 (iOS·Android 모두)
backgroundColor: '#F4F4F5',
borderRadius: 8,
borderWidth: 1,
borderColor: '#E4E4E7',
padding: 8,
// 콘텐츠 → SDK가 그리는 안쪽 뷰에 전달
color: '#111',
fontSize: 16,
fontWeight: '700',
numberOfLines: 1,
ellipsizeMode: 'tail',
}}
/>
<ExelbidNativeAd.Media style={{ height: 160, resizeMode: 'cover' }} />네이티브 inner view로 전달되는 콘텐츠 prop: color, fontFamily, fontSize,
fontWeight, textAlign, numberOfLines, ellipsizeMode, resizeMode(이미지).
나머지(size/margin/padding/backgroundColor/border*/borderRadius …)는 표준 RN
ViewStyle로 슬롯 바깥 뷰에 적용됩니다. 즉 테두리·둥근 모서리·배경은 iOS·Android
모두 동작합니다. 단독 네이티브와 미디에이션 네이티브 슬롯 모두 동일합니다.
resizeMode는 미디어의 채움 방식입니다.cover는 종횡비 유지 + 슬롯을 꽉 채우고 넘치는 가장자리를 자르며,contain은 종횡비 유지 + 전체를 슬롯 안에 맞춥니다(여백 가능). 텍스트가 아니라 이미지/미디어 슬롯에만 의미가 있습니다.
커스텀 폰트는 iOS·Android 앱에도 등록해야 적용됩니다
광고의 글자는 RN이 아니라 휴대폰 운영체제(iOS·Android)가 직접 그립니다. 그래서
fontFamily로 폰트를 지정하려면, 그 폰트 파일이 iOS 앱과 Android 앱 양쪽에 등록
되어 있어야 합니다. RN(JS)에서 폰트 이름만 넘기면 광고 글자에는 반영되지 않고,
등록되지 않은 폰트는 휴대폰 기본 폰트로 대체됩니다.
| 폰트 종류 | 동작 |
|---|---|
| 휴대폰에 기본 내장된 폰트 ('Georgia' 등) | 추가 등록 없이 바로 적용 ✅ |
| 직접 추가한 커스텀 폰트 | 아래처럼 iOS·Android 앱에 각각 등록해야 적용 |
- iOS — 폰트 파일을 앱(Runner) 타깃에 넣고
Info.plist의UIAppFonts에 등록.fontFamily에는 PostScript 이름을 씁니다(UIFont(name:)기준). - Android — 폰트 파일을 앱의
res/font/<name>.ttf(또는.otf)에 넣습니다.fontFamily에는 확장자를 뺀 **리소스 이름(<name>)**을 씁니다 (resources.getIdentifier(name, "font", …)기준).
요청할 자산 지정 (desiredAssets)
desiredAssets로 SDK가 채울 자산을 좁힐 수 있습니다(iOS만 적용, Android는 SDK
한계로 무시됨). 값은 SDK 자산명이라 슬롯 컴포넌트명과 다릅니다: title · desc ·
desc2 · ctatext · sponsored · displayUrl · phone · address · icon ·
main · logo · rating · likes · downloads · price · salePrice · video.
<ExelbidNativeAd adUnitId="…" desiredAssets={['title', 'main', 'icon', 'ctatext']}>데이터 전용 자산 (onLoad payload)
렌더 슬롯이 없는 값(별점·가격·다운로드 수 등)은 onLoad의 data로 받아 평범한 RN
컴포넌트로 직접 그립니다. data는 NativeAdData입니다.
| 필드 | 필드 | 필드 |
|---|---|---|
| title | body | secondaryBody |
| callToAction | sponsored | displayUrl |
| phone | address | iconImageUrl |
| mainImageUrl | logoImageUrl | rating |
| likes | downloads | price |
| salePrice | hasVideo | |
iOS는 17필드 전부를 채웁니다. Android는 SDK 한계로 7필드 (
secondaryBody/phone/address/likes/downloads/price/salePrice)만 채웁니다.
전면 광고 (Interstitial)
뷰가 아니라 명령형 핸들입니다. create() → load() → (onLoad) → present()
순서로 동작합니다.
import { ExelbidInterstitialAd } from '@motiv-i/react-native-exelbid';
const ad = ExelbidInterstitialAd.create({
adUnitId: 'YOUR_UNIT',
options: { testing: true },
});
const off = ad.addListener((e) => {
if (e.event === 'onLoad') ad.present();
if (e.event === 'onFail') console.warn(e.error?.code, e.error?.message);
});
ad.load();
// 정리
// off();
// ad.destroy(); // id/리스너까지 완전 해제
// ad.stop(); // 광고 객체만 해제(같은 인스턴스로 재로드 가능)이벤트: onLoad · onFail · onWillAppear · onDidAppear · onWillDisappear ·
onDidDisappear · onClick · onLeaveApp · onClickFinish
(Android은 SDK 한계로 일부만 발사).
create에 fullWebView: true를 주면 크리에이티브가 화면을 꽉 채웁니다.
isReady()로 표시 가능 여부를 확인할 수 있습니다.
비디오 광고
전면과 동일한 명령형 핸들입니다. iOS는 재생 진행률(onProgress)도 전달합니다.
import { ExelbidVideoAd } from '@motiv-i/react-native-exelbid';
const ad = ExelbidVideoAd.create({
adUnitId: 'YOUR_UNIT',
options: { testing: true, videoSkipMin: 5 },
});
const off = ad.addListener((e) => {
if (e.event === 'onLoad') ad.present();
if (e.event === 'onProgress') console.log(`${e.percent}%`); // iOS 전용
if (e.event === 'onFail') console.warn(e.error?.message);
});
ad.load();
// off(); ad.destroy();이벤트: onLoad · onFail · onProgress(iOS) · onWillAppear · onDidAppear ·
onWillDisappear · onDidDisappear · onClick · onLeaveApp.
onProgress(재생 진행률)는 iOS에서만 발생합니다. Android는 진행률 콜백을 지원하지 않습니다.
Android 비디오 재생에 필요한 의존성은 이 패키지가 번들합니다 — 호스트 앱에서 별도로 추가할 필요가 없습니다.
광고 옵션 (AdOptions)
모든 광고의 options(전면/비디오는 create의 options)로 타깃팅/테스트 설정을
전달합니다. 모두 선택이며, 설정한 항목만 적용됩니다.
const options = {
keywords: { category: 'sports' },
yearOfBirth: 1990,
gender: 'male', // 'unspecified' | 'male' | 'female'
location: { latitude: 37.5, longitude: 127.0 },
coppa: false, // 아동 대상 여부 (COPPA)
testing: true, // 테스트 광고 (개발 중 권장)
videoSkipMin: 15, // (비디오) 스킵 가능 최소 재생 시간(초)
videoSkipAfter: 10, // (비디오) 자동 스킵 시점(초) — iOS 전용
};미디에이션
미디에이션이란?
하나의 광고 자리에 여러 광고 네트워크를 연결해 두고, 순서대로 시도해 가장 먼저 광고를 채우는 네트워크를 노출하는 방식입니다(워터폴). 한 네트워크가 실패하면 다음 네트워크로 자동 폴백하므로, 단일 네트워크보다 노출률(필레이트)이 올라갑니다.
- 시도 순서(우선순위)는 ExelBid 콘솔(서버)에서 설정합니다. 앱 코드로 순서를 정하지 않습니다.
- ExelBid 네트워크는 기본 포함(추가 설정 불필요)입니다.
- AdMob · FAN · AdFit은 사용하려면 호스트 앱이 직접 연결해야 합니다. 연결(등록)하지
않은 네트워크는 워터폴에서 자동으로 건너뜁니다(
adapterNotRegistered).
왜 직접 연결해야 하나요? 이 패키지는 각 네트워크의 어댑터 코드만 제공하고, 실제 네트워크 SDK는 포함하지 않습니다. 그래서 쓰지 않는 네트워크의 SDK가 앱에 들어가지 않습니다(앱 용량·정책 부담 최소화). 스코프: AdMob(전 포맷) · FAN(전 포맷) · AdFit(배너+네이티브 — SDK가 전면/비디오 미지원).
설정 순서
사용할 외부 네트워크(AdMob/FAN/AdFit)마다 아래를 진행합니다. ExelBid만 쓸 경우 1·5만 하면 됩니다(2~4 불필요).
- ExelBid 콘솔에서 해당 광고 단위의 워터폴(네트워크·순서)을 구성
- 네트워크 SDK 추가 — 앱에 해당 네트워크 SDK 의존성 추가 (Android / iOS)
- 네트워크별 필수 설정 — 앱 ID 등 (네트워크별 필수 설정)
- 어댑터 등록 — 이 패키지가 제공하는 모듈을 등록
- 코드 작성 —
ExelbidMediated*컴포넌트/클래스로 광고 요청 (코드에서 사용)
Android — 3rd-party 연결
(1) android/app/build.gradle에 사용할 네트워크 SDK만 추가(어댑터는 이 패키지에
포함돼 있고, 네트워크 SDK만 호스트가 추가):
dependencies {
implementation "com.google.android.gms:play-services-ads:23.4.0" // AdMob
implementation "com.facebook.android:audience-network-sdk:6.20.0" // FAN
implementation "com.kakao.adfit:ads-base:3.12.9" // AdFit
}AdFit을 쓰면 Kakao maven 저장소가 필요합니다 — 루트
android/build.gradle의allprojects.repositories(또는settings.gradle)에 추가:maven { url "https://devrepo.kakao.com/nexus/content/groups/public/" }
(2) 앱 시작 시 사용할 네트워크만 등록(JS 한 줄):
import { Exelbid } from '@motiv-i/react-native-exelbid';
Exelbid.registerMediationNetworks(['admob', 'fan', 'adfit']);⚠️ SDK를 추가하지 않고 등록만 하면, 그 네트워크 광고를 로드하는 시점에
NoClassDefFoundError가 발생합니다. (1)·(2)는 항상 같이 하세요.
iOS — 3rd-party 연결
iOS는 어댑터가 별도 패키지라 호스트 앱에 연결 + AppDelegate에서 등록합니다(JS
registerMediationNetworks는 iOS에서 동작하지 않으니 아래처럼 Swift에서 등록하세요).
CocoaPods 팟은 AdMob·FAN만 제공합니다(AdFit은 SwiftPM 전용).
(1) ios/Podfile의 앱 타깃에 추가 후 RCT_NEW_ARCH_ENABLED=1 pod install:
pod 'ExelBid_Mediation_Adapter/AdMob'
pod 'ExelBid_Mediation_Adapter/FAN'(2) Swift 코드에서 등록(메타타입이라 Swift 필요 — AppDelegate를 Swift로 두거나 Swift 헬퍼를 추가):
import ExelBidSDK
import ExelBidMediationAdapter // CocoaPods 단일 모듈로 AdMob·FAN 어댑터 노출
import GoogleMobileAds
// application(_:didFinishLaunchingWithOptions:) 안, 광고 요청 전에:
ExelBidMediationKit.shared.register(modules: [
AdMobMediationModule.self,
FANMediationModule.self,
])
MobileAds.shared.start(completionHandler: nil) // AdMob 초기화ObjC++ AppDelegate라면 Swift 헬퍼(
@objc class)에 위 등록을 넣고#import "<앱>-Swift.h"후 호출합니다. 첫 Swift 파일을 추가하면 Xcode가-Swift.h를 자동 생성하므로 브리징 헤더는 불필요합니다. (example/ios의MediationBootstrap.swift참고.)ExelBid 본가 네트워크는 이 패키지가 자동 등록하므로 호스트는 3rd-party만 등록하면 됩니다.
네트워크별 필수 설정
네트워크마다 앱에 추가로 넣어야 하는 식별자/설정이 다릅니다.
| 네트워크 | Android 필수 | iOS 필수 | 비고 |
|---|---|---|---|
| AdMob | AndroidManifest.xml에 com.google.android.gms.ads.APPLICATION_ID 메타데이터 | Info.plist에 GADApplicationIdentifier + MobileAds.shared.start(...) | 앱 ID는 AdMob 콘솔 발급(광고 단위 ID와 다름). 없으면 init 시 크래시 |
| FAN (Meta) | 추가 식별자 없음 (AndroidX 필요) | 추가 식별자 없음 (SKAdNetwork·ATT 권장) | 앱 단위 ID 없이 placement ID로 동작. 실기기 테스트는 Meta 테스트 디바이스 등록 필요 |
| AdFit (Kakao) | Kakao maven 저장소 | (CocoaPods 미지원 — SwiftPM 전용) | 앱 단위 ID 없이 client ID로 동작. 전면/비디오 미지원(배너·네이티브만) |
AdMob App ID 설정 예시
Android —
AndroidManifest.xml의<application>안:<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY" />iOS —
Info.plist:<key>GADApplicationIdentifier</key> <string>ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY</string>
공통: 모든 네트워크의 실제 낙찰은 ExelBid 콘솔 워터폴에 해당 네트워크 라인아이템이 등록되어 있어야 발생합니다. 광고 단위 ID(placement/client ID 등)는 보통 콘솔 설정에 포함됩니다.
코드에서 사용
미디에이션 광고는 일반 광고와 동일한 컴포넌트/클래스에 Mediated가 붙은 버전을
씁니다: ExelbidMediatedBanner · ExelbidMediatedNativeAd ·
ExelbidMediatedInterstitialAd · ExelbidMediatedVideoAd. JS API는 iOS·Android
공통입니다.
공통 추가 옵션:
perNetworkTimeout— 네트워크당 타임아웃(초). 초과 시 다음 네트워크로 폴백.onWinningNetwork/winningNetwork— 낙찰(노출 성공)된 네트워크 이름.onWaterfall/WaterfallEvent— 워터폴 진행 추적(아래).
Mediated Banner / Native (Fabric 뷰)
import { ExelbidMediatedBanner } from '@motiv-i/react-native-exelbid';
<ExelbidMediatedBanner
adUnitId="YOUR_UNIT"
size={{ width: 320, height: 50 }}
perNetworkTimeout={5}
onWinningNetwork={(net) => console.log('낙찰:', net)}
onWaterfall={(e) => console.log(e.type)} // fetching→trying→won/lost/noFill
onLoad={() => {}}
onFail={(e) => console.warn(e.message)}
/>;ExelbidMediatedNativeAd는 일반 네이티브와 동일한 슬롯·스타일·onLoad(data)
API에 perNetworkTimeout·onWinningNetwork만 추가됩니다.
Mediated Interstitial / Video (명령형) — 일반 전면/비디오와 같은
create/load/present 패턴이며, 이벤트에 onWaterfall이 추가되고 onLoad 이벤트
데이터에 winningNetwork가 실립니다.
import { ExelbidMediatedVideoAd } from '@motiv-i/react-native-exelbid';
const ad = ExelbidMediatedVideoAd.create({
adUnitId: 'YOUR_UNIT',
perNetworkTimeout: 5,
});
const off = ad.addListener((e) => {
if (e.event === 'onWaterfall') console.log(e.waterfall?.type);
if (e.event === 'onLoad') {
console.log('낙찰:', e.winningNetwork);
ad.present();
}
});
ad.load();
// off(); ad.destroy(); // (전면/비디오 미디에이션은 stop()도 지원)WaterfallEvent
onWaterfall이 전달하는 이벤트(type으로 분기). 워터폴 디버깅에 유용하며,
decodeWaterfallEvent/formatWaterfallEvent 헬퍼도 export됩니다.
| type | 의미 | 추가 필드 |
|---|---|---|
| fetching | 워터폴 조회 시작 | — |
| fetched | 네트워크 목록 수신 | networks: string[] |
| trying | 특정 네트워크 시도 | network, position, total, unitId |
| won | 낙찰 | network, position, latencyMs |
| lost | 해당 네트워크 실패 | network, position, reason |
| noFill | 전체 실패 | — |
등록하지 않은 네트워크가
lost(reason: 'adapterNotRegistered')로 보이면, 해당 네트워크의 SDK 추가 + 등록이 빠진 것입니다(Android / iOS).플레이스홀더 단위 ID로도 waterfall 로그(fetching→trying→lost/noFill)가 뜨면 워터폴 브리지가 정상입니다. 실광고는 각 네트워크의 실 단위 ID가 있어야 노출됩니다.
에러 처리 (AdError)
onFail로 전달되는 AdError는 { code, message, statusCode? } 형태입니다.
statusCode는 실패한 요청의 HTTP 상태(SDK가 보고할 때만, 없으면 0/undefined)입니다.
onFail={(error) => {
console.warn(`[${error.code}] ${error.message}` +
(error.statusCode ? ` (HTTP ${error.statusCode})` : ''));
}}흔한 실패 상황:
| 상황 | 설명 |
|---|---|
| noFill | 노출 가능한 광고 없음. 테스트/플레이스홀더 단위에서 가장 흔함(정상 동작) |
| 잘못된 광고 단위 ID | 존재하지 않거나 형식이 틀린 unit ID |
| 네트워크 오류 | 통신 실패 — statusCode가 실릴 수 있음(HTTP 상태) |
| 준비 전 표시 | onLoad 전에 present() 호출 |
| 취소됨 | stop() 등으로 진행 중 요청 취소 |
테스트/플레이스홀더 단위에서는 노출 가능한 광고가 없어
onFail(noFill)이 흔히 발생합니다 — 이때 네이티브→JS 이벤트 브리지가 정상 동작하는 것입니다.
공통 API 요약
| 클래스 / 컴포넌트 | 용도 |
|---|---|
| Exelbid | 전역 — getSdkVersion · setLogLevel · requestTrackingAuthorization · getTrackingAuthorizationStatus · registerMediationNetworks |
| ExelbidBanner | 임베디드 배너 (ref로 load/stop) |
| ExelbidNativeAd + ExelbidNativeAd.* 슬롯 | 호스트 렌더링 네이티브 광고 |
| ExelbidInterstitialAd / ExelbidVideoAd | 전면 / 비디오 (명령형 create/load/present) |
| ExelbidMediatedBanner / …NativeAd / …InterstitialAd / …VideoAd | 미디에이션 광고 |
| AdOptions | 타깃팅/테스트 옵션 |
| AdError | 에러 ({ code, message, statusCode? }) |
| WaterfallEvent · TrackingAuthorizationStatus · NativeAdData | 워터폴 이벤트 / ATT 상태 / 네이티브 자산 값 |
샘플 앱
example/에 RN 0.76 네이티브 호스트 셸(android/, ios/)이 포함된 전체 동작 예제가
있습니다. Home / Ads / Mediation 3탭에서 전 광고 유형을 실행해 볼 수 있습니다.
corepack enable # 패키지매니저는 yarn 4
cd example && yarn install
cd ios && RCT_NEW_ARCH_ENABLED=1 pod install # iOS
cd .. && yarn android --deviceId <adb id> # Android (newArchEnabled 이미 true)example/src/adUnitIds.ts의 플랫폼별 테스트 단위 ID를 본인 ExelBid 광고 단위로
바꾸세요. 상태가 failed: …(noFill)로 바뀌면 네이티브→JS 이벤트 브리지가 정상
동작하는 것입니다.
트러블슈팅
NativeExelbid.setLogLevel is not a function (런타임, 빌드는 통과) — codegen이
스펙을 생성하지 못했거나 New Arch가 꺼진 경우입니다. RCT_NEW_ARCH_ENABLED=1 pod
install(iOS) / newArchEnabled=true(Android)를 확인하고, iOS는 Pods 재설치 후
클린 빌드하세요.
iOS 'ReactNativeExelbidSpec/ReactNativeExelbidSpec.h' file not found — codegen이
이 라이브러리를 건너뛴 경우입니다. pod install을 New Arch로 다시 돌리고, 그래도
안 되면 example/ios 기준으로 Pods·DerivedData를 지우고 재설치하세요.
iOS 앱이 켜자마자 종료(ATT) — requestTrackingAuthorization()을 호출하는데
Info.plist에 NSUserTrackingUsageDescription이 없으면 iOS가 SIGKILL로 종료합니다.
iOS 프로젝트 설정의 문구를 추가하세요.
미디에이션 NoClassDefFoundError(Android) / 컴파일 에러(iOS) — 어댑터는 등록했는데
해당 네트워크 SDK를 앱에 추가하지 않은 경우입니다. SDK 추가와 등록은 항상 같이 하세요
(미디에이션).
커스텀 폰트가 광고에 반영되지 않음 — 광고 글자는 OS가 그리므로 폰트를 iOS
(UIAppFonts)·Android(res/font/) 앱에 등록해야 합니다
(커스텀 폰트).
onFail(noFill)만 계속 발생 — 테스트/플레이스홀더 단위 ID는 노출 가능한 광고가
없어 정상적으로 noFill이 납니다. 실 단위 ID로 바꿔 확인하세요.
부록: AdMob 옵션 (선택)
네이티브 광고 검사기 끄기 — AdMob 네이티브 광고를 테스트 기기에서 띄우면 Google Mobile Ads SDK가 광고 위에 검사기 오버레이를 표시합니다(테스트 광고 한정). 거슬리면:
Android —
AndroidManifest.xml의<application>안:<meta-data android:name="com.google.android.gms.ads.flag.NATIVE_AD_DEBUGGER_ENABLED" android:value="false" />iOS —
Info.plist:<key>GADNativeAdValidatorEnabled</key> <false/>
전면/비디오 노치 채우기 (Android) — AdMob 전면·비디오는 GMA SDK의 AdActivity가
렌더링하며 기본적으로 상태바/노치 영역을 비워 둡니다. 그 영역까지 채우려면 호스트 앱
res/values/styles.xml에 풀스크린 테마를 정의하고 AndroidManifest.xml에서
tools:replace로 AdActivity의 테마를 교체하세요.
<!-- res/values/styles.xml -->
<style name="AdCutoutTheme" parent="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowFullscreen">true</item>
</style><!-- AndroidManifest.xml (<manifest>에 xmlns:tools 추가) -->
<activity android:name="com.google.android.gms.ads.AdActivity"
android:theme="@style/AdCutoutTheme"
tools:replace="android:theme" />
windowFullscreen은 상태바를 숨기고,windowLayoutInDisplayCutoutMode=shortEdges는 컷아웃(노치/펀치홀) 영역까지 진입합니다(API 28+, 하위는 무시). GMA SDK가 런타임에 인셋을 직접 적용해 위 테마가 무시되면 GMA SDK 업그레이드가 근본 해법입니다.
