> For the complete documentation index, see [llms.txt](https://developers.fingerpush.com/app-push/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.fingerpush.com/app-push/sdk-manual/undefined.md).

# 실시간 현황 푸시 매뉴얼

## iOS

### 1. 개요

FingerPush iOS Live Activity SDK는 ActivityKit Live Activity의 **시작·토큰 등록**을 처리합니다.\
앱 또는 Push-to-Start로 Live Activity를 시작한 뒤, FingerPush 서버에 **start / update 토큰**을 등록하면 서버 APNs 푸시로 **갱신·종료**할 수 있습니다.

> ActivityKit은 Swift 전용입니다. Swift에서 SDK API를 직접 호출하고, Objective-C 앱은 `@objc` 래퍼를 통해 사용합니다.

***

### 2. 지원 버전

iOS FingerPush SDK 3.8.9 부터 지원

| 기능                                            | 최소 iOS    | SDK API                                                                                       |
| --------------------------------------------- | --------- | --------------------------------------------------------------------------------------------- |
| 앱에서 Live Activity 시작 + update 토큰 등록           | **16.2+** | `registerLiveActivityStart`                                                                   |
| Push-to-Start용 start 토큰 등록 및 start 수신 observe | **17.2+** | <p><code>registerLiveActivityPushToStart</code><br><code>observeLiveActivityUpdate</code></p> |

***

### 3. 배포 구성

```
├── finger.xcframework          ← FingerPush SDK 바이너리
└── finger+LiveActivity.swift   ← Live Activity Swift 확장 (소스 배포)
```

`finger+LiveActivity.swift`는 Swift 제네릭/`ActivityAttributes` 때문에 **xcframework에 넣지 않고**, 앱 타겟 **Compile Sources**에 소스로 추가합니다.\
(앱과 SDK 간 Swift 컴파일러 버전 불일치 방지)

***

### 4. 프로젝트 설정

1. `finger.xcframework` Link / Embed
2. `finger+LiveActivity.swift`를 앱 타겟 Compile Sources에 추가
3. Widget Extension에 Live Activity UI 및 `ActivityAttributes` 구현
   * 앱 타겟과 Widget Extension에 **동일 Attributes 타입** 공유
4. 앱 `Info.plist`에 Supports Live Activities(`NSSupportsLiveActivities) = YES`
5. (권장) Push-to-Start / 잦은 갱신 사용 시 info.plist의 Supports Live Activities Frequent Updates(`NSSupportsLiveActivitiesFrequentUpdates`) = YES 관련 설정 확인
6. 푸시·Live Activity 관련 Push Notifications Capability 추가 / APNs 인증서 설정
7. Background Modes의 Remote notifications 활성화

***

### 5. 아키텍처 — 두 가지 시작 방식

#### 방식 A. 앱에서 직접 시작 (iOS 16.2+)

```
앱
  → registerLiveActivityStart (핑거푸시 api)
    Live Activity 생성

서버
  → update 푸시 발송
```

#### 방식 B. Push-to-Start (iOS 17.2+)

```
앱
  → registerLiveActivityPushToStart (핑거푸시 api)
    start 토큰 발급 → 서버 등록

서버
  → start 푸시 발송

iOS (푸시 수신시)
  → Live Activity 생성 (앱 종료 상태에서도 가능)

앱 AppDelegate (didFinishLaunching)
  → observeLiveActivityUpdate (핑거푸시 api)
    update 토큰 발급 → 서버 등록

이후
  → 서버가 update / end 푸시로 제어
```

***

### 6. API 레퍼런스

제공 API는 아래 **3개**입니다.

#### 6.1 `registerLiveActivityStart` (iOS 16.2+)

#### 방식 A. 앱에서 직접 시작

앱에서 Live Activity를 시작하고, **update 토큰**을 FingerPush 서버에 자동 등록합니다.

```swift
func registerLiveActivityStart<A: ActivityAttributes>(
    attributes: A,
    content: ActivityContent<A.ContentState>,
    orderId: String,
    onTokenRegistered: ((_ updateToken: String?, _ serverCode: String?, _ error: Error?) -> Void)? = nil,
    completion: @escaping (Result<Activity<A>, Error>) -> Void
)
```

| 파라미터                | 설명                               |
| ------------------- | -------------------------------- |
| `attributes`        | 고정 표시 데이터 (주문번호, 가게명 등)          |
| `content`           | 초기 데이터 `ContentState` (주문확인 중 등) |
| `orderId`           | 핑거푸시 서버에서 Activity를 구분하는 ID      |
| `onTokenRegistered` | 핑거푸시 서버 orderId 등록 결과 (비동기)      |
| `completion`        | Activity 생성 직후 결과                |

**예시**

```swift
if #available(iOS 16.2, *) {
    finger.sharedData().registerLiveActivityStart(
        attributes: OrderLiveActivityAttributes(
            orderId: "ORDER-001",
            storeName: "핑거푸시 카페",
            orderItems: "아메리카노 외 2건"
        ),
        content: .init(
            state: .init(
                status: .preparing,
                statusMessage: "주문이 확인 되었습니다",
                estimatedMinutes: 30,
                deliveryPersonName: nil
            ),
            staleDate: nil
        ),
        orderId: "ORDER-001",
        onTokenRegistered: { updateToken, serverCode, error in
            print("updateToken:", updateToken ?? "")
            print("serverCode:", serverCode ?? "", error ?? "")
        }
    ) { result in
        switch result {
        case .success(let activity):
            print("시작:", activity.id)
        case .failure(let error):
            print("실패:", error)
        }
    }
}
```

***

#### 6.2 `registerLiveActivityPushToStart` (iOS 17.2+)

#### 방식 B. Push-to-Start

Push-to-Start용 **start 토큰**을 발급받아 서버에 등록합니다.\
`orderId`를 아는 시점(예: 주문 완료 화면)에서 ViewController 등에서 호출합니다.

방식B(Push-to-start)는 `registerLiveActivityPushToStart와 observeLiveActivityUpdate 2개 api 를 사용해야합니다.`

```swift
@available(iOS 17.2, *)
func registerLiveActivityPushToStart<A: ActivityAttributes>(
    for attributesType: A.Type,
    orderId: String,
    onRegistered: ((_ startTokenHex: String?, _ serverCode: String?, _ error: Error?) -> Void)? = nil
)
```

| 파라미터             | 설명                                                              |
| ---------------- | --------------------------------------------------------------- |
| `attributesType` | `ActivityAttributes` 타입 (예: `OrderLiveActivityAttributes.self`) |
| `orderId`        | 핑거푸시 서버에서 Activity를 구분하는 ID                                     |
| `onRegistered`   | 핑거푸시 서버 orderId 등록 결과 (비동기)                                     |

핑거푸시 서버는 등록된 start 토큰으로 start 푸시를 보내 Live Activity를 생성합니다.

**예시**

```swift
if #available(iOS 17.2, *) {
    finger.sharedData().registerLiveActivityPushToStart(
        for: OrderLiveActivityAttributes.self,
        orderId: "ORDER-001"
    ) { startTokenHex, serverCode, error in
        print("start 토큰:", startTokenHex ?? "")
        print("serverCode:", serverCode ?? "", error ?? "")
    }
}
```

***

#### 6.3 `observeLiveActivityUpdate` (iOS 17.2+)

#### 방식 B. Push-to-Start

Push-to-Start로 생성된 Activity를 감지하고, **update 토큰**을 서버에 등록합니다.\
`AppDelegate`의 `didFinishLaunchingWithOptions`에서 **앱 실행당 1회** 호출합니다.

방식B(Push-to-start)는 `registerLiveActivityPushToStart와 observeLiveActivityUpdate 2개 api 를 사용해야합니다.`

```swift
@available(iOS 17.2, *)
func observeLiveActivityUpdate<A: ActivityAttributes>(
    for attributesType: A.Type,
    orderIdProvider: @escaping (A) -> String,
    onRegistered: ((_ activity: Activity<A>, _ updateTokenHex: String?, _ error: Error?) -> Void)? = nil
)
```

| 파라미터              | 설명                                            |
| ----------------- | --------------------------------------------- |
| `attributesType`  | 관찰할 Attributes 타입                             |
| `orderIdProvider` | attributes에서 orderId 추출 (예: `{ $0.orderId }`) |
| `onRegistered`    | update 토큰 서버 등록 결과                            |

**예시 (AppDelegate / Swift 래퍼)**

```swift
if #available(iOS 17.2, *) {
    finger.sharedData().observeLiveActivityUpdate(
        for: OrderLiveActivityAttributes.self,
        orderIdProvider: { $0.orderId }
    ) { activity, updateTokenHex, error in
        print("Activity:", activity.id)
        print("updateToken:", updateTokenHex ?? "", error ?? "")
    }
}
```

***

### 7. Objective-C 사용 시 연동

SDK Live Activity API는 Swift 제네릭이라 Obj-C에서 직접 호출할 수 없습니다.\
앱에서 `@objc` 래퍼를 만들어 사용합니다. (샘플: `OrderLiveActivityManager`)

권장 매핑:

| 시점                              | 호출                                                               |
| ------------------------------- | ---------------------------------------------------------------- |
| 주문 완료 등                         | `registerLiveActivityStart` 또는 `registerLiveActivityPushToStart` |
| `didFinishLaunchingWithOptions` | `observeLiveActivityUpdate` (Push-to-Start 사용 시)                 |

***

### 8. Attributes / ContentState 구현 가이드

앱·Widget이 **동일한** `ActivityAttributes`를 공유해야 합니다.

```swift
struct OrderLiveActivityAttributes: ActivityAttributes {
    public struct ContentState: Codable, Hashable {
        var status: OrderStatus
        var statusMessage: String
        var estimatedMinutes: Int?
        var deliveryPersonName: String?
    }

    var orderId: String
    var storeName: String
    var orderItems: String
}
```

| 구분           | 필드                                   | 변경                     |
| ------------ | ------------------------------------ | ---------------------- |
| Attributes   | `orderId`, `storeName`, `orderItems` | Live Activity 수명 동안 고정 |
| ContentState | `status`, `statusMessage` 등          | 서버 update 푸시로 변경       |

Push-to-Start 시 서버 payload의 `attributes-type`은 **타입 이름과 일치**해야 합니다.\
예: `"OrderLiveActivityAttributes"`

***

### 9. 서버 푸시 Payload

참고, iOS 푸시 수신 시 payload 입니다.

#### Start (Push-to-Start, start 토큰)

```json
{
  "aps": {
    "timestamp": 1718432400,
    "event": "start",
    "input-push-token": 1,
    "content-state": {
      "status": "preparing",
      "statusMessage": "주문이 접수되었습니다"
    },
    "attributes-type": "OrderLiveActivityAttributes",
    "attributes": {
      "orderId": "ORDER-001",
      "storeName": "핑거푸시 카페",
      "orderItems": "아메리카노 외 2건"
    },
    "alert": {
      "title": "Live Activity 시작",
      "body": "배송 상태가 시작되었습니다",
      "sound": "default"
    }
  }
}
```

* `timestamp`: 발송 시점 Unix time 권장
* `alert`: start에 필요
* `input-push-token: 1`: update 토큰 발급용

#### Update (update 토큰)

```json
{
  "aps": {
    "timestamp": 1718436000,
    "event": "update",
    "content-state": {
      "status": "onTheWay",
      "statusMessage": "배달이 시작되었습니다",
      "estimatedMinutes": 15
    },
    "alert": {
      "title": "배달 시작",
      "body": "주문이 배달 중입니다",
      "sound": "default"
    }
  }
}
```

* `alert`는 선택. 넣으면 Live Activity 확장/배너 알림 용도
* **일반 알림센터 푸시와는 다름**. 알림센터에 남는 일반 푸시가 필요하면 **별도 alert 푸시** 발송

#### End (update 토큰)

```json
{
  "aps": {
    "timestamp": 1718439000,
    "event": "end",
    "content-state": {
      "status": "delivered",
      "statusMessage": "배달이 완료되었습니다"
    },
    "dismissal-date": 1718439060
  }
}
```

***

### 10. 권장 호출 위치

<table><thead><tr><th width="129.6875">방식</th><th>API</th><th width="207.921875">호출 위치</th><th>횟수</th></tr></thead><tbody><tr><td>방식 A. 앱에서 직접 시작</td><td><code>registerLiveActivityStart</code></td><td>주문 완료 등 ViewController</td><td>필요 시</td></tr><tr><td>방식 B. Push-to-Start</td><td><code>registerLiveActivityPushToStart</code></td><td>주문 완료 등 ViewController</td><td>필요 시</td></tr><tr><td>방식 B. Push-to-Start</td><td><code>observeLiveActivityUpdate</code></td><td><code>AppDelegate</code> <code>didFinishLaunchingWithOptions</code></td><td>앱 실행 시마다</td></tr></tbody></table>

***

### 11. 트러블슈팅

| 증상                                           | 확인                                                                                                                                 |
| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Live Activity가 안 뜸                           | iOS 지원 버전, 권한 info.plist-`NSSupportsLiveActivities`, Widget Extension 지원OS 버전 확인, 실시간 현황 권한, capability 설정, Background Modes 설정 확인 |
| start 푸시 후 Activity 생성 안됨 (Push-to-Start 방식) | <p>핑거푸시 앱 설정 APNs 등록 확인<br>Push-to-Start 방식 예산 소진 : 테스트 시 과도한 호출됨 - 30분후 다시 테스트 권장</p>                                             |
| Activity 가 update 안됨 (Push-to-Start 방식)      | `observeLiveActivityUpdate`를 AppDelegate에서 호출 확인                                                                                   |
| Swift 버전 불일치 빌드 에러                           | `finger+LiveActivity.swift`를 소스로 배포·추가했는지                                                                                          |
| Obj-C에서 API가 안 보임                            | `@objc` 래퍼 사용 여부                                                                                                                   |
| Live Activity  멈춤(로딩인디게이터 노출)                | 푸시 수신 시 데이터 형식 불일치로 actitivity 크래시                                                                                                 |

***

## Android

실시간 현황 푸시 기능은 Android SDK 3.8.3 이상부터 지원합니다.

### 푸시 스타일 설정

> 실시간 현황 푸시 UI 스타일을 등록하는 설정입니다.\
> 고정 값은 `Application`에서, 상황에 따라 바꿀 때는 푸시 수신부에서도 등록할 수 있습니다.

<details>

<summary>푸시 스타일 설정 예시 및 Builder 메서드</summary>

```kt
val style = FingerLiveNotification.StyleConfig.Builder()
    .setStartIcon(R.drawable.ic_start)
    .setEndIcon(R.drawable.ic_end)
    .setTrackerIcon(R.drawable.ic_tracker)
    .setLargeIcon(R.drawable.ic_large)
    .setActiveColor(Color.parseColor("#4CAF50"))
    .setInactiveColor(Color.parseColor("#BDBDBD"))
    .setDefaultShortText("배달중")
    .setOnlyAlertOnce(false)
    .setShowPoints(false)
    .build()
FingerLiveNotification.registerStyleConfig(style)
```

Builder 메서드

* setStartIcon(int)\
  진행 바 시작 아이콘. drawable 리소스 ID<br>
* setEndIcon(int)\
  진행 바 끝 아이콘. drawable 리소스 ID<br>
* setTrackerIcon(int)\
  현재 위치를 가리키는 트래커 아이콘. drawable 리소스 ID<br>
* setLargeIcon(int)\
  알림 큰 아이콘. drawable 리소스 ID. 페이로드 값이 우선되며, 없거나 실패 시 사용.<br>
* setActiveColor(int)\
  완료/활성 세그먼트 색. 색상 값(Color Int). 기본값 Color.GREEN<br>
* setInactiveColor(int)\
  미완료/비활성 세그먼트 색. 색상 값(Color Int). 기본값 Color.GRAY<br>
* setDefaultShortText(String)\
  상태바 chip용 짧은 문구.<br>
* setShowPoints(Boolean)\
  세그먼트 사이 포인트(점) 표시. 기본값 true<br>
* setOnlyAlertOnce(Boolean)\
  업데이트 시 소리/진동을 한 번만 작동. 기본값 false

</details>

### 푸시 정보 등록

> 실시간 현황 푸시 정보를 서버에 등록하는 기능입니다.
>
> 실시간 현황 푸시 ID, 채널 ID, 알림 ID 가 제대로 등록되어야 실시간 알림 기능이 정상 작동합니다.

<details>

<summary>푸시 등록하기</summary>

기기(디바이스)에 표시할 식별 정보(`orderNo`, `channel`, `notificationId`)를 서버에 등록합니다.\
등록 후 주문/배송 등 고유 값으로 갱신하는 푸시를 보내면, 앱에서 `FingerLiveNotification.update`로 UI를 업데이트합니다.

```kt
val liveNotification = LiveNotification.Builder().apply {
        setOrderNo(String)        // 실시간 현황 푸시 ID. 주문/배송 등 건을 구분하는 고유 값
        setChannel(String)        // 알림 채널 ID
        setNotificationId(int)    // 알림 ID
    }.build()

FingerPushManager.getInstance(context).registerLiveNotification(liveNotification, object : LiveUpdateListener {
        override fun onSuccess(code: String, message: String) {
                // 등록 성공
        }

        override fun onError(code: String, message: String) {
                // 등록 실패
        }
})
```

</details>

### 푸시 생성/갱신

> 푸시를 받았을 때, 생성/갱신합니다.
>
> 수신부에서 핑거푸시·Live 여부를 확인한 뒤 호출합니다.

<details>

<summary>실시간 현황 푸시 생성/갱신</summary>

```kt
class IntentService : FingerPushFcmListener() {
    override fun onMessage(context: Context, data: Bundle) {
        if (FingerPushManager.isFingerPush(data)) {    // 핑거푸시에서 발송한 페이로드 체크
            if (FingerPushManager.isLiveNotification(data)) {    // 페이로드가 실시간 알림 데이터인지 체크
                // 이동하는 Activity 설정
                val intent = Intent(this@IntentService, MainActivity::class.java).apply {
                    addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
                }
                
                
                val pendingIntent = PendingIntent.getActivity(
                    this@IntentService,
                    int, // LiveNotification.Builder 를 통해 등록한 알림 ID
                    intent,
                    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
                )
                
                FingerLiveNotification.update(this, data, pendingIntent) {
                    // 실시간 알림이 제거되는 경우
                }
            } else {
                // 일반 푸시
                ...
            }
        }
    }
}
```

</details>

### Payload 정보

<table data-search="false"><thead><tr><th width="249.55859375">키</th><th>설명</th></tr></thead><tbody><tr><td>event</td><td>알림 상태 값(start / update / end)</td></tr><tr><td>shortText</td><td>상태바 chip용 짧은 문구</td></tr><tr><td>liveNotititle</td><td>알림 제목</td></tr><tr><td>liveNotiMessage</td><td>알림 내용</td></tr><tr><td>dismissTime</td><td>상태 값이 end 된 후 알림이 자동 제거되는 시간(분). 기본값 10</td></tr><tr><td>currentSegment</td><td>현재 진행 중인 세그먼트 단계</td></tr><tr><td>totalSegment</td><td>총 세그먼트 단계</td></tr></tbody></table>
