Petnow LogoPetnow
Android SDKUI 모듈

기본 사용법

UI 모듈 사용법 설명.


시작하기 전에

이 가이드를 시작하기 전에 시작하기를 완료하세요. PetnowUiClient.initialize()configureDetection() 호출이 필수입니다.

이 가이드에서는 PetnowUI 모듈의 핵심인 PetnowCameraFragment를 사용하여 반려동물 코무늬·얼굴 촬영 기능을 앱에 통합하는 방법을 단계별로 안내합니다.

카메라 화면 생성

Fragment를 상속하고 Detection 리스너를 설정합니다.

카메라 화면 표시

captureSessionId를 전달하고 Fragment를 화면에 표시합니다.

촬영 결과 처리

성공/실패 콜백을 처리합니다.

에러 처리

권한 및 초기화 에러를 올바르게 처리합니다.

추가 기능

진행률/상태 관찰, 카메라 전환, Detection 재실행을 사용합니다.



Step 1: 카메라 화면 생성

PetnowCameraFragment를 상속하여 카메라 화면을 생성합니다.

import android.os.Bundle
import io.petnow.ui.PetnowCameraFragment
import io.petnow.ui.PetnowCameraDetectionListener
import java.util.UUID

class ClientCameraFragment : PetnowCameraFragment(), PetnowCameraDetectionListener { 
  
  companion object {
    fun newInstance(captureSessionId: UUID) = ClientCameraFragment().apply { 
      arguments = Bundle().apply { 
        putString(ARG_CAPTURE_SESSION_ID, captureSessionId.toString()) 
      } 
    } 
  }
  
  override fun provideCustomOverlayLayout(): Int? = null

  override fun onAttach(context: Context) {
      super.onAttach(context)
      setPetnowCameraDetectionListener(this) 
  }

  // Step 3에서 구현
  override fun onDetectionFinished(result: DetectionCaptureResult) { }

  // Step 5에서 구현
  override fun onDetectionProgress(progress: Int) { }
  override fun onDetectionStatus(primaryDetectionStatus: PetnowDetectionStatus) { }
}

파라미터 이해하기

요소설명
PetnowCameraFragment카메라 프리뷰·탐지 로직을 제공하는 기본 Fragment
PetnowCameraDetectionListener탐지 결과·진행률·상태 콜백 인터페이스
ARG_CAPTURE_SESSION_ID서버에서 발급받은 캡처 세션 ID를 전달하는 키
provideCustomOverlayLayout()커스텀 오버레이 레이아웃 리소스 ID (없으면 null)

Step 2: 카메라 화면 표시

captureSessionId 전달 필수

PetnowCameraFragmentcaptureSessionId를 arguments로 받아야 합니다. 시작하기에서 서버로부터 받은 captureSessionId를 전달하세요.

서버에서 캡처 세션을 생성한 뒤, Fragment를 화면에 표시합니다.

// Activity 또는 다른 Fragment에서 ClientCameraFragment 사용
val captureSessionId: UUID = // 서버로부터 받은 captureSessionId

val fragment = ClientCameraFragment.newInstance(captureSessionId) 
supportFragmentManager.beginTransaction()
    .replace(R.id.fragment_container, fragment)
    .commit()

Fragment가 attach되면 내부적으로 카메라를 초기화하고 탐지를 시작합니다. 기본 Tracking UI만 표시됩니다.

UI Module Setup1

Step 3: 촬영 결과 처리

탐지가 완료되면 onDetectionFinished 콜백이 호출됩니다.

override fun onDetectionFinished(result: DetectionCaptureResult) { 
    when (result) {
        is DetectionCaptureResult.Success -> { 
            val (noseImages, faceImages) = result 
            // 서버에 업로드하거나 다음 화면으로 이동
            uploadImages(noseImages, faceImages)
        }
        is DetectionCaptureResult.Fail -> { 
            // 탐지 실패 — 재시도하거나 세션을 종료합니다
            showRetryOrExitDialog() 
        }
    }
}

DetectionCaptureResult 타입

sealed class DetectionCaptureResult {
    data class Success(
        val noseImageFiles: List<File>,
        val faceImageFiles: List<File>
    ) : DetectionCaptureResult()
    
    data object Fail : DetectionCaptureResult()
}

실패(Fail) 처리하기

DetectionCaptureResult.Fail은 촬영 시간 내에 충분한 이미지를 확보하지 못했을 때 전달됩니다(예: 반려동물이 프레임을 벗어남, 조명 부적합 등).

실패 시 서버 세션 관리

Fail을 수신하면 서버의 캡처 세션은 아직 열린 상태입니다.

  • 재시도: resumeDetection()을 호출하여 동일 세션에서 촬영을 다시 시작
  • 종료: 카메라 화면을 닫고 이전 화면으로 돌아감

재시도하지 않고 화면을 닫으면, Petify Console에서 해당 세션이 "처리 중"으로 표시될 수 있습니다. 서버가 약 5분 후 자동으로 세션을 종료(ABORTED) 처리합니다.

is DetectionCaptureResult.Fail -> {
    AlertDialog.Builder(requireContext())
        .setTitle("촬영 실패")
        .setMessage("코무늬를 충분히 촬영하지 못했습니다. 다시 시도하시겠습니까?")
        .setPositiveButton("재시도") { _, _ ->
            resumeDetection() 
                .onSuccess { /* Detection 재시작됨 */ }
                .onFailure { e -> navigateBack() }
        }
        .setNegativeButton("취소") { _, _ ->
            navigateBack() 
        }
        .show()
}

Step 4: 에러 처리

카메라 사용에는 런타임 권한이 필요합니다. Fragment를 표시하기 전에 권한을 확인하세요.

private val cameraPermissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted ->
    if (isGranted) { 
        showCameraFragment() 
    } else {
        // 권한 거부 시 설정 화면으로 안내
        showPermissionDeniedDialog()
    }
}

private fun checkAndLaunchCamera() {
    when {
        ContextCompat.checkSelfPermission(
            this, Manifest.permission.CAMERA
        ) == PackageManager.PERMISSION_GRANTED -> {
            showCameraFragment()
        }
        else -> {
            cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
        }
    }
}

권한 에러 처리 필수

AndroidManifest.xml<uses-permission android:name="android.permission.CAMERA" />를 선언하고, 런타임에 권한을 요청해야 합니다. 권한 없이 Fragment를 표시하면 카메라가 동작하지 않습니다.

초기화 실패 대응

PetnowUiClient.initialize() 또는 PetnowUiClient.configureDetection()이 사전에 호출되지 않으면 Fragment가 정상 동작하지 않습니다. Application 클래스에서 초기화 여부를 확인하세요.


Step 5: 추가 기능

탐지 진행률 관찰

override fun onDetectionProgress(progress: Int) { 
    // progress: 0~100
    progressBar.progress = progress
}

탐지 상태 관찰

override fun onDetectionStatus(primaryDetectionStatus: PetnowDetectionStatus) { 
    // 각 프레임의 탐지 상태를 UI에 반영
    statusTextView.text = when (primaryDetectionStatus) {
        PetnowDetectionStatus.Detected -> "탐지 성공"
        PetnowDetectionStatus.NoObject -> "반려동물을 프레임에 맞춰주세요"
        PetnowDetectionStatus.TooClose -> "조금 멀리 떨어져주세요"
        PetnowDetectionStatus.TooFarAway -> "조금 가까이 다가가주세요"
        PetnowDetectionStatus.TooDark -> "밝은 곳으로 이동해주세요"
        else -> ""
    }
}

전/후면 카메라 전환

val ctx = context ?: return

if (ctx.cameraInfo.isFrontCameraSupported) {
    switchCamera() 
} else {
    Toast.makeText(ctx, "전면 카메라를 지원하지 않는 기기입니다", Toast.LENGTH_SHORT).show()
}

Detection 재실행

현재 세션의 Detection을 처음부터 다시 시작할 수 있습니다.

resumeDetection() 
    .onSuccess {
        // Detection 재실행 성공
    }
    .onFailure { error ->
        Toast.makeText(context, "재실행 실패: ${error.message}", Toast.LENGTH_SHORT).show()
    }

Detection 일시정지 / 재개

카메라가 작동하는 상태에서 탐지만 일시적으로 멈추고 다시 재개할 수 있습니다. 팁 화면이나 안내 다이얼로그를 표시하는 동안 유용합니다.

// 탐지 일시정지 (진행률, 캡처된 이미지 유지)
pauseDetection()

// 탐지 재개 (중단된 지점부터 계속)
resumeDetection()

startDetectionSession / pauseDetection / resumeDetection

  • startDetectionSession() — 진행률을 0으로 리셋하고 새 Detection Session 시작. 재촬영 시 사용.
  • pauseDetection() — 탐지만 일시적으로 멈춤. 카메라 프리뷰 유지, 진행률 유지.
  • resumeDetection() — 일시정지 시점부터 이어서 재개.
// 예시: 팁 다이얼로그를 표시하는 동안 탐지 일시정지
pauseDetection()

AlertDialog.Builder(requireContext())
    .setTitle("촬영 팁")
    .setMessage("밝은 곳에서 코를 정면으로 촬영하세요.")
    .setPositiveButton("확인") { _, _ ->
        resumeDetection()
    }
    .setOnCancelListener {
        resumeDetection()
    }
    .show()

모범 사례

API 키 안전하게 관리하기

// ❌ 나쁜 예: 코드에 하드코딩
val apiKey = "sk_live_abc123..."

// ✅ 좋은 예: BuildConfig 또는 local.properties 사용
val apiKey = BuildConfig.PETNOW_API_KEY

API 키를 소스 코드에 직접 작성하지 마세요. local.properties 또는 CI/CD 환경변수를 통해 주입하는 것을 권장합니다.

리소스 정리하기

Fragment가 종료될 때 카메라 리소스가 자동으로 해제됩니다. 다만 백그라운드 전환 등 비정상 종료 시에도 안전하게 정리되도록 onDestroyView에서 확인하세요.

override fun onDestroyView() {
    super.onDestroyView()
    // PetnowCameraFragment가 내부적으로 리소스를 정리합니다.
    // 추가 정리가 필요한 커스텀 리소스가 있다면 여기서 처리하세요.
}

실패 시 재시도 또는 화면 닫기

DetectionCaptureResult.Fail을 받았을 때 재시도하지 않는다면, 카메라 화면을 닫아주세요. 서버가 약 5분 후 자동으로 세션을 종료(ABORTED) 처리합니다.


문제 해결

Q. 카메라가 시작되지 않아요

A. 다음을 확인하세요:

  1. AndroidManifest.xml에 카메라 권한(CAMERA) 선언 여부
  2. 런타임 권한 요청 후 Fragment를 표시했는지
  3. PetnowUiClient.initialize()PetnowUiClient.configureDetection()이 사전에 호출되었는지

Q. 촬영이 계속 실패해요

A. 촬영 환경을 점검하세요:

  1. 밝은 실내 조명 아래에서 촬영 (직사광선·그림자 회피)
  2. 카메라와 반려동물 코 사이 거리 30~50 cm 유지
  3. 반려동물이 가만히 있도록 간식 등으로 유도
  4. onDetectionStatus로 전달되는 상태를 UI에 표시하여 사용자에게 가이드 제공

Q. 촬영 실패 후 화면이 멈춤 상태로 남아요

A. DetectionCaptureResult.Fail을 수신한 후 UI 처리를 하지 않으면 카메라 화면이 멈춤 상태로 남습니다. 반드시 resumeDetection()으로 재시도하거나, 화면을 닫고 이전 화면으로 돌아가는 처리를 구현하세요. 자세한 내용은 Step 3를 참고하세요.

Q. resumeDetection()이 실패해요

A. 이미 세션이 종료된 상태에서 호출하면 실패합니다. onFailure 콜백에서 사용자를 이전 화면으로 안내하세요.


다음 단계

기본 사용법을 익혔다면 다음 문서를 참고하세요:

On this page