Petnow LogoPetnow
React Native SDK

기본 사용법

usePetnowCamera hook과 CameraView로 카메라 UI를 통합하는 단계별 가이드.


시작하기 전에

이 가이드를 시작하기 전에 시작하기를 완료하세요. 패키지 설치·카메라 권한 설정·Petnow API 키가 필요합니다.

이 가이드에서는 usePetnowCamera hook과 <CameraView>를 사용하여 반려동물 코무늬/얼굴 촬영 기능을 앱에 통합하는 방법을 단계별로 안내합니다.

카메라 준비

usePetnowCamera로 컨트롤러를 만들고 ready 상태를 기다립니다.

카메라 표시

ready가 되면 <CameraView>를 마운트해 프리뷰와 오버레이를 띄웁니다.

이벤트 받기

탐지 상태·진행률·완료 결과를 콜백으로 처리합니다.

결과 처리

촬영한 이미지를 앱 서버로 업로드합니다.

추가 기능

재촬영, 카메라 전환, 일시정지/재개를 사용합니다.


Step 1: 카메라 준비

usePetnowCamera는 라이선스를 보유하고 검출 커맨드를 노출하는 컨트롤러 핸들을 반환하는 hook입니다. apiKey를 네이티브 SDK에 저장하며(apiKey가 바뀌면 다시 저장), camera.state로 SDK 준비 상태를 노출합니다. status'ready'가 될 때까지 기다린 뒤 <CameraView>를 마운트하며, 준비되기 전에는 로딩 화면을 표시하는 것이 좋습니다.

import { usePetnowCamera, CameraView } from '@petify/react-native-camera-ui';

function Scan({ apiKey }) {
  const camera = usePetnowCamera({ apiKey });

  // ready일 때만 CameraView를 마운트한다.
  if (camera.state.status !== 'ready') { 
    return <Loading state={camera.state} />;  // 직접 만드는 로딩 컴포넌트
  }

  // Step 2에서 <CameraView>를 마운트합니다.
  return null;
}

camera.state

SDK 준비 상태입니다. status === 'ready'일 때만 <CameraView>를 마운트하세요.

status의미
idleapiKey가 아직 없음
initializing네이티브 SDK에 라이선스 저장 중
ready저장 완료 — 카메라를 마운트할 수 있음
error네이티브 initialize 호출 실패(state.error에 메시지)

state라이선스 저장을 추적하며 검증이 아닙니다. 라이선스 검증은 iOS에서 뷰 마운트 시점(initializeCamera)에 수행되고, 검증 실패는 onDetectionStatusfailed로 표면화됩니다. Android는 라이선스 서버 검증이 없습니다(키는 모니터링 용도로 보관). ready보다 먼저 <CameraView>를 마운트하는 것은 지원되지 않습니다.


Step 2: 카메라 표시

camera.stateready가 되면 <CameraView>를 마운트합니다. 마운트가 카메라를 초기화하고, 언마운트가 정리합니다. 종류(species)·목적(purpose)과 서버에서 발급한 captureSessionId를 prop으로 전달합니다.

<CameraView
  camera={camera}
  species="DOG"
  purpose="PET_PROFILE_REGISTRATION"
  captureSessionId={captureSessionId} 
  style={{ flex: 1 }}
  onDetectionStatus={setStatus}
  onDetectionProgress={setProgress}
  onDetectionFinished={onFinished}
/>

<CameraView> 위에 가이드 텍스트·버튼 같은 UI를 올리는 방법은 커스터마이징을 참고하세요.

Props

Prop타입설명
cameraPetnowCamerausePetnowCamera가 반환한 컨트롤러
species'DOG' | 'CAT'반려동물 종류(탐지 파이프라인 결정)
purpose'PET_PROFILE_REGISTRATION' | 'PET_IDENTIFICATION' | 'PET_VERIFICATION'촬영 목적(필요 이미지 수 결정)
captureSessionIdstring서버 발급 UUID(Server API)
difficultyMode?'EASY' | 'NORMAL' | 'HARD'미지정 시 서버가 선택
enableFakeDetection?boolean가짜 이미지 탐지(기본 false)
bracketingMode?boolean캡처 브래키팅 — 채택 프레임 주변을 짧게 연사해 더 좋은 지문 이미지를 수집(기본 false). 런타임 토글 가능(리마운트 불필요)
showDetectionMarker?booleanSDK 기본 코/얼굴 마커 표시(기본 true). falseonDetectionResult의 rect로 완전 커스텀 오버레이를 그릴 수 있으며, 런타임 토글해도 촬영·탐지 세션이 재시작되지 않습니다 (SDK 1.4.3+)
styleViewStyle일반 RN 뷰 스타일

세션 설정(species/purpose/difficultyMode/enableFakeDetection)이나 captureSessionId를 바꾸면 네이티브가 같은 뷰에서 재초기화 흐름을 자동으로 탑니다 — species/purpose/difficultyMode/enableFakeDetection 변경 시 컨트롤러를 재생성하고, captureSessionId 변경 시 capture runtime·detector를 재구성합니다. UX상 더 명확한 전환을 원하면 React key로 뷰를 다시 마운트해도 됩니다.

촬영 목적(purpose)에 따라 서버 캡처 세션의 petId 요건이 다릅니다 — 등록·검증은 petId가 필요하고 식별은 불필요합니다. 자세한 내용은 서버 API – 생체 데이터를 참고하세요.


Step 3: 이벤트 받기

탐지 상태·진행률·완료 결과는 <CameraView>의 이벤트 콜백으로 전달됩니다.

onDetectionStatus={(s: DetectionStatus) => { /* {type} 또는 {type:'failed', reason} */ }}
onDetectionProgress={(p: number) => { /* 0 ~ 100 */ }}
onDetectionFinished={(r: CameraResult) => { /* {success, fingerprintImages, appearanceImages} */ }}
onDetectionResult={(r: DetectionResult) => { /* 프레임별 코/얼굴 박스 — 커스텀 마커용 */ }}
  • onDetectionStatus: 현재 탐지 상태(DetectionStatus). typenoObject | processing | detected | finished | failed(이때 reason 포함).
  • onDetectionProgress: 진행률 0 ~ 100 정수.
  • onDetectionFinished: 최종 결과(CameraResult) — fingerprintImages / appearanceImages는 로컬 file:// URI 배열입니다.
  • onDetectionResult: 프레임별 코/얼굴 검출 박스(DetectionResult). 마커를 직접 그릴 때 사용하며, 예시는 커스터마이징을 참고하세요.

각 타입의 정의는 아래 타입을 참고하세요.

onDetectionProgress·onDetectionResult검출 업데이트마다(프레임 단위에 근접) 메인 스레드에서 호출됩니다. 핸들러는 가볍게 유지하고, 무거운 작업이나 과도한 setState는 피하세요.


Step 4: 결과 처리 / 업로드

onDetectionFinishedfingerprintImages / appearanceImages는 로컬 file:// URI 배열입니다. JS에서 앱 서버로 업로드한 뒤, 서버가 Server API로 등록·인증·식별을 수행합니다. 클라이언트는 촬영과 업로드만 담당합니다.

촬영이 실패해도 onDetectionFinishedsuccess: false빈 이미지 배열로 호출됩니다(실패 사유는 onDetectionStatusfailed로 별도 전달). 결과를 쓰기 전에 r.success를 확인하세요.

x-petnow-api-key앱 서버에만 두세요. RN 클라이언트에서 Petnow API를 직접 호출하지 말고, 아래처럼 file:// URI를 앱 서버로 보내고 앱 서버가 /v2/fingerprints:upload·/v2/appearances:upload로 프록시합니다.

RN에서 file:// URI는 FormData로 업로드합니다:

async function upload(r: CameraResult) {
  if (!r.success) return;
  const form = new FormData();
  // RN의 FormData는 { uri, name, type } 형태로 파일을 받습니다.
  r.fingerprintImages.forEach((uri, i) =>
    form.append('fingerprints', { uri, name: `nose_${i}.jpg`, type: 'image/jpeg' } as any),
  );
  r.appearanceImages.forEach((uri, i) =>
    form.append('appearances', { uri, name: `face_${i}.jpg`, type: 'image/jpeg' } as any),
  );
  form.append('captureSessionId', captureSessionId);
  // 본인 앱 서버 엔드포인트로 전송 → 앱 서버가 x-petnow-api-key로 Petnow에 업로드
  await fetch('https://your-app-server.example.com/petnow/upload', { method: 'POST', body: form });
}

Step 5: 추가 기능

촬영 중·후에 카메라를 제어하는 커맨드입니다. 커맨드는 바인딩된 <CameraView>가 마운트된 뒤 동작합니다.

메서드설명
camera.startDetection()검출 세션 시작(또는 처음부터 재시작 — 재촬영)
camera.pauseDetection()검출 일시정지(카메라는 유지, 진행률 보존)
camera.resumeDetection()일시정지한 검출 재개
camera.switchCamera()전/후면 카메라 전환
camera.retry()같은 apiKey로 네이티브 초기화 재시도(일시적 error 복구용)

재촬영 / 연속 촬영

onDetectionFinished 이후 같은 뷰에서 camera.startDetection()을 호출하면 처음부터 다시 검출합니다(재촬영). 연속 흐름은 완료 콜백에서 다시 시작하면 됩니다.

const onFinished = useCallback((r: CameraResult) => {
  upload(r);
  if (continuousMode) {
    setTimeout(() => camera.startDetection(), 1200); // 잠깐 쉰 뒤 재시작
  }
}, [camera, continuousMode]);

타입

usePetnowCamera<CameraView>가 사용하는 타입입니다.

// usePetnowCamera 옵션
type LicenseInfo = {
  apiKey: string;
};

// usePetnowCamera가 반환하는 컨트롤러 핸들
type PetnowCamera = {
  state: PetnowCameraState;
  startDetection(): void;   // 검출 시작 / 재촬영
  pauseDetection(): void;   // 일시정지(진행률 보존)
  resumeDetection(): void;  // 재개
  switchCamera(): void;     // 전/후면 전환
  retry(): void;            // 네이티브 초기화 재시도
};

type PetnowCameraState =
  | { status: 'idle' }
  | { status: 'initializing' }
  | { status: 'ready' }
  | { status: 'error'; error: string }; // error 메시지는 'error' 상태에만 존재

// onDetectionStatus 콜백 인자
type DetectionStatus =
  | { type: 'noObject' }                 // 대상 미탐지
  | { type: 'processing' }               // 탐지 진행 중
  | { type: 'detected' }                 // 탐지 성공
  | { type: 'finished' }                 // 촬영 완료
  | { type: 'failed'; reason: DetectionFailureReason };  // 실패(사유 포함)

// onDetectionFinished 콜백 인자
type CameraResult = {
  success: boolean;
  fingerprintImages: string[]; // 로컬 file:// URI
  appearanceImages: string[];  // 로컬 file:// URI
};

// onDetectionResult 콜백 인자 (프레임별 검출 박스)
type BoundingBox = { x: number; y: number; width: number; height: number }; // normalized 0–1
type DetectionResult = {
  nose: BoundingBox | null;
  face: BoundingBox | null;
};

에러 / 권한 처리

별도 에러 채널은 없습니다. 실패는 두 곳으로 표면화됩니다.

  • 초기화(라이선스 저장) 실패camera.stateerror. camera.retry()로 재시도.
  • 카메라/검출 단계 실패(잘못된 라이선스(iOS), 권한 거부, 카메라 오픈 실패, 촬영 실패) → onDetectionStatus{ type: 'failed', reason }.
function guideMessage(status: DetectionStatus | null): string {
  if (!status) return '카메라 초기화 중...';
  switch (status.type) {
    case 'failed':
      return `인식 실패: ${status.reason}`;
    case 'noObject':
      return '반려동물을 화면 중앙에 맞춰주세요';
    case 'finished':
      return '촬영 완료!';
    default:
      return '';
  }
}

권한이 거부되면 failed가 전달됩니다. 설정에서 권한을 허용하도록 안내한 뒤 <CameraView>를 다시 마운트하세요.

2계층 수명주기

카메라(뷰 수명)가 바깥 계층이고, 검출은 그 안에서 도는 하위 수명주기입니다. 다이어그램과 자세한 설명은 소개를 참고하세요.

세션 수명 자동화

카메라는 단일 하드웨어 자원인데 RN 뷰는 자주 죽고 살아나므로, 세션은 뷰가 아니라 패키지가 싱글톤으로 관리합니다. 마지막 detach 후 1.5초 grace로 전환·리마운트에서 세션을 재사용하고, captureSessionId가 바뀌면 컨트롤러·세션 소유 구조는 유지한 채 capture runtime과 detector를 새 세션 ID로 재구성합니다. 자세한 내용은 소개를 참고하세요.

유도 사운드

반려동물의 주의를 끄는 유도 사운드(PetnowSound)는 카메라와 독립적으로 재생할 수 있습니다. 사운드 목록·미리듣기·전체 API는 사운드 가이드를 참고하세요.

전체 코드

hook·ready 게이트·<CameraView>·이벤트·업로드를 한 컴포넌트로 합친 예시입니다. captureSessionId는 앱 서버가 Server API로 발급한 값을 prop으로 받습니다.

import { useCallback, useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import {
  usePetnowCamera,
  CameraView,
  type DetectionStatus,
  type CameraResult,
} from '@petify/react-native-camera-ui';

export function Scan({ apiKey, captureSessionId }: { apiKey: string; captureSessionId: string }) {
  const camera = usePetnowCamera({ apiKey });
  const [, setStatus] = useState<DetectionStatus | null>(null);

  // file:// URI를 앱 서버로 업로드 (앱 서버가 x-petnow-api-key로 Petnow에 프록시)
  const upload = useCallback(
    async (r: CameraResult) => {
      if (!r.success) return;
      const form = new FormData();
      r.fingerprintImages.forEach((uri, i) =>
        form.append('fingerprints', { uri, name: `nose_${i}.jpg`, type: 'image/jpeg' } as any),
      );
      r.appearanceImages.forEach((uri, i) =>
        form.append('appearances', { uri, name: `face_${i}.jpg`, type: 'image/jpeg' } as any),
      );
      form.append('captureSessionId', captureSessionId);
      await fetch('https://your-app-server.example.com/petnow/upload', { method: 'POST', body: form });
    },
    [captureSessionId],
  );

  // ready 전에는 CameraView를 마운트하지 않는다.
  if (camera.state.status !== 'ready') {
    return (
      <View style={styles.center}>
        <Text>카메라 준비 중… ({camera.state.status})</Text>
      </View>
    );
  }

  return (
    <CameraView
      camera={camera}
      species="DOG"
      purpose="PET_PROFILE_REGISTRATION"
      captureSessionId={captureSessionId}
      style={styles.fill}
      onDetectionStatus={setStatus}
      onDetectionFinished={upload}
    />
  );
}

const styles = StyleSheet.create({
  fill: { flex: 1 },
  center: { flex: 1, alignItems: 'center', justifyContent: 'center' },
});

다음 단계

On this page