> ## 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.

# Getting Started

> OneS1ght SDK for Flutter (Preview)

<Warning>
  **Preview — not an official release.** The Flutter SDK is provided for evaluation and its API may change.
  The supported SDKs are the [iOS](/en/sdk/integration/ios) and [Android](/en/sdk/integration/android) SDKs.
</Warning>

<Info>
  The Flutter SDK is a plugin that wraps the OneS1ght iOS and Android SDKs. Positioning, zone enter / exit / dwell
  judgement, upload and retries all run in the native SDK; Dart only forwards calls and turns the results into Dart
  types. So when the app goes to the background and Dart pauses, the SDK behaves exactly as in a native app.<br />
  The Dart API follows the iOS SDK names, and native callbacks (`onZoneEnter`, `onPosition`, …) are `Stream`s with the
  same names.
</Info>

## Requirements

| Item | Requirement |
| - | - |
| Flutter | **3.44 or later** — iOS is added only through Swift Package Manager (on by default since 3.44) |
| iOS | Adding the package: **iOS 18.0 or later** · Positioning: **iOS 27.0 or later**, an iPhone with a UWB chip |
| Android | Adding the package: `minSdk 26` · `compileSdk 37` · Positioning: **Android 17 (API 37) or later**, a device that supports UWB DL-TDoA |
| Bundled native SDKs | iOS `0.2.2` · Android `0.0.9` (pinned) |

On devices that cannot position, the app keeps working and only positioning stays inactive.

Before the SDK can actually work, the following must be in place. See the
[checklist before installing the SDK](/en/sdk/overview#checklist-before-installing-the-sdk) for details.

| Prerequisite | Where |
| - | - |
| SDK API key (`ock_sdk_…`) | OneS1ght console → **Mobile SDK** |
| Buildings, floors and locators | Set up together with the positioning infrastructure |
| Zones | OneS1ght console → [Space management](/en/locator/areas) |

## Installation

Add it to `pubspec.yaml` as a git dependency. As a preview, it is not published to 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>
  Put a **tag (`v0.0.1`)** in `ref`. With `main`, your app build silently changes every time the repository changes.
</Warning>

### iOS setup

**Swift Package Manager only.** CocoaPods is not supported — the positioning engine ships as dynamic frameworks that
CocoaPods does not embed, and the app would crash at launch. If Swift Package Manager is off, `pod install` stops and
tells you to run:

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

Raise the iOS deployment target to **18.0** (`ios/Runner.xcodeproj` → `IPHONEOS_DEPLOYMENT_TARGET`).

Add these keys to `ios/Runner/Info.plist`. Without the first three, the app crashes as soon as the permission is
requested.

```xml theme={null}
<key>NSLocationWhenInUseUsageDescription</key>
<string>Used to find your position in the store.</string>
<key>NSNearbyInteractionUsageDescription</key>
<string>Used for precise UWB positioning.</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Used to find nearby positioning devices.</string>
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
    <key>Positioning</key>
    <string>Used to calculate your precise indoor position.</string>
</dict>
```

<Warning>
  The key inside `NSLocationTemporaryUsageDescriptionDictionary` must be **`Positioning`**. Otherwise iOS ignores the
  request with no error and no log.
</Warning>

**From iOS 27.2, location permission must be "Always" for the floor to be found.** Add
`NSLocationAlwaysAndWhenInUseUsageDescription` and, once "While Using" is granted, request "Always" from your app
(for example with `Permission.locationAlways.request()` from [`permission_handler`](https://pub.dev/packages/permission_handler)).
The SDK does not request it. See the [iOS integration guide](/en/sdk/integration/ios) for why.

<Warning>
  Do not add `bluetooth-central` to `UIBackgroundModes`. The SDK stops positioning in the background and does not use
  this mode, and App Review can reject unused background modes (2.5.4).
</Warning>

### Android setup

Change `MainActivity` to extend **`FlutterFragmentActivity`**. The SDK's permission request needs a
`ComponentActivity`, and Flutter's default `FlutterActivity` is not one.

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

class MainActivity : FlutterFragmentActivity()
```

Match the build versions in `android/app/build.gradle.kts`.

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

The positioning permissions (`RANGING`, `ACCESS_FINE_LOCATION`, `ACCESS_COARSE_LOCATION`, `BLUETOOTH_SCAN`) and
`INTERNET` are merged automatically from the Android SDK's manifest. You do not add them yourself.

## Initializing the SDK

Call it once at app start. It validates the key and loads the tenant settings.

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

* On failure it throws `OneS1ghtException` — calling it again retries.
* Calling it again after success is ignored; calling it with a different key rebuilds the session.
* Device support is not checked here. Buildings, floors and zones can be read even on devices that cannot position.

### Using API keys per environment

The OneS1ght console issues API keys for Development and Production separately. We recommend passing the key at
build time instead of writing it in code.

```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_…
```

## Checking device support

```dart theme={null}
switch (await OneS1ght.deviceAvailability()) {
  case DeviceAvailability.available:
    break;
  case DeviceAvailability.osVersionTooLow:
    // "Available after an OS update"
    break;
  case DeviceAvailability.deviceNotSupported:
    // "This device does not support indoor positioning"
    break;
}
```

<Note>
  On Android, read `deviceAvailability()` **after `initialize()`**. Before that the UWB chip cannot be checked, so it
  returns `deviceNotSupported` and leaves a warning in the debug log. On iOS you can read it at any time.
</Note>

## Requesting permissions

```dart theme={null}
final status = await OneS1ght.requestPermission();   // shows the system permission dialog
```

| Result | Meaning |
| - | - |
| `PermissionStatus.authorized` | Granted |
| `PermissionStatus.denied` | Denied — the app cannot ask again, so send the user to Settings |
| `PermissionStatus.unsupported` | The device cannot position — returns right away without a dialog |

* If the permission was already answered, it returns right away without a dialog. You can call it before `initialize`.
* iOS asks for Nearby Interaction permission; the location permission is requested by the SDK when positioning
  starts. Android asks for UWB, precise location and nearby devices at once.
* On Android, if `MainActivity` is not a `FlutterFragmentActivity`, it fails with code `unsupportedActivity`.

## Creating and connecting a profile

```dart theme={null}
final profileId = await OneS1ght.createProfile({'gender': 'F', 'ageGroup': '20s'});
// Keep profileId in your app and reuse it on later launches.

await OneS1ght.identify(profileId: profileId);   // required before starting positioning
```

* Attributes are up to you. For age, we recommend an age group (`"20s"`) instead of an exact value.
* To disconnect (for example on logout), call `identify(profileId: null)`.
* Read, replace, delete: `fetchProfile(id)` · `replaceProfile(id, attributes: …)` (full replace) · `deleteProfile(id)`.

## Selecting a building and floor (optional)

```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);
```

Without `setFloorMap`, the positioning engine finds the floor over BLE. Pass `buildingId` the first time (otherwise
`E3001`). `setFloorMap(null)` clears the floor.

### Following the floor the engine finds

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

### Displaying the floor plan

The `floors()` list leaves the floor plan image empty. Fetch only the floor you draw with
`floor(buildingId:, floorId:)` and `image` (raw bytes) is filled in. Coordinates are floor-local metres; convert them
to screen coordinates with `originX`, `originY`, `widthM` and `heightM`.

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

## Starting and stopping positioning

```dart theme={null}
final session = await OneS1ght.floorSession();   // always the same instance
await session.begin();   // when entering the store
// ...
await session.end();     // stop + upload remaining positions. Initialization and floor are kept
```

If `begin()` fails it throws `OneS1ghtException` — `E1001` (not initialized) · `E1004` (`identify` not called) ·
`E2001` (OS too low) · `E2002` (device not supported).

### Pausing and resuming

```dart theme={null}
await session.pause();    // stops only display, collection and judgement; the engine keeps the floor
await session.resume();   // continues right away
```

### When positioning closes

```dart theme={null}
session.onStopped.listen((reason) {
  if (reason == StopReason.engineFailed) {
    // Fix the cause (permission, Bluetooth, …) and open it again with begin()
  }
});
```

## Receiving events

All events are `Stream`s. You can listen from several places at once.

```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}'));   // floor-local metres
session.onTriggers.listen((t) {
  for (final trigger in t.triggers) {
    // trigger.type: signage · coupon · tracking · merch · generic (new values may be added)
  }
});
```

### Refreshing zone data

```dart theme={null}
final zones = await OneS1ght.refreshZones();   // re-reads only the current floor's zones (no floor plan download)
```

### Receiving console changes

```dart theme={null}
session.onConfigChanged.listen((change) {
  switch (change) {
    case ZonesChanged() || ResyncNeeded():
      scheduleZoneRefresh();      // collect for about 1 s, then OneS1ght.refreshZones() once
    case PlanChanged(:final floorId):
      redrawFloor(floorId);       // fetch floor() again and redraw the map
    case RulesChanged(:final zoneId):
      recheckZoneEvents(zoneId);  // if the user is in that zone, query its events once more
    default:
      break;
  }
});
```

<Warning>
  The SDK does nothing with this signal. Every zone reload restarts enter / exit judgement from scratch, so collapse
  bursts of `ZonesChanged` and handle them once.
</Warning>

## Controlling data upload

```dart theme={null}
await OneS1ght.uploadPendingPositions();    // upload buffered positions now
await OneS1ght.discardPendingPositions();   // drop buffered positions without uploading
```

## Resetting the session

```dart theme={null}
await OneS1ght.reset();   // drops the session — you can initialize with another key
```

## Handling errors

Every failure is a `OneS1ghtException`. `code` is the native SDK's [error code](/en/sdk/faq/error-code) and is the
same on iOS and Android.

```dart theme={null}
try {
  await session.begin();
} on OneS1ghtException catch (e) {
  switch (e.code) {
    case 'E1004':
      await OneS1ght.identify(profileId: savedProfileId);   // connect the profile, then start again
    case 'E2001' || 'E2002':
      showUnsupportedNotice();                              // tell the user the device cannot position
    default:
      debugPrint('${e.code} ${e.message}');
  }
}
```

| Field | Content |
| - | - |
| `code` | SDK error code such as `E1001`. Errors from the Flutter plugin itself are `unsupportedActivity` (Android Activity setup) · `internal` |
| `kind` | `sdk` (SDK state) · `api` (server response) · `plugin` (the plugin) |
| `status` · `detail` | HTTP status and detail for server errors |

## Debug logs

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

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

See [How to use logs](/en/sdk/faq/logging) for log levels and how to read them. We recommend not subscribing in
production builds. The language of SDK logs and messages can be set with `OneS1ght.setLanguage('en')`
(`'ko'`, `'ja'`, `'en'`; `null` follows the device language).

## Background behavior

<Warning>
  UWB positioning is **foreground only** on both iOS and Android. When the app goes to the background, the native SDK
  stops positioning and uploads the remaining positions. This is done by the native SDK, not Dart, so it is the same in
  Flutter apps.
</Warning>

<Note>
  Actual positioning cannot be checked on simulators or emulators. Install on a real device that supports UWB to test.
</Note>

## Differences from the native SDKs

| Item | Native SDKs | Flutter |
| - | - | - |
| Events | Callback properties (`session.onZoneEnter = …`) | `Stream`s with the same names (`session.onZoneEnter.listen(…)`) |
| State values | Properties (`OneS1ght.isInitialized`) | Functions returning a `Future` (`await OneS1ght.isInitialized()`) |
| Initialization | Android also takes a `Context` | The plugin passes it — just `initialize(sdkKey:)` |
| Permission | Android takes `requestPermission(activity)` | The plugin passes the Activity — just `requestPermission()` |
| Not supported | — | Custom positioning providers (`begin(provider:)`) |

## Troubleshooting

| Symptom | Cause and fix |
| - | - |
| `pod install` stops with a OneS1ght message | Swift Package Manager is off. Run `flutter config --enable-swift-package-manager` and rebuild |
| `requestPermission()` fails with `unsupportedActivity` | Change Android `MainActivity` to `FlutterFragmentActivity` |
| `deviceNotSupported` on a supported Android device | It was read before `initialize()`. Read it again after initializing |
| Floor not found on iOS 27.2+ (`E3007`) | "Always" location permission is required ([iOS setup](#ios-setup)) |

<CardGroup cols={2}>
  <Card title="How to use logs" icon="wrench" href="/en/sdk/faq/logging">
    How to use logs during development.
  </Card>

  <Card title="Error codes" icon="triangle-exclamation" href="/en/sdk/faq/error-code">
    Errors that can occur during development.
  </Card>
</CardGroup>


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