StageUp
모바일 SDK인앱 이벤트

Android

AdStage 인앱 이벤트 통합 가이드 (Android)

목차

  1. 개요
  2. 기본 설정
  3. 기본 이벤트 전송
  4. 고급 이벤트 전송
  5. 사용자 및 디바이스 정보
  6. 이벤트 타입별 예제
  7. 베스트 프랙티스
  8. 트러블슈팅

개요

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.5")
    
    // 필수 의존성
    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.AdStageManager
 
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // AdStage 초기화
        AdStageManager.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. 가장 간단한 방식

import io.nbase.adapter.adstage.AdStageManager
import io.nbase.adapter.adstage.models.AdStageEvent
 
// 파라미터 없는 이벤트
AdStageManager.trackEvent(AdStageEvent.FirstOpen())
 
// 로그인 이벤트
AdStageManager.trackEvent(
    AdStageEvent.Login(method = SignUpMethod.EMAIL)
)

2. 구매 이벤트 (검증 예제)

// ✅ GOOD: value와 currency 함께 제공
AdStageManager.trackEvent(
    AdStageEvent.Purchase(
        value = 29.99,
        currency = "USD",
        transactionId = "TXN_123456"
    )
)
 
// ❌ BAD: value만 제공 시 컴파일 에러!
AdStageManager.trackEvent(
    AdStageEvent.Purchase(
        value = 29.99  // ❌ 에러: currency 필수!
    )
)
 
// ✅ GOOD: currency 없이 transactionId만
AdStageManager.trackEvent(
    AdStageEvent.Purchase(
        transactionId = "TXN_123456"
    )
)

3. 상품 조회

// 파라미터와 함께
AdStageManager.trackEvent(
    AdStageEvent.ProductDetailsView(
        itemId = "PROD_123",
        itemName = "Wireless Earbuds"
    )
)
 
// 파라미터 없이
AdStageManager.trackEvent(AdStageEvent.ProductListView())

4. 커스텀 이벤트 (event_name 자동 승격)

표준 이벤트 외에 앱 고유의 이벤트를 전송할 때는 AdStageEvent.Custom을 사용합니다. 파라미터에 event_name 키를 포함하면, 해당 값이 실제 이벤트 이름으로 자동 승격되어 대시보드에 저장됩니다.

// 예: 'promotion_click'이라는 커스텀 이벤트 전송
AdStageManager.trackEvent(
    AdStageEvent.Custom(
        params = mapOf(
            "event_name" to "promotion_click",
            "promotion_id" to "summer_sale_2025",
            "screen" to "home_banner"
        )
    )
)
// 대시보드에는 "promotion_click" 이벤트로 수집됨
 
// event_name 없이 일반 커스텀 이벤트
AdStageManager.trackEvent(
    AdStageEvent.Custom(
        params = mapOf(
            "action" to "button_click",
            "button_id" to "promo_banner",
            "screen" to "home",
            "timestamp" to System.currentTimeMillis()
        )
    )
)
// 대시보드에는 "custom" 이벤트로 수집됨
 
// Click, View도 자유 파라미터 지원
AdStageManager.trackEvent(
    AdStageEvent.Click(
        params = mapOf(
            "campaign_id" to "SUMMER2025",
            "ad_group" to "electronics"
        )
    )
)

표준 이벤트 카탈로그

AdStage SDK는 46개의 표준 이벤트를 제공합니다.

📌 광고 추적 (4개)

// 1. 광고 클릭
AdStageManager.trackEvent(
    AdStageEvent.Click(
        params = mapOf("campaign_id" to "CAMP_123")
    )
)
 
// 2. 광고 노출
AdStageManager.trackEvent(
    AdStageEvent.View(
        params = mapOf("impression_id" to "IMP_456")
    )
)
 
// 3. 앱 설치
AdStageManager.trackEvent(AdStageEvent.Install())
 
// 4. 커스텀 이벤트 (event_name으로 이벤트 이름 지정)
AdStageManager.trackEvent(
    AdStageEvent.Custom(
        params = mapOf(
            "event_name" to "promotion_click",
            "promotion_id" to "summer_sale_2025"
        )
    )
)

👤 사용자 라이프사이클 (5개)

// SignUpMethod enum
enum class SignUpMethod(val value: String) {
    EMAIL("email"),
    GOOGLE("google"),
    APPLE("apple"),
    FACEBOOK("facebook"),
    KAKAO("kakao"),
    NAVER("naver")
}
 
// 1. 회원가입 완료
AdStageManager.trackEvent(
    AdStageEvent.SignUp(method = SignUpMethod.GOOGLE)
)
 
// 2. 회원가입 시작
AdStageManager.trackEvent(AdStageEvent.SignUpStart())
 
// 3. 로그인
AdStageManager.trackEvent(
    AdStageEvent.Login(method = SignUpMethod.EMAIL)
)
 
// 4. 로그아웃
AdStageManager.trackEvent(AdStageEvent.Logout())
 
// 5. 앱 최초 실행
AdStageManager.trackEvent(AdStageEvent.FirstOpen())

📄 콘텐츠 조회 (6개)

// 1. 홈 화면
AdStageManager.trackEvent(AdStageEvent.HomeView())
 
// 2. 상품 목록
AdStageManager.trackEvent(
    AdStageEvent.ProductListView(itemCategory = "electronics")
)
 
// 3. 검색 결과
AdStageManager.trackEvent(
    AdStageEvent.SearchResultView(searchTerm = "wireless headphones")
)
 
// 4. 상품 상세
AdStageManager.trackEvent(
    AdStageEvent.ProductDetailsView(
        itemId = "PROD_123",
        itemName = "Wireless Earbuds"
    )
)
 
// 5. 페이지 조회 (웹)
AdStageManager.trackEvent(
    AdStageEvent.PageView(
        pageUrl = "https://example.com/products",
        pageTitle = "Products"
    )
)
 
// 6. 화면 조회 (앱)
AdStageManager.trackEvent(
    AdStageEvent.ScreenView(
        screenName = "product_detail",
        screenClass = "ProductDetailActivity"
    )
)

🛒 전자상거래 (8개)

// 1. 장바구니 추가
AdStageManager.trackEvent(
    AdStageEvent.AddToCart(
        value = 99000.0,
        currency = "KRW",
        items = listOf(
            EcommerceItem(
                itemId = "PROD_123",
                itemName = "Wireless Earbuds",
                price = 99000.0,
                quantity = 1
            )
        )
    )
)
 
// 2. 장바구니 제거
AdStageManager.trackEvent(
    AdStageEvent.RemoveFromCart(
        value = 50000.0,
        currency = "KRW"
    )
)
 
// 3. 위시리스트 추가
AdStageManager.trackEvent(
    AdStageEvent.AddToWishlist(
        itemId = "PROD_456",
        itemName = "Smart Watch"
    )
)
 
// 4. 결제 정보 입력
AdStageManager.trackEvent(
    AdStageEvent.AddPaymentInfo(paymentType = "credit_card")
)
 
// 5. 결제 시작
AdStageManager.trackEvent(
    AdStageEvent.BeginCheckout(
        value = 150000.0,
        currency = "KRW"
    )
)
 
// 6. 구매 완료 ⭐⭐⭐
AdStageManager.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",
                itemCategory = "electronics",
                price = 126000.0,
                quantity = 1
            )
        )
    )
)
 
// 7. 환불
AdStageManager.trackEvent(
    AdStageEvent.Refund(
        transactionId = "ORDER_20250105_001",
        value = 129000.0,
        currency = "KRW"
    )
)

🎮 진행/성취 (4개)

// 1. 튜토리얼 시작
AdStageManager.trackEvent(
    AdStageEvent.TutorialBegin(
        params = mapOf("tutorial_id" to "intro")
    )
)
 
// 2. 튜토리얼 완료
AdStageManager.trackEvent(
    AdStageEvent.TutorialComplete(
        params = mapOf("duration_seconds" to 120)
    )
)
 
// 3. 레벨 업
AdStageManager.trackEvent(
    AdStageEvent.LevelUp(
        level = 25,
        character = "warrior"
    )
)
 
// 4. 업적 달성
AdStageManager.trackEvent(
    AdStageEvent.Achievement(achievementId = "first_win")
)

💬 상호작용 (3개)

// 1. 검색
AdStageManager.trackEvent(
    AdStageEvent.Search(searchTerm = "gaming laptop")
)
 
// 2. 공유
AdStageManager.trackEvent(
    AdStageEvent.Share(
        contentType = "product",
        itemId = "PROD_789",
        method = "kakao"
    )
)
 
// 3. 광고 클릭
AdStageManager.trackEvent(
    AdStageEvent.AdClick(adId = "AD_12345")
)

🎮 게임 특화 (4개)

// 1. 게임 플레이
AdStageManager.trackEvent(
    AdStageEvent.GamePlay(
        level = 10,
        levelName = "Dragon's Lair",
        character = "mage",
        contentType = "dungeon"
    )
)
 
// 2. 보너스 획득
AdStageManager.trackEvent(
    AdStageEvent.AcquireBonus(
        contentType = "reward",
        itemId = "ITEM_123",
        itemName = "Gold Chest",
        quantity = 1
    )
)
 
// 3. 게임 서버 선택
AdStageManager.trackEvent(
    AdStageEvent.SelectGameServer(
        contentId = "SERVER_01",
        contentType = "pvp",
        itemName = "Asia Server"
    )
)
 
// 4. 패치 완료
AdStageManager.trackEvent(
    AdStageEvent.CompletePatch(
        contentId = "PATCH_2.1.0",
        contentType = "update"
    )
)

📅 구독/체험 (3개)

// 1. 무료 체험 시작
AdStageManager.trackEvent(
    AdStageEvent.StartTrial(
        value = 9900.0,
        currency = "KRW",
        trialDays = 14
    )
)
 
// 2. 구독 시작
AdStageManager.trackEvent(
    AdStageEvent.Subscribe(
        value = 9900.0,
        currency = "KRW",
        subscriptionId = "premium_monthly"
    )
)
 
// 3. 구독 취소
AdStageManager.trackEvent(
    AdStageEvent.Unsubscribe(subscriptionId = "premium_monthly")
)

💰 가상 화폐 (2개)

// 1. 가상 화폐 획득
AdStageManager.trackEvent(
    AdStageEvent.EarnVirtualCurrency(
        virtualCurrencyName = "gold",
        value = 500.0
    )
)
 
// 2. 가상 화폐 사용
AdStageManager.trackEvent(
    AdStageEvent.SpendVirtualCurrency(
        virtualCurrencyName = "gold",
        value = 100.0,
        itemName = "health_potion"
    )
)

🎯 기타 (7개)

// 1. 일정 등록
AdStageManager.trackEvent(
    AdStageEvent.Schedule(
        params = mapOf("event_type" to "appointment")
    )
)
 
// 2. 크레딧 사용
AdStageManager.trackEvent(
    AdStageEvent.SpendCredits(
        value = 10.0,
        itemName = "premium_feature"
    )
)
 
// 3. 프로모션 조회
AdStageManager.trackEvent(
    AdStageEvent.ViewPromotion(
        promotionId = "PROMO_SUMMER",
        promotionName = "Summer Sale 2025",
        creativeSlot = "home_banner_1"
    )
)
 
// 4. 프로모션 선택
AdStageManager.trackEvent(
    AdStageEvent.SelectPromotion(
        promotionId = "PROMO_SUMMER",
        promotionName = "Summer Sale 2025"
    )
)

이벤트 타입별 예제

1. 앱 라이프사이클

class MyApplication : Application() {
    
    override fun onCreate() {
        super.onCreate()
        
        // SDK 초기화
        AdStageManager.initialize(this, apiKey, serverUrl)
        
        // 첫 실행 이벤트는 SDK가 자동으로 전송
        Log.d("App", "✅ 앱 시작")
    }
}

2. 사용자 인증

class AuthManager {
    
    // 회원가입
    fun onSignUpComplete(method: String) {
        val signUpMethod = when(method) {
            "email" -> SignUpMethod.EMAIL
            "google" -> SignUpMethod.GOOGLE
            "apple" -> SignUpMethod.APPLE
            "kakao" -> SignUpMethod.KAKAO
            "naver" -> SignUpMethod.NAVER
            else -> SignUpMethod.EMAIL
        }
        
        AdStageManager.trackEvent(
            AdStageEvent.SignUp(method = signUpMethod)
        )
        
        Log.d("Auth", "✅ 회원가입 완료: $method")
    }
    
    // 로그인
    fun onLoginSuccess(userId: String, method: String) {
        val loginMethod = when(method) {
            "email" -> SignUpMethod.EMAIL
            "google" -> SignUpMethod.GOOGLE
            else -> SignUpMethod.EMAIL
        }
        
        AdStageManager.trackEvent(
            AdStageEvent.Login(method = loginMethod)
        )
        
        Log.d("Auth", "✅ 로그인: $userId")
    }
    
    // 로그아웃
    fun onLogout() {
        AdStageManager.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() {
        AdStageManager.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) {
        // 상품 데이터 로딩...
        
        // 상품 상세 조회 이벤트
        AdStageManager.trackEvent(
            AdStageEvent.ProductDetailsView(
                itemId = productId,
                itemName = product.name
            )
        )
    }
}

4. 전자상거래 전체 플로우

class EcommerceManager {
    
    // 1. 상품 목록 조회
    fun trackProductList(category: String) {
        AdStageManager.trackEvent(
            AdStageEvent.ProductListView(itemCategory = category)
        )
    }
    
    // 2. 검색
    fun trackSearch(query: String) {
        AdStageManager.trackEvent(
            AdStageEvent.Search(searchTerm = query)
        )
    }
    
    // 3. 검색 결과 조회
    fun trackSearchResults(query: String) {
        AdStageManager.trackEvent(
            AdStageEvent.SearchResultView(searchTerm = query)
        )
    }
    
    // 4. 상품 상세 조회
    fun trackProductView(product: Product) {
        AdStageManager.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,
            itemCategory = product.category,
            price = product.price,
            quantity = quantity
        )
        
        AdStageManager.trackEvent(
            AdStageEvent.AddToCart(
                value = product.price * quantity,
                currency = "KRW",
                items = listOf(item)
            )
        )
    }
    
    // 6. 위시리스트 추가
    fun trackAddToWishlist(product: Product) {
        AdStageManager.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,
                itemCategory = cartItem.product.category,
                price = cartItem.product.price,
                quantity = cartItem.quantity
            )
        }
        
        AdStageManager.trackEvent(
            AdStageEvent.BeginCheckout(
                value = cart.totalAmount,
                currency = "KRW",
                items = items
            )
        )
    }
    
    // 8. 결제 정보 입력
    fun trackAddPaymentInfo(paymentMethod: String) {
        AdStageManager.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,
                itemCategory = orderItem.product.category,
                price = orderItem.product.price,
                quantity = orderItem.quantity
            )
        }
        
        AdStageManager.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) {
        AdStageManager.trackEvent(
            AdStageEvent.Refund(
                transactionId = order.id,
                value = order.totalAmount,
                currency = "KRW"
            )
        )
    }
    
    // 11. 공유
    fun trackShare(product: Product, method: String) {
        AdStageManager.trackEvent(
            AdStageEvent.Share(
                contentType = "product",
                itemId = product.id,
                method = method
            )
        )
    }
}

5. 게임 이벤트

class GameEventManager {
    
    // 튜토리얼
    fun trackTutorialBegin(tutorialId: String) {
        AdStageManager.trackEvent(
            AdStageEvent.TutorialBegin(
                params = mapOf("tutorial_id" to tutorialId)
            )
        )
    }
    
    fun trackTutorialComplete(tutorialId: String, duration: Long) {
        AdStageManager.trackEvent(
            AdStageEvent.TutorialComplete(
                params = mapOf(
                    "tutorial_id" to tutorialId,
                    "duration_seconds" to duration / 1000
                )
            )
        )
    }
    
    // 레벨
    fun trackLevelUp(level: Int, character: String) {
        AdStageManager.trackEvent(
            AdStageEvent.LevelUp(
                level = level,
                character = character
            )
        )
    }
    
    // 업적
    fun trackAchievement(achievementId: String) {
        AdStageManager.trackEvent(
            AdStageEvent.Achievement(achievementId = achievementId)
        )
    }
    
    // 게임 플레이
    fun trackGameStart(level: Int, levelName: String, character: String) {
        AdStageManager.trackEvent(
            AdStageEvent.GamePlay(
                level = level,
                levelName = levelName,
                character = character,
                contentType = "pvp"
            )
        )
    }
    
    // 보너스 획득
    fun trackBonusAcquired(itemId: String, itemName: String, quantity: Int) {
        AdStageManager.trackEvent(
            AdStageEvent.AcquireBonus(
                contentType = "reward",
                itemId = itemId,
                itemName = itemName,
                quantity = quantity
            )
        )
    }
    
    // 가상 화폐
    fun trackEarnCurrency(currencyName: String, amount: Double) {
        AdStageManager.trackEvent(
            AdStageEvent.EarnVirtualCurrency(
                virtualCurrencyName = currencyName,
                value = amount
            )
        )
    }
    
    fun trackSpendCurrency(currencyName: String, amount: Double, itemName: String) {
        AdStageManager.trackEvent(
            AdStageEvent.SpendVirtualCurrency(
                virtualCurrencyName = currencyName,
                value = amount,
                itemName = itemName
            )
        )
    }
}

베스트 프랙티스

1. Extension 함수 활용

// ActivityExtensions.kt
fun Activity.trackScreen() {
    val screenName = this::class.java.simpleName
        .removeSuffix("Activity")
        .lowercase()
    
    AdStageManager.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
                
                // 상품 조회 이벤트
                AdStageManager.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
                )
                
                AdStageManager.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)
        
        AdStageManager.trackEvent(
            AdStageEvent.ScreenView(
                screenName = "product_detail",
                screenClass = "ProductDetailScreen"
            )
        )
    }
    
    Column {
        // UI...
        
        Button(
            onClick = {
                viewModel.addToCart(product, 1)
                
                AdStageManager.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) {
        AdStageManager.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
            )
        }
        
        AdStageManager.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) {
        AdStageManager.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) {
            AdStageManager.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
                )
            )
        )
    }
})

변환 예제

// 1. 기본 이벤트
AdStage.trackEvent("first_open")
→ AdStageManager.trackEvent(AdStageEvent.FirstOpen())
 
// 2. 로그인
AdStage.trackEvent("login", mapOf("method" to "email"))
→ AdStageManager.trackEvent(AdStageEvent.Login(method = SignUpMethod.EMAIL))
 
// 3. 화면 조회
AdStage.trackEvent("screen_view", mapOf("screen_name" to "home"))
→ AdStageManager.trackEvent(AdStageEvent.ScreenView(screenName = "home"))
 
// 4. 커스텀
AdStage.trackEvent("custom", mapOf("action" to "click"))
→ AdStageManager.trackEvent(AdStageEvent.Custom(params = mapOf("action" to "click")))

트러블슈팅

1. 컴파일 에러: "currency 파라미터 필수"

문제:

// ❌ 에러
AdStageManager.trackEvent(
    AdStageEvent.Purchase(value = 9900.0)
)

해결:

// ✅ currency 추가 (ISO 4217, 3자리)
AdStageManager.trackEvent(
    AdStageEvent.Purchase(
        value = 9900.0,
        currency = "KRW"
    )
)

2. IDE 자동완성 작동 안 함

해결:

  1. Gradle Sync: File → Sync Project with Gradle Files
  2. Invalidate Caches: File → Invalidate Caches / Restart...
  3. Import 확인:
import io.nbase.adapter.adstage.models.AdStageEvent.*
 
// 이제 자동완성 작동
AdStageManager.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")
// ValidationException: "Currency must be 3-letter ISO 4217 code"
 
// ✅ 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. 디버그 로그 확인

// Application.onCreate()
if (BuildConfig.DEBUG) {
    AdStageManager.setDebugMode(true)
}

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가 자동으로 이벤트를 큐에 저장하고 네트워크 복구 시 전송합니다.


지원

연락처:


© 2025 NBase. All rights reserved.

목차