SDK v1.2 → v1.4 마이그레이션
v1.2.x 통합을 v1.3을 거치지 않고 곧바로 v1.4로 올리는 가이드.
의존성은 곧바로 v1.4로 올리세요 — 중간에 v1.3을 설치할 이유가 없습니다. 이 가이드는 v1.2.x와 v1.4 사이의 모든 코드 변경을 한 곳에 모아 v1.4 API 기준으로만 서술합니다. v1.4가 다시 갈아엎는 v1.3 시절 코드를 잠깐 쓰는 일이 없도록 하기 위해서입니다.
v1.3.x에서 올라오시나요? 더 짧은 SDK v1.3 → v1.4 마이그레이션을 쓰세요. 아직 v1 Server API를 호출한다면 API v1 → v2도 함께 진행하세요.
iOS
1. CameraViewModel → CameraController · 라이선스는 생성자로 · captureSessionId 필수
v1.2 시절 패턴 세 가지가 한 번에 바뀝니다: 타입 이름이 바뀌고, 라이선스가
생성자로 이동하며, initializeCamera에 서버가 발급한 captureSessionId
(POST /api/capture-sessions)가 필수입니다.
// ❌ v1.2.x
@StateObject private var viewModel: CameraViewModel
try await viewModel.initializeCamera(
licenseInfo: LicenseInfo(apiKey: "YOUR_API_KEY"),
initialPosition: .back
) { result in /* ... */ }
CameraView(viewModel: viewModel)
// ✅ v1.4
@StateObject private var controller = CameraController(
configuration: DetectionConfiguration(species: .dog, purpose: .petProfileRegistration),
licenseInfo: LicenseInfo(apiKey: "YOUR_API_KEY")
)
let sessionId = try await createCaptureSessionFromServer() // POST /api/capture-sessions
try await controller.initializeCamera(initialPosition: .back, captureSessionId: sessionId)
CameraView(controller: controller) Breaking: captureSessionId 없이는 컴파일되지 않습니다. 카메라 초기화 전에
서버에서 세션 ID를 발급하세요. → Server API
왜: 컨트롤러가 수명 동안 라이선스를 보유하고(검증 1회),
initializeCamera는 카메라 연결에만 집중하며, 타입 이름이 Android의
CameraController와 일치합니다. → 시작하기
2. previewLayer 제거 — 프리뷰는 CameraView가 담당
CameraViewModel.previewLayer(v1.2)는 제거됐고, v1.4에서 캡처 세션은 공개
getter가 없지만 — 이니셜라이저로 자체 AVCaptureSession을 주입하면 완전 커스텀
프리뷰는 여전히 가능합니다(커스터마이징 참고).
기본 경로에서는 CameraView가 프리뷰를 담당하며, 준비 상태 확인은 published 프로퍼티
isInitialized를 쓰세요.
// ❌ v1.2.x
if viewModel.previewLayer != nil { showCameraView() }
view.layer.addSublayer(viewModel.previewLayer)
// ✅ v1.4 — 프리뷰는 CameraView가 그리고, 준비 상태는 명시적으로 확인
if controller.isInitialized { showCameraView() }
CameraView(controller: controller) 프리뷰 위에 커스텀 UI를 올리려면 CameraView 위에 뷰를 얹으세요 —
커스터마이징 참고.
3. 탐지가 저절로 시작되지 않습니다 — startDetection() 호출
initializeCamera()는 카메라만 연결하고 탐지를 시작하지 않습니다.
반환된 뒤 명시적으로 시작하세요(재촬영도 같은 호출):
try await controller.initializeCamera(initialPosition: .back, captureSessionId: sessionId)
controller.startDetection() 4. 종료: stopDetection() → finalizeCamera()
// ❌ v1.2.x
controller.stopDetection()
// ✅ v1.4
controller.finalizeCamera() 일시정지/재개는 별도 동사(pauseDetection())를 가져 완전 종료가 항상
명시적입니다.
iOS 변경 요약
| v1.2.x | v1.4 |
|---|---|
CameraViewModel / CameraView(viewModel:) | CameraController / CameraView(controller:) |
initializeCamera(licenseInfo:initialPosition:) | 생성자 CameraController(configuration:licenseInfo:) + initializeCamera(initialPosition:captureSessionId:) |
previewLayer | 제거 — 프리뷰는 CameraView, 준비 상태는 isInitialized |
stopDetection() | finalizeCamera(); 탐지 시작은 명시적 startDetection() |
Android
1. 전역 PetnowApiClient 소멸 — 라이선스·설정은 세션 단위로 전달
// ❌ v1.2.x (apiClient 모듈이 더 이상 배포되지 않음 — 컴파일 에러)
PetnowApiClient.init(key = "YOUR_API_KEY", isDebugMode = false)
PetnowApiClient.configureDetectionMode(
purpose = DetectionPurpose.PET_PROFILE_REGISTRATION,
species = PetSpecies.DOG,
enableFakeDetection = true
)- 권장 (
CameraView+CameraController):LicenseInfo를CameraController(context, license, scope)생성자에,DetectionConfiguration을initializeCamera(config, captureSessionId)에 전달합니다. → 기본 사용법 - 기존
PetnowCameraFragment유지: 라이선스는 Fragment args(ARG_API_KEY)나provideLicense()로, 설정은ARG_DETECTION_CONFIGURATION으로 전달합니다. → Fragment (레거시)
PetnowApiClient.isSuccessInitialize의 대체물은 없습니다 — 초기화 실패는 PetnowUIError로 드러납니다(4번). 설정 타입 패키지가 이동하고(io.petnow.api.client.* → io.petnow.ui.config.*, 형태 동일), Android의 isDebugMode는 사라졌습니다.
PetnowApiClient로 Petnow Server API를 호출했다면(캡처 세션·업로드·등록): 해당 헬퍼는 모듈과 함께 은퇴했습니다. 앱 서버에서 Server API를 호출하세요 — v1.4 클라이언트 SDK는 촬영만 담당합니다.
2. captureSessionId 필수
어느 경로든 서버 발급 captureSessionId(POST /api/capture-sessions)가
필수입니다.
// ✅ v1.4 — CameraController 경로
val sessionId = createCaptureSessionFromServer()
controller.initializeCamera(configuration, captureSessionId = sessionId)
// ✅ v1.4 — Fragment 경로
val fragment = MyCameraFragment().apply {
arguments = Bundle().apply {
putString(ARG_CAPTURE_SESSION_ID, sessionId.toString())
}
}Breaking: captureSessionId 없이는 Fragment가 즉시 종료되고, 컨트롤러
경로는 초기화에 실패합니다.
3. 리스너 import 패키지 변경 — 그리고 V2로
// ❌ v1.2.x
import io.petnow.ui.PetnowCameraDetectionListener
// ✅ v1.4
import io.petnow.callback.PetnowCameraDetectionListenerV2 CameraController 경로에서는 setDetectionListenerV2를 쓰세요. V2는 iOS와
동일한 결과 모델 — sealed DetectionStatus
(NoObject/Processing/Detected/Finished/Failed(reason))와
CameraResult(Success(fingerprintImageFiles, appearanceImageFiles)/Fail) —
를 전달합니다.
4. 초기화 실패는 구조화된 PetnowUIError
initializeCamera()가 더 이상 플랫폼 예외를 그대로 던지지 않으며, API 키를
서버에 검증합니다(프로세스당 키별 1회).
// ✅ v1.4 — catch 하나 + sealed when
try {
controller.initializeCamera(configuration, captureSessionId)
} catch (e: PetnowUIError) {
when (e) {
is PetnowUIError.InvalidLicense -> { /* API 키 거절 */ }
is PetnowUIError.PermissionDenied -> { /* 권한 없음 */ }
is PetnowUIError.CameraOpenFailed -> { /* 카메라 오류 — e.cause 참조 */ }
}
}CancellationException은 여전히 그대로 전파됩니다(정상 수명주기 — 다시 던지세요).
Android 변경 요약
| v1.2.x | v1.4 |
|---|---|
PetnowApiClient.init() / configureDetectionMode() | 세션 단위: CameraController(context, license, scope) + initializeCamera(config, captureSessionId) 또는 Fragment args |
PetnowApiClient의 서버 API 헬퍼 | 은퇴 — Server API는 앱 서버에서 호출 |
| 세션 ID 없는 Fragment | ARG_CAPTURE_SESSION_ID 필수 |
import io.petnow.ui.PetnowCameraDetectionListener | io.petnow.callback.…ListenerV2 (권장) |
원시 SecurityException 등 플랫폼 예외 | sealed PetnowUIError |
마이그레이션 체크리스트
공통
- 서버에서
POST /api/capture-sessions로captureSessionId발급
iOS
-
CameraViewModel→CameraController; 라이선스를 생성자로 - 서버 세션 ID로
initializeCamera(initialPosition:captureSessionId:) -
previewLayer사용 코드를CameraView로 교체 (준비 상태는isInitialized) -
initializeCamera후startDetection()호출 ·stopDetection()→finalizeCamera()
Android
-
PetnowApiClient호출 제거; 라이선스/설정을 세션 단위로 전달; 서버 API 호출은 앱 서버로 이관 -
captureSessionId전달 (컨트롤러 인자 또는ARG_CAPTURE_SESSION_ID) - 리스너 import를
io.petnow.callback으로, V2로 이전 -
initializeCamera()주변을PetnowUIErrorcatch로 교체