Notikit 문서

문서 / SDK / Android (Kotlin)

Android (Kotlin)

이 문서는 notikit-android 의 README 를 그대로 가져옵니다.

Notikit Android SDK — 유저 중심 푸시 디바이스 등록/식별. Kotlin & Java 호환.

알림 탭 자동 보고

Application 에서 한 번 설치하면 콜드 스타트 탭이 자동으로 보고된다. 앱 코드에서 Intent 를 직접 읽을 필요가 없다. 앱이 이미 떠 있는 상태의 탭(warm start)만 한 줄 배선이 필요하다 — 아래 참조.

class MyApp : Application() {
    lateinit var session: NotikitSession

    override fun onCreate() {
        super.onCreate()
        val client = Notikit(baseUrl = "https://push.example.com", apiKey = "nk_xxx")
        // 토큰은 갱신되므로 값이 아니라 콜백으로 넘긴다
        session = NotikitAndroid.install(this, client) { currentFcmToken }
    }
}

warm start (앱이 떠 있을 때의 탭)

ActivityLifecycleCallbacks 에는 onNewIntent 가 없고, singleTop/singleTask Activity 는 알림 Intent 를 onNewIntent 로 받는다. 이때 getIntent() 는 원래 실행 Intent 를 계속 돌려주므로(안드로이드 문서) SDK 가 생명주기만 봐서는 이 탭을 볼 수 없다. 런처 Activity 에 다음을 추가한다.

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    setIntent(intent)
    NotikitAndroid.onNewIntent(intent)
}

install 이 돌려주는 세션으로 로그인/로그아웃을 처리한다. 저장소는 SharedPrefsStorage 가 기본으로 쓰이고, 필요하면 직접 구현해 넘길 수 있다.

session.login(StoredUser("user-1", identityHash), token)   // 로그인
session.logout(token)                                       // 로그아웃(바인딩 해제)

동작 방식: 안드로이드에는 iOS 같은 알림 델리게이트가 없다. 알림을 누르면 런처 Activity 가 FCM data 를 Intent extras 로 달고 실행되므로, Activity 생명주기를 관찰해 extras 에서 발송 id 를 찾는다. 같은 Intent 를 재개 때마다 다시 세지 않도록 처리 표식을 남긴다.

설치 (JitPack)

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven("https://jitpack.io")
    }
}

// build.gradle.kts
dependencies {
    implementation("com.github.mintsoft-opensource:notikit-android:0.2.0")
}

Maven Central(dev.notikit:notikit) 배포는 아직이다.

사용 (Kotlin)

공개 메서드는 전부 suspend 이고 내부에서 Dispatchers.IO 로 옮기므로 메인 스레드에서 불러도 안전하다. 직접 스레드를 옮길 필요가 없다.

import com.google.firebase.messaging.FirebaseMessaging
import dev.notikit.Notikit

val notikit = Notikit(baseUrl = "https://push.example.com", apiKey = "nk_xxx") // 공개키만

lifecycleScope.launch {
    val token = FirebaseMessaging.getInstance().token.await()
    notikit.registerDevice(
        token = token,
        platform = "android",
        userId = "user-123", // 고객 서비스의 유저 id
        identityHash = "<서버계산 HMAC>",
    )
}

토큰 교체

registerDevice 를 새 토큰으로 부르면 행이 하나 더 생겨 같은 사람에게 중복 발송된다. 교체는 전용 메서드를 쓴다 — 서버가 기존 행을 제자리 갱신해 토픽 구독·클릭 이력이 보존되고, 밀린 클릭의 토큰도 함께 갱신된다.

class MyMessagingService : FirebaseMessagingService() {
    override fun onNewToken(token: String) {
        val old = lastKnownToken ?: return
        CoroutineScope(Dispatchers.IO).launch { session.rotateToken(old, token) }
        lastKnownToken = token
    }
}

서버가 교체하지 못하면(모르는 옛 토큰, identity 증명 실패 등) 세션이 새 토큰을 현재 유저로 다시 등록한다. 교체나 재등록이 성공했을 때만 밀린 클릭이 새 토큰으로 옮겨진다.

수신(도달) 보고

reportReceived 를 부르지 않으면 콘솔의 "도달" 수가 항상 0 이다. FCM 접수는 기기가 꺼져 있어도 성공하므로, 단말이 실제로 받았다는 보고가 따로 필요하다. 알림을 받은 순간에 부른다. 같은 발송을 다시 부르면 요청 없이 null 을 돌려준다(재배달에 안전).

class MyMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(message: RemoteMessage) {
        val logId = Notikit.logIdFromPayload(message.data) ?: return
        val token = currentFcmToken ?: return
        CoroutineScope(Dispatchers.IO).launch {
            runCatching { notikit.reportReceived(logId, token) }
        }
    }
}

onMessageReceived 는 data 메시지이거나 앱이 포그라운드일 때 불린다. notification 메시지를 백그라운드에서 받으면 시스템 트레이가 직접 표시해 이 콜백이 불리지 않는다.

사용 (Java)

Java 는 suspend 함수를 부를 수 없어 블로킹 파사드를 제공한다. 이름 그대로 막히므로 반드시 백그라운드 스레드에서 호출한다.

NotikitBlocking notikit = new NotikitBlocking(new Notikit("https://push.example.com", "nk_xxx"));
ExecutorService io = Executors.newSingleThreadExecutor();
io.execute(() -> notikit.registerDevice(token, "android", "user-123", hash));

NotikitAndroid.install 이 돌려주는 세션은 NotikitSessionBlocking 으로 감싸 쓴다.

API

설명
registerDevice(token, platform, userId?, identityHash?, ...) FCM 토큰 등록
identify(userId, identityHash?, attributes?) 유저 식별
subscribe(topic, token) 토픽 구독
unsubscribe(topic, token) 토픽 구독 해지
reportReceived(logId, token) 수신(도달) 보고. 이미 보고한 발송이면 null
Notikit.customDataFromPayload(data) 받은 푸시에서 커스텀 필드(템플릿 필드 포함)만 꺼내기
Notikit.deepLinkFromPayload(data) 받은 푸시의 딥링크
  • 유저 id 는 userId(요청 본문 user_id)로 넘긴다. 이전 이름 externalId(external_id)도 계속 동작하지만 deprecated 다 — externalId = 로 부르던 코드, StoredUser(externalId = ...), StoredUser.externalId, 이전 버전이 저장한 로그인 유저 모두 그대로 읽힌다.
  • api-secret 은 서버 전용 — 앱에는 넣지 마세요(공개 api-key 만).
  • 빌드: JDK 17 (툴체인 자동 provisioning). gradle test

라이선스

Apache-2.0

GitHub 에서 이 문서 고치기