Android
AdStage 인앱 이벤트 통합 가이드 (Android)
목차
개요
AdStage 인앱 이벤트는 사용자 행동과 앱 이벤트를 추적하여 마케팅 분석 및 최적화를 지원합니다.
주요 기능
- 유연한 이벤트 전송: 간단한 호출부터 상세한 컨텍스트까지
- 자동 컨텍스트 수집: 디바이스 정보, 사용자 속성 자동 포함
- 세션 관리: 자동 세션 추적 및 관리
- Builder 패턴: 가독성 높은 DSL 스타일 지원
- 비동기 처리: Coroutine 기반 suspend 함수
- 오프라인 지원: 네트워크 재연결 시 자동 재전송
기본 설정
1. Gradle 의존성 추가
settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://repo.nbase.io/repository/nbase-releases") }
}
}build.gradle.kts (Module: app)
dependencies {
implementation("io.nbase:nbase-adapter-adstage:3.0.9")
// 필수 의존성
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
implementation("com.squareup.okhttp3:okhttp:4.11.0")
}2. SDK 초기화
import io.nbase.adapter.adstage.AdStage
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// AdStage 초기화
AdStage.initialize(
context = this,
apiKey = "your-api-key-here",
serverUrl = "https://api.adstage.app" // 선택사항, 기본값 사용 가능
)
Log.d("AdStage", "✅ SDK 초기화 완료")
}
}3. AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 필수 권한 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<!-- ... -->
</application>
</manifest>타입 안전 이벤트 전송
1. 가장 간단한 방식
trackEvent는 suspend 함수와 콜백 오버로드를 함께 제공합니다.
suspend 함수는 코루틴 스코프 안에서 호출하고, 코루틴 밖에서는 콜백 버전을 사용합니다.
import io.nbase.adapter.adstage.AdStage
import io.nbase.adapter.adstage.models.AdStageEvent
import io.nbase.adapter.adstage.models.SignUpMethod
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
CoroutineScope(Dispatchers.IO).launch {
// 파라미터 없는 이벤트 (suspend)
AdStage.trackEvent(AdStageEvent.FirstOpen())
// 로그인 이벤트 (method는 문자열, SignUpMethod enum의 value를 사용)
AdStage.trackEvent(
AdStageEvent.Login(method = SignUpMethod.EMAIL.value)
)
}
// 콜백 버전 (코루틴 없이 호출)
AdStage.trackEvent(AdStageEvent.Login(method = SignUpMethod.EMAIL.value)) { result ->
when (result) {
is TrackEventResult.Success -> Log.d("AdStage", "전송 성공")
is TrackEventResult.Failure -> Log.e("AdStage", "전송 실패: ${result.error}")
}
}2. 구매 이벤트 (검증 예제)
value가 있을 경우 currency는 필수입니다. 누락 시 이벤트 객체 생성 시점에
IllegalArgumentException이 발생합니다(런타임 검증).
// ✅ GOOD: value와 currency 함께 제공
AdStage.trackEvent(
AdStageEvent.Purchase(
value = 29.99,
currency = "USD",
transactionId = "TXN_123456"
)
)
// ❌ BAD: value만 제공 시 런타임 예외 (IllegalArgumentException)
AdStage.trackEvent(
AdStageEvent.Purchase(
value = 29.99 // ❌ 예외: value가 있으면 currency 필수!
)
)
// ✅ GOOD: currency 없이 transactionId만
AdStage.trackEvent(
AdStageEvent.Purchase(
transactionId = "TXN_123456"
)
)3. 상품 조회
// 파라미터와 함께
AdStage.trackEvent(
AdStageEvent.ProductDetailsView(
itemId = "PROD_123",
itemName = "Wireless Earbuds"
)
)
// 파라미터 없이
AdStage.trackEvent(AdStageEvent.ProductListView())4. 커스텀 이벤트 (event_name 자동 승격)
표준 이벤트 외에 앱 고유의 이벤트를 전송할 때는 AdStageEvent.Custom을 사용합니다.
파라미터에 event_name 키를 포함하면, 해당 값이 실제 이벤트 이름으로 자동 승격되어 대시보드에 저장됩니다.
// 예: 'promotion_click'이라는 커스텀 이벤트 전송
AdStage.trackEvent(
AdStageEvent.Custom(
params = mapOf(
"event_name" to "promotion_click",
"promotion_id" to "summer_sale_2025",
"screen" to "home_banner"
)
)
)
// 대시보드에는 "promotion_click" 이벤트로 수집됨
// event_name 없이 일반 커스텀 이벤트
AdStage.trackEvent(
AdStageEvent.Custom(
params = mapOf(
"action" to "button_click",
"button_id" to "promo_banner",
"screen" to "home",
"timestamp" to System.currentTimeMillis()
)
)
)
// 대시보드에는 "custom" 이벤트로 수집됨
// Click, View도 자유 파라미터 지원
AdStage.trackEvent(
AdStageEvent.Click(
params = mapOf(
"campaign_id" to "SUMMER2025",
"ad_group" to "electronics"
)
)
)표준 이벤트 카탈로그
AdStage SDK는 46개의 표준 이벤트를 제공합니다.
📌 광고 추적 (4개)
// 1. 광고 클릭
AdStage.trackEvent(
AdStageEvent.Click(
params = mapOf("campaign_id" to "CAMP_123")
)
)
// 2. 광고 노출
AdStage.trackEvent(
AdStageEvent.View(
params = mapOf("impression_id" to "IMP_456")
)
)
// 3. 앱 설치
AdStage.trackEvent(AdStageEvent.Install())
// 4. 커스텀 이벤트 (event_name으로 이벤트 이름 지정)
AdStage.trackEvent(
AdStageEvent.Custom(
params = mapOf(
"event_name" to "promotion_click",
"promotion_id" to "summer_sale_2025"
)
)
)👤 사용자 라이프사이클 (5개)
SignUp과 Login의 method는 문자열(String?) 파라미터입니다. 직접 문자열을
넣거나, SDK가 제공하는 SignUpMethod enum의 .value를 사용합니다.
// SDK 제공 SignUpMethod enum (참고용)
enum class SignUpMethod(val value: String) {
EMAIL("email"),
GOOGLE("google"),
APPLE("apple"),
FACEBOOK("facebook"),
KAKAO("kakao"),
NAVER("naver")
}
// 1. 회원가입 완료
AdStage.trackEvent(
AdStageEvent.SignUp(method = SignUpMethod.GOOGLE.value)
)
// 2. 회원가입 시작
AdStage.trackEvent(AdStageEvent.SignUpStart())
// 3. 로그인 (문자열 직접 사용도 가능: method = "email")
AdStage.trackEvent(
AdStageEvent.Login(method = SignUpMethod.EMAIL.value)
)
// 4. 로그아웃
AdStage.trackEvent(AdStageEvent.Logout())
// 5. 앱 최초 실행
AdStage.trackEvent(AdStageEvent.FirstOpen())📄 콘텐츠 조회 (6개)
// 1. 홈 화면
AdStage.trackEvent(AdStageEvent.HomeView())
// 2. 상품 목록
AdStage.trackEvent(
AdStageEvent.ProductListView(itemCategory = "electronics")
)
// 3. 검색 결과
AdStage.trackEvent(
AdStageEvent.SearchResultView(searchTerm = "wireless headphones")
)
// 4. 상품 상세
AdStage.trackEvent(
AdStageEvent.ProductDetailsView(
itemId = "PROD_123",
itemName = "Wireless Earbuds"
)
)
// 5. 페이지 조회 (웹)
AdStage.trackEvent(
AdStageEvent.PageView(
pageUrl = "https://example.com/products",
pageTitle = "Products"
)
)
// 6. 화면 조회 (앱)
AdStage.trackEvent(
AdStageEvent.ScreenView(
screenName = "product_detail",
screenClass = "ProductDetailActivity"
)
)🛒 전자상거래 (7개)
EcommerceItem은 itemId, itemName, price, quantity 4개 필드만 가집니다.
// 1. 장바구니 추가
AdStage.trackEvent(
AdStageEvent.AddToCart(
value = 99000.0,
currency = "KRW",
items = listOf(
EcommerceItem(
itemId = "PROD_123",
itemName = "Wireless Earbuds",
price = 99000.0,
quantity = 1
)
)
)
)
// 2. 장바구니 제거
AdStage.trackEvent(
AdStageEvent.RemoveFromCart(
value = 50000.0,
currency = "KRW"
)
)
// 3. 위시리스트 추가
AdStage.trackEvent(
AdStageEvent.AddToWishlist(
itemId = "PROD_456",
itemName = "Smart Watch"
)
)
// 4. 결제 정보 입력
AdStage.trackEvent(
AdStageEvent.AddPaymentInfo(paymentType = "credit_card")
)
// 5. 결제 시작
AdStage.trackEvent(
AdStageEvent.BeginCheckout(
value = 150000.0,
currency = "KRW"
)
)
// 6. 구매 완료 ⭐⭐⭐
AdStage.trackEvent(
AdStageEvent.Purchase(
value = 129000.0,
currency = "KRW",
transactionId = "ORDER_20250105_001",
tax = 12900.0,
shipping = 3000.0,
coupon = "SUMMER2025",
items = listOf(
EcommerceItem(
itemId = "PROD_123",
itemName = "Wireless Earbuds",
price = 126000.0,
quantity = 1
)
)
)
)
// 7. 환불
AdStage.trackEvent(
AdStageEvent.Refund(
transactionId = "ORDER_20250105_001",
value = 129000.0,
currency = "KRW"
)
)🎮 진행/성취 (4개)
// 1. 튜토리얼 시작
AdStage.trackEvent(
AdStageEvent.TutorialBegin(
params = mapOf("tutorial_id" to "intro")
)
)
// 2. 튜토리얼 완료
AdStage.trackEvent(
AdStageEvent.TutorialComplete(
params = mapOf("duration_seconds" to 120)
)
)
// 3. 레벨 업
AdStage.trackEvent(
AdStageEvent.LevelUp(
level = 25,
character = "warrior"
)
)
// 4. 업적 달성
AdStage.trackEvent(
AdStageEvent.Achievement(achievementId = "first_win")
)💬 상호작용 (7개)
// 1. 검색
AdStage.trackEvent(
AdStageEvent.Search(searchTerm = "gaming laptop")
)
// 2. 콘텐츠 선택
AdStage.trackEvent(
AdStageEvent.SelectContent(
contentType = "product",
contentId = "PROD_789"
)
)
// 3. 공유
AdStage.trackEvent(
AdStageEvent.Share(
contentType = "product",
method = "kakao"
)
)
// 4. 좋아요
AdStage.trackEvent(
AdStageEvent.Like(
contentType = "product",
contentId = "PROD_789"
)
)
// 5. 평가
AdStage.trackEvent(
AdStageEvent.Rate(
contentType = "product",
contentId = "PROD_789",
score = 4.5
)
)
// 6. 일정 등록
AdStage.trackEvent(
AdStageEvent.Schedule(
params = mapOf("event_type" to "appointment")
)
)
// 7. 크레딧 사용
AdStage.trackEvent(
AdStageEvent.SpendCredits(
value = 10.0,
itemName = "premium_feature"
)
)📢 인앱 광고 (2개)
// 1. 인앱 광고 노출
AdStage.trackEvent(
AdStageEvent.AdImpression(
adPlatform = "admob",
adFormat = "rewarded_video",
adUnitName = "main_reward"
)
)
// 2. 인앱 광고 클릭
AdStage.trackEvent(
AdStageEvent.AdClick(
adPlatform = "admob",
adFormat = "banner",
adUnitName = "home_bottom"
)
)🎮 게임 특화 (4개)
// 1. 게임 플레이
AdStage.trackEvent(
AdStageEvent.GamePlay(
level = 10,
levelName = "Dragon's Lair",
character = "mage",
contentType = "dungeon"
)
)
// 2. 보너스 획득
AdStage.trackEvent(
AdStageEvent.AcquireBonus(
contentType = "reward",
itemId = "ITEM_123",
itemName = "Gold Chest",
quantity = 1
)
)
// 3. 게임 서버 선택
AdStage.trackEvent(
AdStageEvent.SelectGameServer(
contentId = "SERVER_01",
contentType = "pvp",
itemName = "Asia Server"
)
)
// 4. 패치 완료
AdStage.trackEvent(
AdStageEvent.CompletePatch(
contentId = "PATCH_2.1.0",
contentType = "update"
)
)📅 구독/체험 (3개)
// 1. 무료 체험 시작
AdStage.trackEvent(
AdStageEvent.StartTrial(
value = 9900.0,
currency = "KRW",
trialDays = 14
)
)
// 2. 구독 시작
AdStage.trackEvent(
AdStageEvent.Subscribe(
value = 9900.0,
currency = "KRW",
subscriptionId = "premium_monthly"
)
)
// 3. 구독 취소
AdStage.trackEvent(
AdStageEvent.Unsubscribe(subscriptionId = "premium_monthly")
)💰 금융 특화 (4개)
// 1. 주식 매수
AdStage.trackEvent(
AdStageEvent.BuyStock(
itemId = "005930",
itemName = "삼성전자",
quantity = 10,
price = 70000.0,
value = 700000.0,
currency = "KRW"
)
)
// 2. 주식 매도
AdStage.trackEvent(
AdStageEvent.SellStock(
itemId = "005930",
itemName = "삼성전자",
quantity = 5,
price = 72000.0,
value = 360000.0,
currency = "KRW"
)
)
// 3. 계좌 개설 완료
AdStage.trackEvent(
AdStageEvent.CompleteOpenAccount(
contentType = "증권계좌",
contentId = "ACC_123",
method = "mobile"
)
)
// 4. 카드 신청
AdStage.trackEvent(
AdStageEvent.ApplyCard(
contentType = "신용카드",
itemName = "프리미엄 카드",
itemId = "CARD_001"
)
)이벤트 타입별 예제
1. 앱 라이프사이클
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// SDK 초기화
AdStage.initialize(this, apiKey, serverUrl)
// 첫 실행 이벤트는 SDK가 자동으로 전송
Log.d("App", "✅ 앱 시작")
}
}2. 사용자 인증
class AuthManager {
// 회원가입 (method는 문자열 파라미터)
fun onSignUpComplete(method: String) {
AdStage.trackEvent(
AdStageEvent.SignUp(method = method)
)
Log.d("Auth", "✅ 회원가입 완료: $method")
}
// 로그인
fun onLoginSuccess(userId: String, method: String) {
AdStage.trackEvent(
AdStageEvent.Login(method = method)
)
Log.d("Auth", "✅ 로그인: $userId")
}
// 로그아웃
fun onLogout() {
AdStage.trackEvent(AdStageEvent.Logout())
Log.d("Auth", "🚪 로그아웃")
}
}3. 화면 추적 (BaseActivity 패턴)
abstract class BaseActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
trackScreenView()
}
abstract fun getScreenName(): String
private fun trackScreenView() {
AdStage.trackEvent(
AdStageEvent.ScreenView(
screenName = getScreenName(),
screenClass = this::class.java.simpleName
)
)
Log.d("Screen", "📺 ${getScreenName()}")
}
}
// 사용 예제
class HomeActivity : BaseActivity() {
override fun getScreenName() = "home"
}
class ProductDetailActivity : BaseActivity() {
override fun getScreenName() = "product_detail"
private fun loadProduct(productId: String) {
// 상품 데이터 로딩...
// 상품 상세 조회 이벤트
AdStage.trackEvent(
AdStageEvent.ProductDetailsView(
itemId = productId,
itemName = product.name
)
)
}
}4. 전자상거래 전체 플로우
class EcommerceManager {
// 1. 상품 목록 조회
fun trackProductList(category: String) {
AdStage.trackEvent(
AdStageEvent.ProductListView(itemCategory = category)
)
}
// 2. 검색
fun trackSearch(query: String) {
AdStage.trackEvent(
AdStageEvent.Search(searchTerm = query)
)
}
// 3. 검색 결과 조회
fun trackSearchResults(query: String) {
AdStage.trackEvent(
AdStageEvent.SearchResultView(searchTerm = query)
)
}
// 4. 상품 상세 조회
fun trackProductView(product: Product) {
AdStage.trackEvent(
AdStageEvent.ProductDetailsView(
itemId = product.id,
itemName = product.name
)
)
}
// 5. 장바구니 추가
fun trackAddToCart(product: Product, quantity: Int) {
val item = EcommerceItem(
itemId = product.id,
itemName = product.name,
price = product.price,
quantity = quantity
)
AdStage.trackEvent(
AdStageEvent.AddToCart(
value = product.price * quantity,
currency = "KRW",
items = listOf(item)
)
)
}
// 6. 위시리스트 추가
fun trackAddToWishlist(product: Product) {
AdStage.trackEvent(
AdStageEvent.AddToWishlist(
itemId = product.id,
itemName = product.name
)
)
}
// 7. 결제 시작
fun trackBeginCheckout(cart: Cart) {
val items = cart.items.map { cartItem ->
EcommerceItem(
itemId = cartItem.product.id,
itemName = cartItem.product.name,
price = cartItem.product.price,
quantity = cartItem.quantity
)
}
AdStage.trackEvent(
AdStageEvent.BeginCheckout(
value = cart.totalAmount,
currency = "KRW",
items = items
)
)
}
// 8. 결제 정보 입력
fun trackAddPaymentInfo(paymentMethod: String) {
AdStage.trackEvent(
AdStageEvent.AddPaymentInfo(paymentType = paymentMethod)
)
}
// 9. 구매 완료 ⭐⭐⭐
fun trackPurchase(order: Order) {
val items = order.items.map { orderItem ->
EcommerceItem(
itemId = orderItem.product.id,
itemName = orderItem.product.name,
price = orderItem.product.price,
quantity = orderItem.quantity
)
}
AdStage.trackEvent(
AdStageEvent.Purchase(
value = order.totalAmount,
currency = "KRW",
transactionId = order.id,
tax = order.tax,
shipping = order.shippingFee,
coupon = order.couponCode,
items = items
)
)
Log.d("Ecommerce", "✅ 구매 완료: ${order.id}, ${order.totalAmount}원")
}
// 10. 환불
fun trackRefund(order: Order) {
AdStage.trackEvent(
AdStageEvent.Refund(
transactionId = order.id,
value = order.totalAmount,
currency = "KRW"
)
)
}
// 11. 공유
fun trackShare(product: Product, method: String) {
AdStage.trackEvent(
AdStageEvent.Share(
contentType = "product",
method = method
)
)
}
}5. 게임 이벤트
class GameEventManager {
// 튜토리얼
fun trackTutorialBegin(tutorialId: String) {
AdStage.trackEvent(
AdStageEvent.TutorialBegin(
params = mapOf("tutorial_id" to tutorialId)
)
)
}
fun trackTutorialComplete(tutorialId: String, duration: Long) {
AdStage.trackEvent(
AdStageEvent.TutorialComplete(
params = mapOf(
"tutorial_id" to tutorialId,
"duration_seconds" to duration / 1000
)
)
)
}
// 레벨
fun trackLevelUp(level: Int, character: String) {
AdStage.trackEvent(
AdStageEvent.LevelUp(
level = level,
character = character
)
)
}
// 업적
fun trackAchievement(achievementId: String) {
AdStage.trackEvent(
AdStageEvent.Achievement(achievementId = achievementId)
)
}
// 게임 플레이
fun trackGameStart(level: Int, levelName: String, character: String) {
AdStage.trackEvent(
AdStageEvent.GamePlay(
level = level,
levelName = levelName,
character = character,
contentType = "pvp"
)
)
}
// 보너스 획득
fun trackBonusAcquired(itemId: String, itemName: String, quantity: Int) {
AdStage.trackEvent(
AdStageEvent.AcquireBonus(
contentType = "reward",
itemId = itemId,
itemName = itemName,
quantity = quantity
)
)
}
// 크레딧(재화) 사용
fun trackSpendCredits(amount: Double, itemName: String) {
AdStage.trackEvent(
AdStageEvent.SpendCredits(
value = amount,
itemName = itemName
)
)
}
}베스트 프랙티스
1. Extension 함수 활용
// ActivityExtensions.kt
fun Activity.trackScreen() {
val screenName = this::class.java.simpleName
.removeSuffix("Activity")
.lowercase()
AdStage.trackEvent(
AdStageEvent.ScreenView(
screenName = screenName,
screenClass = this::class.java.simpleName
)
)
}
// 사용
class ProfileActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
trackScreen() // ✅ 간단!
}
}2. ViewModel에서 이벤트 추적
class ProductViewModel : ViewModel() {
private val _productState = MutableLiveData<Product>()
val productState: LiveData<Product> = _productState
fun loadProduct(productId: String) {
viewModelScope.launch {
try {
val product = repository.getProduct(productId)
_productState.value = product
// 상품 조회 이벤트
AdStage.trackEvent(
AdStageEvent.ProductDetailsView(
itemId = product.id,
itemName = product.name
)
)
} catch (e: Exception) {
Log.e("ProductVM", "Failed to load product", e)
}
}
}
fun addToCart(product: Product, quantity: Int) {
viewModelScope.launch {
try {
repository.addToCart(product, quantity)
val item = EcommerceItem(
itemId = product.id,
itemName = product.name,
price = product.price,
quantity = quantity
)
AdStage.trackEvent(
AdStageEvent.AddToCart(
value = product.price * quantity,
currency = "KRW",
items = listOf(item)
)
)
} catch (e: Exception) {
Log.e("ProductVM", "Failed to add to cart", e)
}
}
}
}3. Compose UI에서 사용
@Composable
fun ProductDetailScreen(
productId: String,
viewModel: ProductViewModel = hiltViewModel()
) {
val product by viewModel.productState.collectAsState()
// 화면 진입 시 이벤트
LaunchedEffect(productId) {
viewModel.loadProduct(productId)
AdStage.trackEvent(
AdStageEvent.ScreenView(
screenName = "product_detail",
screenClass = "ProductDetailScreen"
)
)
}
Column {
// UI...
Button(
onClick = {
viewModel.addToCart(product, 1)
AdStage.trackEvent(
AdStageEvent.Custom(
params = mapOf(
"action" to "add_to_cart_button",
"product_id" to product.id
)
)
)
}
) {
Text("장바구니에 추가")
}
}
}4. 이벤트 래퍼 클래스
object AnalyticsManager {
private const val TAG = "Analytics"
fun trackScreen(screenName: String, screenClass: String? = null) {
AdStage.trackEvent(
AdStageEvent.ScreenView(
screenName = screenName,
screenClass = screenClass
)
)
Log.d(TAG, "📺 Screen: $screenName")
}
fun trackPurchase(order: Order) {
val items = order.items.map {
EcommerceItem(
itemId = it.product.id,
itemName = it.product.name,
price = it.product.price,
quantity = it.quantity
)
}
AdStage.trackEvent(
AdStageEvent.Purchase(
value = order.totalAmount,
currency = "KRW",
transactionId = order.id,
items = items
)
)
Log.d(TAG, "💰 Purchase: ${order.id}")
}
fun trackError(error: Throwable, context: String) {
AdStage.trackEvent(
AdStageEvent.Custom(
params = mapOf(
"event_type" to "error",
"error_message" to (error.message ?: "unknown"),
"context" to context
)
)
)
Log.e(TAG, "❌ Error: $context", error)
}
}5. 성능 최적화: Throttling
class ThrottledEventTracker {
private val lastTrackTimes = mutableMapOf<String, Long>()
private val throttleInterval = 2000L // 2초
fun trackEvent(event: AdStageEvent) {
val eventKey = event.eventName
val now = System.currentTimeMillis()
val lastTime = lastTrackTimes[eventKey] ?: 0L
if (now - lastTime >= throttleInterval) {
AdStage.trackEvent(event)
lastTrackTimes[eventKey] = now
} else {
Log.d("AdStage", "⏱️ Throttled: $eventKey")
}
}
}
// 사용 (스크롤 이벤트 등)
val throttledTracker = ThrottledEventTracker()
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
throttledTracker.trackEvent(
AdStageEvent.Custom(
params = mapOf(
"action" to "scroll",
"delta_y" to dy
)
)
)
}
})변환 예제 (문자열 기반 → 타입 안전 API)
현재 SDK는 AdStageEvent 타입 안전 이벤트만 지원합니다. 아래는 문자열 기반
이벤트명을 타입 안전 이벤트로 변환하는 예시입니다(화살표 오른쪽이 실제 사용 형태).
// 1. 기본 이벤트
"first_open"
→ AdStage.trackEvent(AdStageEvent.FirstOpen())
// 2. 로그인 (method는 문자열 파라미터)
"login", method = "email"
→ AdStage.trackEvent(AdStageEvent.Login(method = "email"))
// 3. 화면 조회
"screen_view", screen_name = "home"
→ AdStage.trackEvent(AdStageEvent.ScreenView(screenName = "home"))
// 4. 커스텀
"custom", action = "click"
→ AdStage.trackEvent(AdStageEvent.Custom(params = mapOf("action" to "click")))트러블슈팅
1. 런타임 예외: "value가 있을 경우 currency는 필수입니다"
문제:
// ❌ IllegalArgumentException (이벤트 객체 생성 시점)
AdStage.trackEvent(
AdStageEvent.Purchase(value = 9900.0)
)해결:
// ✅ currency 추가 (ISO 4217, 3자리)
AdStage.trackEvent(
AdStageEvent.Purchase(
value = 9900.0,
currency = "KRW"
)
)2. IDE 자동완성 작동 안 함
해결:
- Gradle Sync:
File → Sync Project with Gradle Files - Invalidate Caches:
File → Invalidate Caches / Restart... - Import 확인:
import io.nbase.adapter.adstage.models.AdStageEvent.*
// 이제 자동완성 작동
AdStage.trackEvent(Purchase(...))3. ProGuard/R8 난독화 문제
# proguard-rules.pro
# AdStage 이벤트 모델 보존
-keep class io.nbase.adapter.adstage.models.** { *; }
-keepclassmembers class io.nbase.adapter.adstage.models.** { *; }
# AdStageEvent sealed class
-keep class io.nbase.adapter.adstage.models.AdStageEvent { *; }
-keep class io.nbase.adapter.adstage.models.AdStageEvent$* { *; }
# Kotlin Metadata
-keep class kotlin.Metadata { *; }
4. 통화 코드 검증 에러
// ❌ 2자리 코드
AdStageEvent.Purchase(value = 100.0, currency = "KR")
// IllegalArgumentException: "currency는 ISO 4217 3자리 코드여야 합니다 (예: USD, KRW)"
// ✅ 3자리 ISO 4217 코드
AdStageEvent.Purchase(value = 100.0, currency = "KRW") // 한국 원
AdStageEvent.Purchase(value = 100.0, currency = "USD") // 미국 달러
AdStageEvent.Purchase(value = 100.0, currency = "JPY") // 일본 엔주요 통화 코드:
| 국가 | 통화 | 코드 |
|---|---|---|
| 한국 | 원 | KRW |
| 미국 | 달러 | USD |
| 일본 | 엔 | JPY |
| 유럽연합 | 유로 | EUR |
| 영국 | 파운드 | GBP |
5. 디버그 로그 확인
SDK는 AdStage / AdapterAdStage 태그로 로그를 출력합니다. 아래 필터로 Logcat에서
이벤트 전송 흐름을 확인할 수 있습니다.
Logcat 필터:
# Android Studio
tag:AdStage OR tag:AdapterAdStage
# adb
adb logcat | grep -i adstage참고 자료
표준 참조
FAQ
Q: 커스텀 이벤트는 어떻게 전송하나요?
A: AdStageEvent.Custom(params = mapOf(...)) 사용
Q: value 없이 currency만 사용할 수 있나요?
A: 아니요, value 사용 시 currency는 필수입니다.
Q: 오프라인에서도 작동하나요?
A: 예, SDK가 자동으로 이벤트를 큐에 저장하고 네트워크 복구 시 전송합니다.
지원
연락처:
- 이메일: support@nbase.io
© 2025 NBase. All rights reserved.

