임베디드 오퍼월 통합
앱의 특정 탭에 AdChain SDK를 임베디드 방식으로 통합하는 실무 가이드입니다.
대상: 임베디드 오퍼월을 구현하는 개발팀
SDK 버전: v1.0.21
마지막 업데이트: 2025-10-30
개요
이 가이드는 앱의 혜택/포인트 탭에 AdChain SDK를 임베디드 방식으로 통합하는 방법을 설명합니다. React Native의 <AdchainOfferwallView /> 컴포넌트를 사용하여 기존 화면에 오퍼월을 삽입합니다.
이 문서는 실제 프로덕션 환경의 구현 사례를 기반으로 작성되었습니다. 일반적인 SDK 사용법은 React Native API 레퍼런스를 참고하세요.
빠른 시작: 필수 Props
임베디드 오퍼월에서 사용하는 Props와 필수 여부는 다음과 같습니다:
placementId
✅ 필수
✅ 필수
오퍼월 식별자
style
✅ 필수
✅ 필수
{ flex: 1 } 권장
onCustomEvent
✅ 필수
✅ 필수
WebView → Native 이벤트 처리
onBackPressOnFirstPage
❌ 불필요
✅ 필수
Android 백버튼: 첫 페이지일 때
onBackNavigated
❌ 불필요
✅ 필수
Android 백버튼: 뒤로가기 성공 시
onOfferwallOpened
선택
선택
WebView 로드 완료
onOfferwallClosed
선택
선택
오퍼월 닫힘
onOfferwallError
권장
권장
WebView 로딩 실패
placementId 설정
placementId는 애드체인 팀과 사전 협의하여 정의합니다. 이 예시에서는 "benefits_tab"을 사용합니다.
<AdchainOfferwallView
placementId="benefits_tab" // 실제 값은 애드체인 팀과 협의
// ...
/>주의: 실제 placementId는 애드체인 서버 설정과 일치해야 합니다. 값을 변경할 경우 AdChain 팀과 사전 협의가 필요합니다.
필수 이벤트 처리: onCustomEvent
임베디드 오퍼월에서 가장 중요한 Props입니다. WebView에서 발생하는 이벤트를 Native로 전달받아 처리합니다.
처리해야 하는 이벤트
1. buy_ticket (구현 예시)
buy_ticket (구현 예시)사용자가 오퍼월에서 특정 액션을 요청할 때 발생합니다. 앱의 해당 기능 UI를 표시합니다.
payload 구조 (예시):
{
ticketId: string,
amount: number
}처리 방법:
onCustomEvent={(eventType, payload) => {
if (eventType === 'buy_ticket') {
// 앱의 티켓 구매 UI를 표시
ShowBuyTicketUI();
// 또는 payload를 사용하여 처리
// const { ticketId, amount } = payload;
}
}}참고: 이 이벤트는 특정 구현 사례입니다. 실제 이벤트 타입과 payload 구조는 애드체인 팀과 사전 협의하여 정의합니다.
2. show_ticket_list (구현 예시)
show_ticket_list (구현 예시)사용자가 보유 항목 목록을 보려고 할 때 발생합니다. 앱의 해당 리스트 화면으로 이동합니다.
payload 구조 (예시):
{
userId: string
}처리 방법:
onCustomEvent={(eventType, payload) => {
if (eventType === 'show_ticket_list') {
// 앱의 티켓 리스트를 표시
ShowTicketListUI();
}
}}참고: payload를 사용하지 않는 경우도 있습니다. 실제 구현은 비즈니스 로직에 따라 다릅니다.
전체 onCustomEvent 예제
<AdchainOfferwallView
placementId="benefits_tab"
style={{ flex: 1 }}
onCustomEvent={(eventType, payload) => {
console.log('[WebView → App]', eventType, payload);
// 구현 예시 이벤트 처리
if (eventType === 'buy_ticket') {
// 티켓 구매 UI 표시
ShowBuyTicketUI();
}
else if (eventType === 'show_ticket_list') {
// 티켓 리스트 표시
ShowTicketListUI();
}
// 처리되지 않은 이벤트 로깅
else {
console.warn('알 수 없는 이벤트:', eventType, payload);
}
}}
/>중요: 실제 이벤트 타입과 처리 방법은 애드체인 팀과 사전 협의하여 정의하세요. 위 예시는 참고용입니다.
SafeArea 처리
Android와 iOS에서 상태바/노치 영역을 올바르게 처리하기 위해 SafeArea를 적용해야 합니다.
문제 상황
Android: 오퍼월이 상태바 영역까지 올라가서 상단이 화면 끝에 붙어 표시됨
iOS: 노치나 Dynamic Island가 있는 기기에서 오퍼월 콘텐츠가 가려질 수 있음
해결 방법
SafeArea 라이브러리를 사용하여 오퍼월을 감쌉니다. react-native-safe-area-context 또는 다른 SafeArea 관련 라이브러리를 사용할 수 있습니다.
1. 라이브러리 설치 (예시):
npm install react-native-safe-area-context2. SafeAreaView 적용:
import { SafeAreaView } from 'react-native-safe-area-context';
import { AdchainOfferwallView } from '@1selfworld/adchain-sdk-react-native';
const BenefitsTab = () => {
return (
<SafeAreaView style={{ flex: 1 }} edges={['top']}>
<AdchainOfferwallView
style={{ flex: 1, width: '100%' }}
placementId="benefits_tab"
// ... 다른 props
/>
</SafeAreaView>
);
};edges 옵션 설명:
edges={['top']}: 상단만 SafeArea 적용하단 탭바가 있는 경우
bottom은 제외합니다iOS와 Android 모두 자동으로 대응됩니다
주의사항:
SafeAreaView는 오퍼월을 포함하는 전체 탭 화면에 적용합니다
다른 화면(홈, 설정 등)에는 영향을 주지 않습니다
iOS 노치/Dynamic Island 기기와 Android 상태바 영역 모두에서 올바르게 동작합니다
프로젝트에서 이미 사용 중인 SafeArea 라이브러리가 있다면 그것을 사용해도 무방합니다
Android 백버튼 처리
Android에서는 하드웨어 백버튼을 직접 처리해야 합니다. 그렇지 않으면 WebView 내부 네비게이션을 무시하고 앱이 종료됩니다.
문제 상황
사용자가 오퍼월 안에서 여러 페이지를 이동함 (예: 메인 → 이벤트 상세 → 참여 화면)
백버튼을 누르면 앱이 종료됨 (WebView 뒤로가기 무시)
해결 방법
React Native의 BackHandler로 백버튼 이벤트를 캐치하고, UIManager.dispatchViewManagerCommand로 네이티브에 처리를 위임합니다.
import React, { useRef, useEffect, useState } from 'react';
import { BackHandler, findNodeHandle, UIManager } from 'react-native';
import { AdchainOfferwallView } from '@1selfworld/adchain-sdk-react-native';
const BenefitsTab = () => {
const offerwallViewRef = useRef(null);
const [shouldAllowExit, setShouldAllowExit] = useState(false);
// Android 백버튼 처리
useEffect(() => {
const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
if (offerwallViewRef.current) {
const viewId = findNodeHandle(offerwallViewRef.current);
if (viewId) {
UIManager.dispatchViewManagerCommand(viewId, 'handleBackPress', []);
return true; // 앱 종료 방지
}
}
return false;
});
return () => backHandler.remove();
}, []);
// 앱 종료 처리
useEffect(() => {
if (shouldAllowExit) {
const timer = setTimeout(() => BackHandler.exitApp(), 100);
return () => clearTimeout(timer);
}
}, [shouldAllowExit]);
return (
<AdchainOfferwallView
ref={offerwallViewRef}
placementId="benefits_tab"
style={{ flex: 1, width: '100%' }}
// Android 백버튼 처리
onBackPressOnFirstPage={() => {
console.log('첫 페이지 - 앱 종료 허용');
setShouldAllowExit(true);
}}
onBackNavigated={() => {
console.log('WebView 뒤로가기 성공');
setShouldAllowExit(false);
}}
// 이벤트 처리
onCustomEvent={(eventType, payload) => {
if (eventType === 'buy_ticket') {
ShowBuyTicketUI();
} else if (eventType === 'show_ticket_list') {
ShowTicketListUI();
}
}}
/>
);
};동작 방식:
백버튼 누름 →
BackHandler이벤트 캐치handleBackPress명령을 네이티브로 전송네이티브 WebView에서
canGoBack()확인:true(2+ 페이지) → WebView 내부 뒤로가기 →onBackNavigated호출false(첫 페이지) →onBackPressOnFirstPage호출
앱 종료 처리 커스터마이징:
위 예시에서는 BackHandler.exitApp()으로 앱을 즉시 종료하지만, 기존 앱의 종료 로직으로 변경할 수 있습니다:
onBackPressOnFirstPage={() => {
// 예시 1: 종료 확인 토스트
Toast.show('한 번 더 누르면 종료됩니다');
// 예시 2: 종료 확인 팝업
Alert.alert('앱 종료', '앱을 종료하시겠습니까?', [
{ text: '취소', style: 'cancel' },
{ text: '종료', onPress: () => BackHandler.exitApp() }
]);
// 예시 3: 홈 화면으로 이동
navigation.navigate('HomeTab');
}}앱마다 종료 동작이 다를 수 있으므로 (즉시 종료, 토스트 표시, 확인 팝업 등) 기존 앱의 백버튼 동작에 맞춰 구현하세요.
주의: iOS에서는 백버튼이 없으므로 이 코드가 불필요합니다. Android에서만 작동합니다.
전체 샘플 코드
임베디드 오퍼월을 구현하는 전체 코드입니다:
import React, { useRef, useEffect, useState } from 'react';
import { BackHandler, findNodeHandle, UIManager, Alert, Platform } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { AdchainOfferwallView } from '@1selfworld/adchain-sdk-react-native';
const BenefitsTab = () => {
const offerwallViewRef = useRef(null);
const [shouldAllowExit, setShouldAllowExit] = useState(false);
// Android 백버튼 처리 (Android만)
useEffect(() => {
if (Platform.OS !== 'android') return;
const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
if (offerwallViewRef.current) {
const viewId = findNodeHandle(offerwallViewRef.current);
if (viewId) {
UIManager.dispatchViewManagerCommand(viewId, 'handleBackPress', []);
return true; // 앱 종료 방지
}
}
return false;
});
return () => backHandler.remove();
}, []);
// 앱 종료 처리
useEffect(() => {
if (shouldAllowExit) {
const timer = setTimeout(() => BackHandler.exitApp(), 100);
return () => clearTimeout(timer);
}
}, [shouldAllowExit]);
return (
<SafeAreaView style={{ flex: 1 }} edges={['top']}>
<AdchainOfferwallView
ref={offerwallViewRef}
placementId="benefits_tab"
style={{ flex: 1, width: '100%' }}
// Android 백버튼 처리
onBackPressOnFirstPage={() => {
console.log('첫 페이지 - 앱 종료 허용');
setShouldAllowExit(true);
}}
onBackNavigated={() => {
console.log('WebView 뒤로가기 성공');
setShouldAllowExit(false);
}}
// 기본 이벤트 (선택)
onOfferwallOpened={() => console.log('오퍼월 열림')}
onOfferwallClosed={() => console.log('오퍼월 닫힘')}
onOfferwallError={(error) => {
console.error('오퍼월 오류:', error);
Alert.alert('오류', '오퍼월을 불러올 수 없습니다. 다시 시도해주세요');
}}
// WebView 이벤트 브릿지 (필수)
onCustomEvent={(eventType, payload) => {
console.log('[WebView → App]', eventType, payload);
// 필수 이벤트 처리 (구현 예시)
if (eventType === 'buy_ticket') {
// 티켓 구매 UI 표시
ShowBuyTicketUI();
}
else if (eventType === 'show_ticket_list') {
// 티켓 리스트 표시
ShowTicketListUI();
}
// 처리되지 않은 이벤트 로깅
else {
console.warn('처리되지 않은 이벤트:', eventType, payload);
}
}}
/>
</SafeAreaView>
);
};
export default BenefitsTab;프로덕션 배포 체크리스트
앱을 배포하기 전에 다음 항목들을 확인하세요:
SDK 설정
초기화
인증
placementId
UI/UX 처리
Android 백버튼 처리 구현됨 (Android 필수)
이벤트 처리
onCustomEvent핸들러 구현됨필수 이벤트 처리 (애드체인 팀과 협의된 이벤트)
에러 처리
모든 SDK 메서드에 try-catch 적용
사용자에게 명확한 에러 메시지 표시
처리되지 않은 이벤트 로깅 (
console.warn추가)
문제 해결
오퍼월이 로딩되지 않아요
증상: 탭이 빈 화면으로 표시됨
확인사항:
SDK가 초기화되었나요?
const isReady = await AdchainSDK.isInitialized(); console.log('SDK 준비:', isReady);사용자가 로그인되었나요?
const loggedIn = await AdchainSDK.isLoggedIn(); console.log('로그인 상태:', loggedIn);onOfferwallError에서 에러가 발생했나요?onOfferwallError={(error) => { console.error('오류:', error); // 로그 확인 }}
Android 백버튼이 작동하지 않아요
증상: 백버튼을 누르면 앱이 종료됨 (WebView 뒤로가기 무시)
해결:
ref={offerwallViewRef}추가했는지 확인BackHandler.addEventListener등록했는지 확인UIManager.dispatchViewManagerCommand호출 코드 확인
위의 "Android 백버튼 처리" 섹션의 전체 코드를 참고하세요.
이벤트가 작동하지 않아요
증상: WebView에서 버튼을 눌러도 아무 반응이 없음
해결:
onCustomEvent핸들러가 구현되었는지 확인애드체인 팀과 협의된 이벤트 타입을 처리하는지 확인
콘솔 로그 확인:
onCustomEvent={(eventType, payload) => { console.log('[WebView → App]', eventType, payload); // 이벤트가 들어오는지 확인 // ... }}
환경 전환 (STAGING → PRODUCTION)
테스트 환경에서 프로덕션으로 전환할 때:
1. app.json 수정:
// 개발/테스트
"environment": "STAGING"
// 프로덕션
"environment": "PRODUCTION"2. 재빌드 필수:
# iOS
npx expo prebuild --platform ios --clean
npx expo run:ios
# Android
npx expo prebuild --platform android --clean
npx expo run:android중요: 환경 변경 시 반드시 npx expo prebuild --clean 실행!
추가 리소스
일반 SDK 사용법: React Native API 레퍼런스
설치 가이드: React Native 시작하기
문제 해결: 문제 해결 가이드
FAQ: 자주 묻는 질문
마지막 업데이트: 2025-10-30 문서 버전: 1.0 SDK 버전: v1.0.21
Last updated