모바일 SDK인앱 이벤트
Android
AdStage 인앱 이벤트 통합 가이드 (Android)
목차
개요
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 { *; }

