> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ones1ght.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 시작하기

> OneS1ght SDK for Flutter (미리보기)

<Warning>
  **미리보기 — 정식 배포가 아닙니다.** Flutter SDK 는 평가용으로 제공되며 API 가 바뀔 수 있습니다.
  정식 지원 SDK 는 [iOS](/sdk/integration/ios) · [Android](/sdk/integration/android) SDK 입니다.
</Warning>

<Info>
  Flutter SDK 는 OneS1ght iOS·Android SDK 를 감싼 플러그인입니다. 측위, 구역 진입·이탈·체류 판정, 전송·재시도는
  전부 네이티브 SDK 가 하고, Dart 는 호출을 넘기고 결과를 Dart 타입으로 바꾸기만 합니다. 그래서 앱이 백그라운드로 가
  Dart 가 멈춰도 SDK 동작은 네이티브 앱과 같습니다.<br />
  Dart API 이름은 iOS SDK 를 따르고, 네이티브 콜백(`onZoneEnter`·`onPosition` 등)은 같은 이름의 `Stream` 입니다.
</Info>

## 요구사항

| 항목 | 요구사항 |
| - | - |
| Flutter | **3.44 이상** — iOS 는 Swift Package Manager 로만 붙습니다(3.44 부터 기본으로 켜져 있습니다) |
| iOS | 패키지 추가: **iOS 18.0 이상** · 측위 동작: **iOS 27.0 이상**, UWB 칩이 있는 iPhone |
| Android | 패키지 추가: `minSdk 26` · `compileSdk 37` · 측위 동작: **Android 17 (API 37) 이상**, UWB DL-TDoA 지원 기기 |
| 함께 실리는 네이티브 SDK | iOS `0.2.2` · Android `0.0.9` (버전 고정) |

측위가 안 되는 기기에서도 앱은 정상 동작하고 측위만 비활성됩니다.

SDK 가 실제로 동작하려면 아래 항목이 먼저 준비되어 있어야 합니다. 자세한 내용은
[SDK 설치 전 체크리스트](/sdk/overview#sdk-설치-전-체크리스트)를 참고해 주세요.

| 사전 준비 | 어디서 |
| - | - |
| SDK API 키 (`ock_sdk_…`) | OneS1ght 콘솔 → **모바일 SDK** |
| 건물·층·로케이터 설치 | 측위 인프라 설치 시 함께 진행 |
| 구역(Zone) | OneS1ght 콘솔 → [공간 관리](/locator/areas) |

## 설치

`pubspec.yaml` 에 git 의존성으로 추가합니다. 미리보기라 pub.dev 에는 올라가 있지 않습니다.

```yaml theme={null}
dependencies:
  ones1ght_sdk:
    git:
      url: https://github.com/onecheck-inc/OneS1ght-Flutter-SDK
      ref: v0.0.1
```

```dart theme={null}
import 'package:ones1ght_sdk/ones1ght.dart';
```

<Warning>
  `ref` 에는 **태그(`v0.0.1`)** 를 넣어 주세요. `main` 을 넣으면 저장소가 바뀔 때마다 앱 빌드가 조용히 달라집니다.
</Warning>

### iOS 설정

**Swift Package Manager 로만 붙습니다.** CocoaPods 는 지원하지 않습니다 — 측위 엔진이 동적 프레임워크라 CocoaPods 로는
앱에 실리지 않고, 앱이 실행 즉시 종료됩니다. Swift Package Manager 가 꺼져 있으면 `pod install` 이 아래 명령을 안내하며
멈춥니다.

```bash theme={null}
flutter config --enable-swift-package-manager
```

iOS 배포 타깃을 **18.0** 으로 올립니다(`ios/Runner.xcodeproj` → `IPHONEOS_DEPLOYMENT_TARGET`).

`ios/Runner/Info.plist` 에 아래 키를 넣습니다. 앞의 세 개가 없으면 권한을 요청하는 순간 앱이 종료됩니다.

```xml theme={null}
<key>NSLocationWhenInUseUsageDescription</key>
<string>매장 내 위치를 파악하는 데 사용합니다.</string>
<key>NSNearbyInteractionUsageDescription</key>
<string>UWB 정밀 측위에 사용합니다.</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>주변 측위 장비를 찾는 데 사용합니다.</string>
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
    <key>Positioning</key>
    <string>정확한 실내 위치를 계산하는 데 사용합니다.</string>
</dict>
```

<Warning>
  `NSLocationTemporaryUsageDescriptionDictionary` 안의 키는 **`Positioning`** 이어야 합니다. 다르면 iOS 가 요청을
  오류도 로그도 없이 무시합니다.
</Warning>

**iOS 27.2 이상에서는 위치 권한이 '항상' 이어야 층을 찾습니다.** `NSLocationAlwaysAndWhenInUseUsageDescription` 을 넣고,
'앱 사용 중' 허용 뒤 앱이 직접 '항상' 을 요청해 주세요(예: [`permission_handler`](https://pub.dev/packages/permission_handler)
의 `Permission.locationAlways.request()`). SDK 는 '항상' 을 요청하지 않습니다. 이유는
[iOS 연동 가이드](/sdk/integration/ios)를 참고해 주세요.

<Warning>
  `UIBackgroundModes` 에 `bluetooth-central` 은 넣지 마세요. SDK 는 백그라운드에서 측위를 멈춰 이 모드를 쓰지 않고,
  App Store 심사에서 쓰지 않는 백그라운드 모드(2.5.4)로 거절될 수 있습니다.
</Warning>

### Android 설정

`MainActivity` 를 **`FlutterFragmentActivity`** 로 바꿉니다. SDK 의 권한 요청에는 `ComponentActivity` 가 필요한데,
Flutter 기본 `FlutterActivity` 는 그렇지 않습니다.

```kotlin theme={null}
// android/app/src/main/kotlin/.../MainActivity.kt
import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity : FlutterFragmentActivity()
```

`android/app/build.gradle.kts` 에서 빌드 버전을 맞춥니다.

```kotlin theme={null}
android {
    compileSdk = 37
    defaultConfig {
        minSdk = 26
    }
}
```

측위 권한(`RANGING`·`ACCESS_FINE_LOCATION`·`ACCESS_COARSE_LOCATION`·`BLUETOOTH_SCAN`)과 `INTERNET` 은 Android SDK
매니페스트에서 자동으로 병합됩니다. 따로 넣지 않습니다.

## SDK 초기화

앱 시작 시 한 번 호출합니다. 키 검증과 테넌트 설정 수신을 함께 합니다.

```dart theme={null}
await OneS1ght.initialize(sdkKey: 'YOUR_API_KEY');
```

* 실패하면 `OneS1ghtException` 을 던집니다 — 다시 호출하면 재시도입니다.
* 성공 후 다시 호출해도 무시되고, 다른 키로 호출하면 세션을 새로 구성합니다.
* 기기 지원 여부는 여기서 보지 않습니다. 측위가 안 되는 기기에서도 건물·층·구역 조회는 됩니다.

### 환경별 API 키 활용법

OneS1ght 콘솔에서는 API 키를 개발용(Development)과 운영용(Production)으로 구분하여 발급할 수 있습니다.
키는 코드에 넣지 말고 빌드할 때 넘기는 것을 권장합니다.

```dart theme={null}
const sdkKey = String.fromEnvironment('ONES1GHT_SDK_KEY');
await OneS1ght.initialize(sdkKey: sdkKey);
```

```bash theme={null}
flutter run --dart-define=ONES1GHT_SDK_KEY=ock_sdk_…
```

## 기기 지원 확인

```dart theme={null}
switch (await OneS1ght.deviceAvailability()) {
  case DeviceAvailability.available:
    break;
  case DeviceAvailability.osVersionTooLow:
    // "OS 업데이트 후 사용할 수 있습니다"
    break;
  case DeviceAvailability.deviceNotSupported:
    // "이 기기는 실내 측위를 지원하지 않습니다"
    break;
}
```

<Note>
  Android 에서는 `deviceAvailability()` 를 **`initialize()` 다음에** 읽어 주세요. 그 전에는 UWB 칩을 확인할 수 없어
  `deviceNotSupported` 가 오고 디버그 로그에 경고가 남습니다. iOS 는 언제 읽어도 됩니다.
</Note>

## 권한 요청

```dart theme={null}
final status = await OneS1ght.requestPermission();   // 시스템 권한 창이 뜹니다
```

| 결과 | 뜻 |
| - | - |
| `PermissionStatus.authorized` | 허용됨 |
| `PermissionStatus.denied` | 거부됨 — 앱에서 다시 물을 수 없으니 설정 앱으로 안내해 주세요 |
| `PermissionStatus.unsupported` | 측위가 안 되는 기기 — 창 없이 바로 돌아옵니다 |

* 이미 답한 권한이면 창 없이 바로 돌아옵니다. `initialize` 전에도 부를 수 있습니다.
* iOS 는 Nearby Interaction 권한을 묻고, 위치 권한은 측위를 시작할 때 SDK 가 묻습니다. Android 는 UWB·정밀 위치·
  근처 기기 권한을 한 번에 묻습니다.
* Android 에서 `MainActivity` 가 `FlutterFragmentActivity` 가 아니면 코드 `unsupportedActivity` 오류가 납니다.

## 프로필 생성 및 연결

```dart theme={null}
final profileId = await OneS1ght.createProfile({'gender': 'F', 'ageGroup': '20s'});
// profileId 는 앱이 보관해 두고 다음 실행에도 씁니다.

await OneS1ght.identify(profileId: profileId);   // 측위 시작 전에 꼭 호출
```

* 속성은 자유롭게 정할 수 있습니다. 나이는 정확한 값 대신 연령대(`"20s"`)를 권장합니다.
* 로그아웃 등으로 연결을 끊을 때는 `identify(profileId: null)` 을 호출합니다.
* 조회·교체·삭제: `fetchProfile(id)` · `replaceProfile(id, attributes: …)`(전체 교체) · `deleteProfile(id)`.

## 건물·층 선택 (선택)

```dart theme={null}
final buildings = await OneS1ght.buildings();
final floors = await OneS1ght.floors(buildingId: buildings.first.id);
await OneS1ght.setFloorMap(floors.first, buildingId: buildings.first.id);
```

`setFloorMap` 을 부르지 않으면 측위 엔진이 BLE 로 층을 찾습니다. 처음 지정할 때는 `buildingId` 를 함께 넘겨 주세요
(없으면 `E3001`). `setFloorMap(null)` 은 층 지정을 비웁니다.

### 엔진이 찾은 층 따라가기

```dart theme={null}
final session = await OneS1ght.floorSession();
session.onFloorDetected.listen((floorId) async {
  if (floorId == null) return;   // 층을 잃음
  final floor = await OneS1ght.floor(buildingId: buildingId, floorId: floorId);
  await OneS1ght.setFloorMap(floor, buildingId: buildingId);
});
```

### 도면 표시

`floors()` 목록에는 도면 이미지가 비어 있습니다. 지도를 그릴 층만 `floor(buildingId:, floorId:)` 로 받으면
`image`(원본 바이트)가 채워져 옵니다. 좌표는 도면 로컬 미터이며 `originX`·`originY`·`widthM`·`heightM` 으로 화면 좌표로
바꿉니다.

```dart theme={null}
final floor = await OneS1ght.floor(buildingId: buildingId, floorId: floorId);
if (floor.image != null) Image.memory(floor.image!);
```

## 측위 시작과 종료

```dart theme={null}
final session = await OneS1ght.floorSession();   // 항상 같은 인스턴스
await session.begin();   // 매장 진입 시
// ...
await session.end();     // 종료 + 남은 좌표 전송. 초기화·층 설정은 유지
```

`begin()` 이 실패하면 `OneS1ghtException` 을 던집니다 — `E1001`(초기화 안 함) · `E1004`(`identify` 안 함) ·
`E2001`(OS 낮음) · `E2002`(기기 미지원).

### 일시정지와 재개

```dart theme={null}
await session.pause();    // 좌표 표시·수집·판정만 멈춤, 엔진은 층을 계속 잡고 있음
await session.resume();   // 바로 이어짐
```

### 측위가 닫혔을 때

```dart theme={null}
session.onStopped.listen((reason) {
  if (reason == StopReason.engineFailed) {
    // 권한·Bluetooth 등 원인을 풀고 begin() 으로 다시 엽니다
  }
});
```

## 이벤트 수신

이벤트는 모두 `Stream` 입니다. 여러 곳에서 동시에 들어도 됩니다.

```dart theme={null}
session.onZoneEnter.listen((zone) => debugPrint('IN  ${zone.name}'));
session.onZoneExit.listen((zone) => debugPrint('OUT ${zone.name}'));
session.onZoneDwell.listen((d) => debugPrint('DWELL ${d.zone.name} ${d.seconds}s'));
session.onPosition.listen((c) => debugPrint('${c.x}, ${c.y}'));   // 도면 로컬 미터
session.onTriggers.listen((t) {
  for (final trigger in t.triggers) {
    // trigger.type: signage · coupon · tracking · merch · generic (새 값이 늘 수 있습니다)
  }
});
```

### 구역정보 업데이트

```dart theme={null}
final zones = await OneS1ght.refreshZones();   // 현재 층의 구역만 다시 받음(도면 재다운로드 없음)
```

### 콘솔 변경 수신

```dart theme={null}
session.onConfigChanged.listen((change) {
  switch (change) {
    case ZonesChanged() || ResyncNeeded():
      scheduleZoneRefresh();      // 1초 정도 모았다가 OneS1ght.refreshZones() 한 번
    case PlanChanged(:final floorId):
      redrawFloor(floorId);       // floor() 로 다시 받아 지도를 다시 그림
    case RulesChanged(:final zoneId):
      recheckZoneEvents(zoneId);  // 지금 들어가 있는 구역이면 그 구역의 이벤트를 한 번 다시 조회
    default:
      break;
  }
});
```

<Warning>
  SDK 는 이 신호로 아무것도 하지 않습니다. 구역을 다시 받을 때마다 진출입 판정이 처음부터 시작되므로, 연달아 오는
  `ZonesChanged` 는 접어서 한 번만 처리해 주세요.
</Warning>

## 데이터 전송 제어

```dart theme={null}
await OneS1ght.uploadPendingPositions();    // 쌓인 좌표를 지금 전송
await OneS1ght.discardPendingPositions();   // 쌓인 좌표를 보내지 않고 버림
```

## 세션 재설정

```dart theme={null}
await OneS1ght.reset();   // 세션을 버림 — 다른 키로 initialize 할 수 있음
```

## 오류 처리

모든 실패는 `OneS1ghtException` 입니다. `code` 는 네이티브 SDK 의 [에러 코드](/sdk/faq/error-code)이며 iOS·Android 가
같습니다.

```dart theme={null}
try {
  await session.begin();
} on OneS1ghtException catch (e) {
  switch (e.code) {
    case 'E1004':
      await OneS1ght.identify(profileId: savedProfileId);   // 프로필 연결 후 다시 시작
    case 'E2001' || 'E2002':
      showUnsupportedNotice();                              // 측위 불가 기기 안내
    default:
      debugPrint('${e.code} ${e.message}');
  }
}
```

| 필드 | 내용 |
| - | - |
| `code` | `E1001` 같은 SDK 에러 코드. Flutter 플러그인 자체 오류는 `unsupportedActivity`(Android Activity 설정) · `internal` |
| `kind` | `sdk`(SDK 상태) · `api`(서버 응답) · `plugin`(플러그인) |
| `status` · `detail` | 서버 오류일 때 HTTP 상태와 세부 사유 |

## 디버그 로그

```dart theme={null}
import 'package:flutter/foundation.dart';   // kDebugMode · debugPrint

if (kDebugMode) {
  OneS1ght.onDebugLog.listen((log) => debugPrint('OneS1ght $log'));
}
```

로그 등급과 해석은 [로그 사용법](/sdk/faq/logging)을 참고해 주세요. 운영 빌드에서는 구독하지 않는 것을 권장합니다.
SDK 로그·안내 문구의 언어는 `OneS1ght.setLanguage('ko')` 로 지정할 수 있습니다(`'ko'`·`'ja'`·`'en'`, `null` 이면 기기 언어).

## 백그라운드 동작

<Warning>
  UWB 측위는 iOS·Android 모두 **포그라운드 전용**입니다. 앱이 백그라운드로 가면 네이티브 SDK 가 측위를 멈추고 남은 좌표를
  전송합니다. 이 동작은 Dart 가 아니라 네이티브 SDK 가 하므로 Flutter 앱에서도 같습니다.
</Warning>

<Note>
  실제 측위는 시뮬레이터·에뮬레이터에서 확인할 수 없습니다. UWB 를 지원하는 실기기에 설치하여 테스트해 주세요.
</Note>

## 네이티브 SDK 와 다른 점

| 항목 | 네이티브 SDK | Flutter |
| - | - | - |
| 이벤트 | 콜백 속성(`session.onZoneEnter = …`) | 같은 이름의 `Stream`(`session.onZoneEnter.listen(…)`) |
| 상태 값 | 속성(`OneS1ght.isInitialized`) | `Future` 를 돌려주는 함수(`await OneS1ght.isInitialized()`) |
| 초기화 | Android 는 `Context` 를 함께 넘김 | 플러그인이 넘겨 줌 — `initialize(sdkKey:)` 만 |
| 권한 | Android 는 `requestPermission(activity)` | 플러그인이 Activity 를 넘겨 줌 — `requestPermission()` 만 |
| 지원하지 않는 것 | — | 커스텀 측위 provider(`begin(provider:)`) |

## 문제 해결하기

| 증상 | 원인과 해결 |
| - | - |
| `pod install` 이 OneS1ght 안내 문구와 함께 멈춤 | Swift Package Manager 가 꺼져 있습니다. `flutter config --enable-swift-package-manager` 후 다시 빌드 |
| `requestPermission()` 이 `unsupportedActivity` | Android `MainActivity` 를 `FlutterFragmentActivity` 로 변경 |
| Android 에서 지원 기기인데 `deviceNotSupported` | `initialize()` 전에 읽었습니다. 초기화 후 다시 읽어 주세요 |
| iOS 27.2 이상에서 층을 못 찾음(`E3007`) | 위치 권한 '항상' 이 필요합니다([iOS 설정](#ios-설정)) |

<CardGroup cols={2}>
  <Card title="로그 사용법" icon="wrench" href="/sdk/faq/logging">
    개발 중 로그 사용법을 안내합니다.
  </Card>

  <Card title="에러 코드 안내" icon="triangle-exclamation" href="/sdk/faq/error-code">
    개발 중 발생하는 오류 내용을 안내합니다.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.