Petnow LogoPetnow

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. CameraViewModelCameraController, 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.xv1.4
CameraViewModel / CameraView(viewModel:)CameraController / CameraView(controller:)
initializeCamera(licenseInfo:initialPosition:)constructor CameraController(configuration:licenseInfo:) + initializeCamera(initialPosition:captureSessionId:)
previewLayerremoved — 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): pass LicenseInfo to the CameraController(context, license, scope) constructor and DetectionConfiguration to initializeCamera(config, captureSessionId). → Basic Usage
  • Keep PetnowCameraFragment: pass the license via Fragment args (ARG_API_KEY) or provideLicense(), and the configuration via ARG_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.xv1.4
PetnowApiClient.init() / configureDetectionMode()per-session: CameraController(context, license, scope) + initializeCamera(config, captureSessionId), or Fragment args
PetnowApiClient server-API helpersretired — call the Server API from your app server
Fragment without a session IDARG_CAPTURE_SESSION_ID required
import io.petnow.ui.PetnowCameraDetectionListenerio.petnow.callback.…ListenerV2 (recommended)
raw SecurityException / platform exceptionssealed PetnowUIError

Migration checklist

Common

  • Server issues a captureSessionId via POST /api/capture-sessions

iOS

  • CameraViewModelCameraController; license into the constructor
  • initializeCamera(initialPosition:captureSessionId:) with the server session ID
  • Replace previewLayer usage with CameraView (+ isInitialized for readiness)
  • Call startDetection() after initializeCamera · stopDetection()finalizeCamera()

Android

  • Remove PetnowApiClient calls; pass license/config per session; move server-API calls to your app server
  • Pass captureSessionId (controller arg or ARG_CAPTURE_SESSION_ID)
  • Listener import to io.petnow.callback, move to V2
  • Catch PetnowUIError around initializeCamera()

On this page