SDK v1.2 → v1.4 Migration
Upgrade a v1.2.x integration directly to v1.4 — no need to pass through v1.3.
Upgrade your dependency straight to v1.4 — there is no reason to install v1.3 on the way. This guide lists every code change between v1.2.x and v1.4 in one place, expressed against the v1.4 API only, so you never write intermediate v1.3-era code that v1.4 then replaces.
Coming from v1.3.x instead? Use the shorter SDK v1.3 → v1.4 Migration. Still calling the v1 Server API? Also follow API v1 → v2.
iOS
1. CameraViewModel → CameraController, license in the constructor, captureSessionId required
Three v1.2-era patterns change in one stroke: the type is renamed, the license
moves to the constructor, and initializeCamera now requires a
captureSessionId issued by the server (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: without captureSessionId the call does not compile. Generate a
session ID server-side before initializing the camera. →
Server API
Why: the controller owns the license for its lifetime (validation runs
once), initializeCamera only connects the camera, and the type name matches
Android's CameraController. → Getting Started
2. previewLayer removed — CameraView renders the preview
CameraViewModel.previewLayer (v1.2) is gone, and in v1.4 the capture session has no public getter — but you can still inject
your own AVCaptureSession through the initializer for a fully custom
preview. → Customization
CameraView owns preview rendering; to check readiness, use the published
isInitialized.
// ❌ v1.2.x
if viewModel.previewLayer != nil { showCameraView() }
view.layer.addSublayer(viewModel.previewLayer)
// ✅ v1.4 — CameraView renders the preview; check readiness explicitly
if controller.isInitialized { showCameraView() }
CameraView(controller: controller) To layer custom UI over the preview, place your views on top of CameraView —
see Customization.
3. Detection no longer starts by itself — call startDetection()
initializeCamera() connects the camera but does not begin detection.
After it returns, start (and re-start for a retake) explicitly:
try await controller.initializeCamera(initialPosition: .back, captureSessionId: sessionId)
controller.startDetection() 4. Teardown: stopDetection() → finalizeCamera()
// ❌ v1.2.x
controller.stopDetection()
// ✅ v1.4
controller.finalizeCamera() Pause/resume have their own verbs (pauseDetection()), so a full teardown is
always explicit.
iOS change summary
| v1.2.x | v1.4 |
|---|---|
CameraViewModel / CameraView(viewModel:) | CameraController / CameraView(controller:) |
initializeCamera(licenseInfo:initialPosition:) | constructor CameraController(configuration:licenseInfo:) + initializeCamera(initialPosition:captureSessionId:) |
previewLayer | removed — CameraView renders the preview; readiness via isInitialized |
stopDetection() | finalizeCamera(); detection starts via an explicit startDetection() |
Android
1. Global PetnowApiClient gone — license and configuration are passed per session
// ❌ v1.2.x (the apiClient module is no longer shipped — compile error)
PetnowApiClient.init(key = "YOUR_API_KEY", isDebugMode = false)
PetnowApiClient.configureDetectionMode(
purpose = DetectionPurpose.PET_PROFILE_REGISTRATION,
species = PetSpecies.DOG,
enableFakeDetection = true
)- Recommended (
CameraView+CameraController): passLicenseInfoto theCameraController(context, license, scope)constructor andDetectionConfigurationtoinitializeCamera(config, captureSessionId). → Basic Usage - Keep
PetnowCameraFragment: pass the license via Fragment args (ARG_API_KEY) orprovideLicense(), and the configuration viaARG_DETECTION_CONFIGURATION. → Fragment (legacy)
PetnowApiClient.isSuccessInitialize has no replacement — initialization failures surface as PetnowUIError (item 4). The configuration types move packages (io.petnow.api.client.* → io.petnow.ui.config.*, same shape), and isDebugMode is gone on Android.
If you called the Petnow Server API through PetnowApiClient (capture sessions, uploads, registration): those helpers were retired with the module. Call the Server API from your app server — the v1.4 client SDK handles capture only.
2. captureSessionId is required
Whichever path you take, a server-issued captureSessionId
(POST /api/capture-sessions) is now mandatory.
// ✅ v1.4 — CameraController path
val sessionId = createCaptureSessionFromServer()
controller.initializeCamera(configuration, captureSessionId = sessionId)
// ✅ v1.4 — Fragment path
val fragment = MyCameraFragment().apply {
arguments = Bundle().apply {
putString(ARG_CAPTURE_SESSION_ID, sessionId.toString())
}
}Breaking: without captureSessionId the Fragment terminates immediately
and the controller path fails to initialize.
3. Listener import package changed — and move to V2
// ❌ v1.2.x
import io.petnow.ui.PetnowCameraDetectionListener
// ✅ v1.4
import io.petnow.callback.PetnowCameraDetectionListenerV2 On the CameraController path use setDetectionListenerV2: V2 delivers the
same result models as iOS — a sealed DetectionStatus
(NoObject/Processing/Detected/Finished/Failed(reason)) and
CameraResult (Success(fingerprintImageFiles, appearanceImageFiles)/Fail).
4. Initialization failures are the structured PetnowUIError
initializeCamera() no longer leaks raw platform exceptions, and it validates
the API key against the server (once per key per process).
// ✅ v1.4 — a single catch + sealed when
try {
controller.initializeCamera(configuration, captureSessionId)
} catch (e: PetnowUIError) {
when (e) {
is PetnowUIError.InvalidLicense -> { /* API key rejected */ }
is PetnowUIError.PermissionDenied -> { /* permission missing */ }
is PetnowUIError.CameraOpenFailed -> { /* camera error — see e.cause */ }
}
}CancellationException still propagates untouched (normal lifecycle — rethrow it).
Android change summary
| v1.2.x | v1.4 |
|---|---|
PetnowApiClient.init() / configureDetectionMode() | per-session: CameraController(context, license, scope) + initializeCamera(config, captureSessionId), or Fragment args |
PetnowApiClient server-API helpers | retired — call the Server API from your app server |
| Fragment without a session ID | ARG_CAPTURE_SESSION_ID required |
import io.petnow.ui.PetnowCameraDetectionListener | io.petnow.callback.…ListenerV2 (recommended) |
raw SecurityException / platform exceptions | sealed PetnowUIError |
Migration checklist
Common
- Server issues a
captureSessionIdviaPOST /api/capture-sessions
iOS
-
CameraViewModel→CameraController; license into the constructor -
initializeCamera(initialPosition:captureSessionId:)with the server session ID - Replace
previewLayerusage withCameraView(+isInitializedfor readiness) - Call
startDetection()afterinitializeCamera·stopDetection()→finalizeCamera()
Android
- Remove
PetnowApiClientcalls; pass license/config per session; move server-API calls to your app server - Pass
captureSessionId(controller arg orARG_CAPTURE_SESSION_ID) - Listener import to
io.petnow.callback, move to V2 - Catch
PetnowUIErroraroundinitializeCamera()