StageUp
모바일 SDK인앱 이벤트

Android

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

목차

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

개요

AdStage 인앱 이벤트는 사용자 행동과 앱 이벤트를 추적하여 마케팅 분석 및 최적화를 지원합니다.

주요 기능

  • 유연한 이벤트 전송: 간단한 호출부터 상세한 컨텍스트까지
  • 자동 컨텍스트 수집: 디바이스 정보, 사용자 속성 자동 포함
  • 세션 관리: 자동 세션 추적 및 관리
  • Builder 패턴: 가독성 높은 DSL 스타일 지원
  • 비동기 처리: Coroutine 기반 suspend 함수
  • 오프라인 지원: 네트워크 재연결 시 자동 재전송

기본 설정

1. 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"
        )
    }
}

2. 사용자 ID 설정 (권장)

// 로그인 시
AdStage.setUserId("user_123456")
 
// 로그아웃 시
AdStage.setUserId(null)
 
// 현재 사용자 ID 조회
val userId = AdStage.getUserId()
Log.d(TAG, "Current User: $userId")

3. 세션 관리

// 세션 자동 관리 (SDK가 자동으로 처리)
// - 앱 시작 시 자동 세션 생성
// - 백그라운드 진입 후 일정 시간 경과 시 새 세션 생성
 
// 수동 세션 시작
AdStage.startNewSession()
 
// 현재 세션 ID 조회
val sessionId = AdStage.getSessionId()
Log.d(TAG, "Session ID: $sessionId")

기본 이벤트 전송

1. 가장 간단한 방식 (이벤트 이름만)

import io.nbase.adapter.adstage.AdStage
import io.nbase.adapter.adstage.models.TrackEventResult
 
// 콜백 방식
AdStage.trackEvent("APP_OPEN") { result ->
    when (result) {
        is TrackEventResult.Success -> {
            Log.d(TAG, "✅ 이벤트 전송 성공")
        }
        is TrackEventResult.Failure -> {
            Log.e(TAG, "❌ 이벤트 전송 실패: ${result.error}")
        }
    }
}

2. 파라미터 포함

val params = mapOf(
    "screen" to "home",
    "action" to "button_click",
    "button_id" to "promo_banner"
)
 
AdStage.trackEvent("USER_ACTION", params) { result ->
    when (result) {
        is TrackEventResult.Success -> {
            Log.d(TAG, "이벤트 전송 완료")
        }
        is TrackEventResult.Failure -> {
            Log.e(TAG, "전송 실패: ${result.error}")
        }
    }
}

3. suspend 함수 사용 (Coroutine)

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
 
class EventTracker {
    fun trackPageView(pageName: String) {
        CoroutineScope(Dispatchers.Main).launch {
            try {
                val params = TrackEventParams.builder("PAGE_VIEW")
                    .param("page", pageName)
                    .param("timestamp", System.currentTimeMillis())
                    .build()
                
                val response = AdStage.trackEvent(params)
                
                Log.d(TAG, """
                    ✅ 이벤트 전송 성공
                    - Event ID: ${response.id}
                    - Timestamp: ${response.timestamp}
                """.trimIndent())
                
            } catch (e: Exception) {
                Log.e(TAG, "❌ 에러: ${e.message}")
            }
        }
    }
}

고급 이벤트 전송

1. Builder 패턴으로 상세 정보 포함

import io.nbase.adapter.adstage.models.TrackEventParams
import io.nbase.adapter.adstage.models.DeviceInfo
import io.nbase.adapter.adstage.models.UserAttributes
 
fun trackPurchaseEvent(orderId: String, amount: Int) {
    CoroutineScope(Dispatchers.Main).launch {
        try {
            val params = TrackEventParams.builder("PURCHASE")
                // 사용자 정보
                .userId("user_123456")
                .sessionId(AdStage.getSessionId() ?: "")
                
                // 디바이스 정보
                .device {
                    category("mobile")
                    platform("Android")
                    model(Build.MODEL)
                    appVersion(BuildConfig.VERSION_NAME)
                    osVersion(Build.VERSION.RELEASE)
                }
                
                // 사용자 속성
                .user {
                    gender("male")
                    country("KR")
                    city("Seoul")
                    age("28")
                    language("ko-KR")
                }
                
                // 이벤트별 파라미터
                .param("order_id", orderId)
                .param("amount", amount)
                .param("currency", "KRW")
                .param("payment_method", "card")
                
                .build()
            
            val response = AdStage.trackEvent(params)
            
            Log.d(TAG, "구매 이벤트 전송 완료: ${response.id}")
            
        } catch (e: Exception) {
            Log.e(TAG, "구매 이벤트 실패: ${e.message}")
        }
    }
}

2. 전역 설정 활용

// Application에서 한 번만 설정
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        AdStage.initialize(this, apiKey)
        
        // 전역 디바이스 정보 설정
        val deviceInfo = DeviceInfo(
            category = "mobile",
            platform = "Android",
            model = Build.MODEL,
            appVersion = BuildConfig.VERSION_NAME,
            osVersion = Build.VERSION.RELEASE
        )
        AdStage.setDeviceInfo(deviceInfo)
        
        // 전역 사용자 속성 설정 (로그인 후)
        setupGlobalUserAttributes()
    }
    
    private fun setupGlobalUserAttributes() {
        // 사용자 로그인 시 호출
        val userAttributes = UserAttributes(
            gender = "male",
            country = "KR",
            city = "Seoul",
            age = "28",
            language = "ko-KR"
        )
        AdStage.setUserAttributes(userAttributes)
    }
}
 
// 이후 간단하게 호출
fun trackSimpleEvent() {
    // 전역 설정이 자동으로 포함됨
    AdStage.trackEvent("BUTTON_CLICK", mapOf(
        "button_id" to "share_button"
    )) { result ->
        // 처리
    }
}

3. 여러 파라미터 추가

val params = TrackEventParams.builder("USER_INTERACTION")
    .param("screen", "product_detail")
    .param("product_id", "PROD_12345")
    .param("category", "electronics")
    .param("price", 299000)
    .param("in_stock", true)
    
    // Map으로 여러 파라미터 한번에 추가
    .params(mapOf(
        "referrer" to "search",
        "search_term" to "wireless headphones",
        "position" to 3
    ))
    
    .build()
 
CoroutineScope(Dispatchers.Main).launch {
    val response = AdStage.trackEvent(params)
    Log.d(TAG, "이벤트 ID: ${response.id}")
}

사용자 및 디바이스 정보

1. DeviceInfo 구성

import android.os.Build
import io.nbase.adapter.adstage.models.DeviceInfo
 
// 자동 수집
val deviceInfo = DeviceInfo(
    category = "mobile",  // mobile, tablet, desktop, other
    platform = "Android",
    model = Build.MODEL,  // "SM-G991N", "Pixel 7"
    appVersion = BuildConfig.VERSION_NAME,  // "2.1.0"
    osVersion = Build.VERSION.RELEASE  // "13", "14"
)
 
// 전역 설정
AdStage.setDeviceInfo(deviceInfo)
 
// 또는 이벤트별 설정
val params = TrackEventParams.builder("APP_OPEN")
    .device(deviceInfo)
    .build()

2. UserAttributes 구성

import io.nbase.adapter.adstage.models.UserAttributes
 
// 사용자 속성 설정
val userAttributes = UserAttributes(
    gender = "male",      // male, female, other, unknown
    country = "KR",       // ISO 3166-1 alpha-2
    city = "Seoul",
    age = "28",
    language = "ko-KR"    // BCP 47
)
 
// 전역 설정
AdStage.setUserAttributes(userAttributes)
 
// 또는 Builder로 구성
val params = TrackEventParams.builder("SIGNUP_COMPLETE")
    .user {
        gender("female")
        country("US")
        city("New York")
        age("25")
        language("en-US")
    }
    .build()

3. 사용자 프로퍼티 업데이트

// 로그인 시
fun onUserLogin(userId: String, userProfile: UserProfile) {
    AdStage.setUserId(userId)
    
    val userAttributes = UserAttributes(
        gender = userProfile.gender,
        country = userProfile.country,
        city = userProfile.city,
        age = userProfile.age.toString(),
        language = Locale.getDefault().toLanguageTag()
    )
    AdStage.setUserAttributes(userAttributes)
    
    // 로그인 이벤트 전송
    AdStage.trackEvent("USER_LOGIN", mapOf(
        "login_method" to "email"
    )) { result ->
        // 처리
    }
}
 
// 로그아웃 시
fun onUserLogout() {
    // 로그아웃 이벤트 먼저 전송
    AdStage.trackEvent("USER_LOGOUT") { result ->
        // 이벤트 전송 후 사용자 정보 초기화
        AdStage.setUserId(null)
        AdStage.setUserAttributes(null)
    }
}

이벤트 타입별 예제

1. 앱 라이프사이클 이벤트

class MyApplication : Application(), LifecycleObserver {
    
    override fun onCreate() {
        super.onCreate()
        
        ProcessLifecycleOwner.get().lifecycle.addObserver(this)
        
        // 앱 시작
        trackAppOpen()
    }
    
    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onAppForeground() {
        AdStage.trackEvent("APP_FOREGROUND") { result ->
            Log.d(TAG, "앱 포그라운드 이벤트 전송")
        }
    }
    
    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onAppBackground() {
        AdStage.trackEvent("APP_BACKGROUND") { result ->
            Log.d(TAG, "앱 백그라운드 이벤트 전송")
        }
    }
    
    private fun trackAppOpen() {
        AdStage.trackEvent("APP_OPEN", mapOf(
            "version" to BuildConfig.VERSION_NAME,
            "build" to BuildConfig.VERSION_CODE
        )) { result ->
            // 처리
        }
    }
}

2. 화면 조회 이벤트

abstract class BaseActivity : AppCompatActivity() {
    
    override fun onResume() {
        super.onResume()
        
        // 화면 조회 이벤트
        trackScreenView(getScreenName())
    }
    
    abstract fun getScreenName(): String
    
    private fun trackScreenView(screenName: String) {
        val params = mapOf(
            "screen_name" to screenName,
            "screen_class" to this::class.java.simpleName,
            "timestamp" to System.currentTimeMillis()
        )
        
        AdStage.trackEvent("SCREEN_VIEW", params) { result ->
            when (result) {
                is TrackEventResult.Success -> {
                    Log.d(TAG, "Screen view tracked: $screenName")
                }
                is TrackEventResult.Failure -> {
                    Log.e(TAG, "Failed to track screen: ${result.error}")
                }
            }
        }
    }
}
 
// 사용 예제
class HomeActivity : BaseActivity() {
    override fun getScreenName() = "home"
}
 
class ProductDetailActivity : BaseActivity() {
    override fun getScreenName() = "product_detail"
}

3. 전자상거래 이벤트

class EcommerceTracker {
    
    // 상품 조회
    fun trackProductView(product: Product) {
        AdStage.trackEvent("VIEW_ITEM", mapOf(
            "item_id" to product.id,
            "item_name" to product.name,
            "item_category" to product.category,
            "price" to product.price,
            "currency" to "KRW"
        )) { result ->
            // 처리
        }
    }
    
    // 장바구니 추가
    fun trackAddToCart(product: Product, quantity: Int) {
        AdStage.trackEvent("ADD_TO_CART", mapOf(
            "item_id" to product.id,
            "item_name" to product.name,
            "quantity" to quantity,
            "price" to product.price,
            "value" to product.price * quantity
        )) { result ->
            // 처리
        }
    }
    
    // 구매 시작
    fun trackBeginCheckout(cartTotal: Int, itemCount: Int) {
        AdStage.trackEvent("BEGIN_CHECKOUT", mapOf(
            "value" to cartTotal,
            "currency" to "KRW",
            "item_count" to itemCount
        )) { result ->
            // 처리
        }
    }
    
    // 구매 완료
    fun trackPurchase(order: Order) {
        CoroutineScope(Dispatchers.Main).launch {
            try {
                val params = TrackEventParams.builder("PURCHASE")
                    .param("transaction_id", order.id)
                    .param("value", order.total)
                    .param("currency", "KRW")
                    .param("tax", order.tax)
                    .param("shipping", order.shipping)
                    .param("coupon", order.couponCode ?: "")
                    .param("item_count", order.items.size)
                    .params(mapOf(
                        "payment_method" to order.paymentMethod,
                        "shipping_method" to order.shippingMethod
                    ))
                    .build()
                
                val response = AdStage.trackEvent(params)
                
                Log.d(TAG, "구매 완료 이벤트 전송: ${response.id}")
                
            } catch (e: Exception) {
                Log.e(TAG, "구매 이벤트 실패: ${e.message}")
            }
        }
    }
    
    // 환불
    fun trackRefund(orderId: String, refundAmount: Int) {
        AdStage.trackEvent("REFUND", mapOf(
            "transaction_id" to orderId,
            "value" to refundAmount,
            "currency" to "KRW"
        )) { result ->
            // 처리
        }
    }
}

4. 사용자 행동 이벤트

class UserActionTracker {
    
    // 검색
    fun trackSearch(query: String, resultCount: Int) {
        AdStage.trackEvent("SEARCH", mapOf(
            "search_term" to query,
            "result_count" to resultCount
        )) { result ->
            // 처리
        }
    }
    
    // 공유
    fun trackShare(contentType: String, contentId: String, method: String) {
        AdStage.trackEvent("SHARE", mapOf(
            "content_type" to contentType,
            "content_id" to contentId,
            "method" to method  // "kakao", "facebook", "instagram"
        )) { result ->
            // 처리
        }
    }
    
    // 좋아요
    fun trackLike(contentType: String, contentId: String) {
        AdStage.trackEvent("LIKE", mapOf(
            "content_type" to contentType,
            "content_id" to contentId
        )) { result ->
            // 처리
        }
    }
    
    // 댓글
    fun trackComment(contentType: String, contentId: String) {
        AdStage.trackEvent("COMMENT", mapOf(
            "content_type" to contentType,
            "content_id" to contentId
        )) { result ->
            // 처리
        }
    }
    
    // 리뷰 작성
    fun trackReview(productId: String, rating: Int, hasText: Boolean) {
        AdStage.trackEvent("WRITE_REVIEW", mapOf(
            "product_id" to productId,
            "rating" to rating,
            "has_text" to hasText
        )) { result ->
            // 처리
        }
    }
}

5. 게임 이벤트

class GameEventTracker {
    
    // 레벨 시작
    fun trackLevelStart(levelNumber: Int, levelName: String) {
        AdStage.trackEvent("LEVEL_START", mapOf(
            "level_number" to levelNumber,
            "level_name" to levelName
        )) { result ->
            // 처리
        }
    }
    
    // 레벨 완료
    fun trackLevelComplete(levelNumber: Int, score: Int, duration: Long) {
        AdStage.trackEvent("LEVEL_COMPLETE", mapOf(
            "level_number" to levelNumber,
            "score" to score,
            "duration_seconds" to duration / 1000,
            "success" to true
        )) { result ->
            // 처리
        }
    }
    
    // 레벨 실패
    fun trackLevelFail(levelNumber: Int, reason: String) {
        AdStage.trackEvent("LEVEL_FAIL", mapOf(
            "level_number" to levelNumber,
            "fail_reason" to reason,
            "success" to false
        )) { result ->
            // 처리
        }
    }
    
    // 아이템 획득
    fun trackEarnVirtualCurrency(currencyName: String, amount: Int, source: String) {
        AdStage.trackEvent("EARN_VIRTUAL_CURRENCY", mapOf(
            "virtual_currency_name" to currencyName,
            "value" to amount,
            "source" to source  // "level_reward", "daily_bonus", "purchase"
        )) { result ->
            // 처리
        }
    }
    
    // 아이템 사용
    fun trackSpendVirtualCurrency(currencyName: String, amount: Int, itemName: String) {
        AdStage.trackEvent("SPEND_VIRTUAL_CURRENCY", mapOf(
            "virtual_currency_name" to currencyName,
            "value" to amount,
            "item_name" to itemName
        )) { result ->
            // 처리
        }
    }
    
    // 튜토리얼 완료
    fun trackTutorialComplete(tutorialId: String) {
        AdStage.trackEvent("TUTORIAL_COMPLETE", mapOf(
            "tutorial_id" to tutorialId
        )) { result ->
            // 처리
        }
    }
}

6. 광고 이벤트

class AdEventTracker {
    
    // 광고 노출
    fun trackAdImpression(adType: String, adId: String) {
        AdStage.trackEvent("AD_IMPRESSION", mapOf(
            "ad_type" to adType,  // "banner", "interstitial", "rewarded"
            "ad_id" to adId
        )) { result ->
            // 처리
        }
    }
    
    // 광고 클릭
    fun trackAdClick(adType: String, adId: String) {
        AdStage.trackEvent("AD_CLICK", mapOf(
            "ad_type" to adType,
            "ad_id" to adId
        )) { result ->
            // 처리
        }
    }
    
    // 리워드 광고 완료
    fun trackAdRewardEarned(adType: String, rewardType: String, rewardAmount: Int) {
        AdStage.trackEvent("AD_REWARD_EARNED", mapOf(
            "ad_type" to adType,
            "reward_type" to rewardType,
            "reward_amount" to rewardAmount
        )) { result ->
            // 처리
        }
    }
}

베스트 프랙티스

1. 이벤트 네이밍 규칙

// ✅ GOOD: UPPER_SNAKE_CASE
"USER_LOGIN"
"PURCHASE_COMPLETE"
"LEVEL_START"
 
// ❌ BAD: 일관성 없는 네이밍
"userLogin"
"purchase-complete"
"level.start"

2. 파라미터 네이밍 규칙

// ✅ GOOD: snake_case
mapOf(
    "product_id" to "PROD_123",
    "item_name" to "Wireless Headphones",
    "price_krw" to 299000
)
 
// ❌ BAD: 일관성 없는 네이밍
mapOf(
    "productId" to "PROD_123",
    "ItemName" to "Wireless Headphones",
    "price-krw" to 299000
)

3. 공통 파라미터 재사용

object EventParams {
    // 공통 파라미터
    const val SCREEN_NAME = "screen_name"
    const val ITEM_ID = "item_id"
    const val VALUE = "value"
    const val CURRENCY = "currency"
    
    // 공통 값
    const val CURRENCY_KRW = "KRW"
    const val CURRENCY_USD = "USD"
}
 
// 사용
AdStage.trackEvent("PURCHASE", mapOf(
    EventParams.VALUE to 10000,
    EventParams.CURRENCY to EventParams.CURRENCY_KRW
))

4. Extension 함수 활용

fun Context.trackScreenView(screenName: String) {
    AdStage.trackEvent("SCREEN_VIEW", mapOf(
        "screen_name" to screenName,
        "timestamp" to System.currentTimeMillis()
    )) { result ->
        when (result) {
            is TrackEventResult.Success -> 
                Log.d("TrackEvent", "Screen tracked: $screenName")
            is TrackEventResult.Failure -> 
                Log.e("TrackEvent", "Failed: ${result.error}")
        }
    }
}
 
// 사용
class MainActivity : AppCompatActivity() {
    override fun onResume() {
        super.onResume()
        trackScreenView("home")
    }
}

5. 이벤트 래퍼 클래스

class AnalyticsManager(private val context: Context) {
    
    fun trackEvent(
        eventName: String,
        params: Map<String, Any> = emptyMap(),
        includeTimestamp: Boolean = true
    ) {
        val finalParams = if (includeTimestamp) {
            params + ("timestamp" to System.currentTimeMillis())
        } else {
            params
        }
        
        AdStage.trackEvent(eventName, finalParams) { result ->
            when (result) {
                is TrackEventResult.Success -> {
                    Log.d(TAG, "✅ Event: $eventName")
                }
                is TrackEventResult.Failure -> {
                    Log.e(TAG, "❌ Event: $eventName, Error: ${result.error}")
                    // 실패한 이벤트를 로컬 DB에 저장 후 재시도
                    saveFailedEvent(eventName, finalParams)
                }
            }
        }
    }
    
    private fun saveFailedEvent(eventName: String, params: Map<String, Any>) {
        // 로컬 DB에 저장
        // 네트워크 복구 시 재시도
    }
}

6. 성능 최적화

// ❌ BAD: 너무 자주 호출
fun onScrollChanged(scrollY: Int) {
    AdStage.trackEvent("SCROLL", mapOf("scroll_y" to scrollY))
}
 
// ✅ GOOD: Throttle 적용
private var lastScrollTrackTime = 0L
private val SCROLL_TRACK_INTERVAL = 5000L // 5초
 
fun onScrollChanged(scrollY: Int) {
    val currentTime = System.currentTimeMillis()
    if (currentTime - lastScrollTrackTime > SCROLL_TRACK_INTERVAL) {
        AdStage.trackEvent("SCROLL", mapOf("scroll_y" to scrollY))
        lastScrollTrackTime = currentTime
    }
}

트러블슈팅

1. 이벤트가 전송되지 않음

체크리스트:

  • AdStage.initialize() 호출 확인
  • API Key 올바른지 확인
  • 네트워크 권한 확인
  • 인터넷 연결 상태 확인

디버깅:

AdStage.trackEvent("TEST_EVENT") { result ->
    when (result) {
        is TrackEventResult.Success -> {
            Log.d(TAG, "✅ 성공")
        }
        is TrackEventResult.Failure -> {
            Log.e(TAG, "❌ 실패: ${result.error}")
            Log.e(TAG, "스택 트레이스: ${result.throwable?.stackTraceToString()}")
        }
    }
}

2. 사용자 ID가 포함되지 않음

// 확인 1: userId 설정 여부
val userId = AdStage.getUserId()
Log.d(TAG, "Current User ID: $userId")
 
// 확인 2: 로그인 시 userId 설정
fun onLogin(userId: String) {
    AdStage.setUserId(userId)
    
    // 즉시 확인
    val currentUserId = AdStage.getUserId()
    Log.d(TAG, "User ID set: $currentUserId")
}

3. 디바이스 정보가 누락됨

// 전역 설정 확인
val deviceInfo = DeviceInfo(
    category = "mobile",
    platform = "Android",
    model = Build.MODEL,
    appVersion = BuildConfig.VERSION_NAME,
    osVersion = Build.VERSION.RELEASE
)
AdStage.setDeviceInfo(deviceInfo)
 
// 또는 이벤트별로 명시적 설정
val params = TrackEventParams.builder("APP_OPEN")
    .device(deviceInfo)
    .build()

4. 네트워크 에러 처리

class RetryableEventTracker {
    private val failedEvents = mutableListOf<Pair<String, Map<String, Any>>>()
    
    fun trackEventWithRetry(eventName: String, params: Map<String, Any>) {
        AdStage.trackEvent(eventName, params) { result ->
            when (result) {
                is TrackEventResult.Success -> {
                    Log.d(TAG, "이벤트 전송 성공")
                }
                is TrackEventResult.Failure -> {
                    Log.e(TAG, "이벤트 전송 실패, 재시도 큐에 추가")
                    failedEvents.add(eventName to params)
                    scheduleRetry()
                }
            }
        }
    }
    
    private fun scheduleRetry() {
        // 네트워크 복구 시 재시도
        Handler(Looper.getMainLooper()).postDelayed({
            retryFailedEvents()
        }, 30000) // 30초 후 재시도
    }
    
    private fun retryFailedEvents() {
        val eventsToRetry = failedEvents.toList()
        failedEvents.clear()
        
        eventsToRetry.forEach { (eventName, params) ->
            trackEventWithRetry(eventName, params)
        }
    }
}

5. ProGuard 설정

# AdStage Event Models
-keep class io.nbase.adapter.adstage.models.** { *; }
-keepclassmembers class io.nbase.adapter.adstage.models.** { *; }

# TrackEventParams Builder
-keep class io.nbase.adapter.adstage.models.TrackEventParams$Builder { *; }

참고 자료


목차