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

# Flutter Setup Guide

## System requirements

| Item | Requirement |
| - | - |
| Flutter | 3.44 or later (iOS can only be installed through Swift Package Manager) |
| iOS | iOS 27 or later · iPhone with UWB (minimum app target is iOS 18) |
| Android | Android 17 (API 37) or later · device with UWB DL-TDoA support (minimum app target is API 26) |
| Native SDKs | iOS `0.2.2` · Android `0.0.9` (pinned by the plugin) |
| Language | Dart |

On devices that cannot receive positioning data, the SDK stops itself and does not affect the rest of your app.

## Installing the SDK

**Step 1**

Add the package from [pub.dev](https://pub.dev/packages/ones1ght_sdk).

```bash theme={null}
flutter pub add ones1ght_sdk
```

To add it manually, put it under dependencies in `pubspec.yaml`.

```yaml pubspec.yaml theme={null}
dependencies:
  ones1ght_sdk: ^0.0.4
```

**Step 2**

Import it in your Dart code.

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
import 'package:ones1ght_sdk/ones1ght_sdk.dart';
```

## iOS setup

**1. Enable Swift Package Manager**

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

**2. Raise the deployment target**

Raise `IPHONEOS_DEPLOYMENT_TARGET` in `ios/Runner.xcodeproj` to **18.0**.

**3. Add the usage descriptions**

Add all five usage descriptions below to `ios/Runner/Info.plist`. None of them can be left out. For how to add them, see the [iOS Setup Guide](/en/developers/sdk/integration/ios#ios-permissions).

| Key | Permission | Purpose |
| - | - | - |
| `NSBluetoothAlwaysUsageDescription` | Bluetooth | Receiving locator BLE signals |
| `NSLocationWhenInUseUsageDescription` | Location | Determining the locator's location |
| `NSLocationAlwaysAndWhenInUseUsageDescription` | Location (Always) | Determining the locator's location (iOS 27.2 beta or later) |
| `NSLocationTemporaryUsageDescriptionDictionary` | Precise location | Computing the precise indoor position The key inside must be `Positioning` |
| `NSNearbyInteractionUsageDescription` | Nearby Interaction | Precise positioning of the user |

```xml Info.plist theme={null}
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Used to find nearby positioning devices.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Used to determine your indoor location.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Used on iOS 27.2 and later to find the floor of the building you are on.</string>
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
    <key>Positioning</key>
    <string>Used to compute your precise indoor position.</string>
</dict>
<key>NSNearbyInteractionUsageDescription</key>
<string>Used for precise UWB positioning.</string>
```

On iOS 27.2 and later, the location permission must be **'Always'** for the floor to be found. The SDK does not request 'Always', so after 'While Using the App' has been granted, request it from the app yourself.

```dart theme={null}
// Import permission_handler with a prefix — its PermissionStatus name clashes with the SDK's.
import 'package:permission_handler/permission_handler.dart' as ph;

await ph.Permission.locationAlways.request();
```

## Android setup

**1. Change MainActivity**

The SDK's permission request needs a `ComponentActivity`, so change `MainActivity` to **`FlutterFragmentActivity`**.

```kotlin MainActivity.kt icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/kotlin.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=3721cc48a6f44aef50ea7a0151e369f2" theme={null}
import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity : FlutterFragmentActivity()
```

**2. Match the build versions**

```kotlin android/app/build.gradle.kts icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/kotlin.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=3721cc48a6f44aef50ea7a0151e369f2" theme={null}
android {
    compileSdk = 37
    defaultConfig {
        minSdk = 26
    }
}
```

Positioning permissions are merged automatically from the Android SDK manifest.

## Initializing the SDK

Runs the initialization needed to start the positioning engine.

| Function · Property | Description | Returns |
| - | - | - |
| `initialize(sdkKey:)` | Validates the key and fetches the configuration. Once at app launch | `Future<void>` |
| `isInitialized()` | Whether initialization succeeded | `Future<bool>` |
| `sdkVersion()` | SDK version | `Future<String>` |
| `pluginVersion` | Plugin version | `String` (e.g. `'0.0.4'`) |

Every call returns a `Future`, and events are `Stream`s.

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
const sdkKey = String.fromEnvironment('ONES1GHT_SDK_KEY');   // flutter run --dart-define=ONES1GHT_SDK_KEY=…
await OneS1ght.initialize(sdkKey: sdkKey);
```

## Profiles

Each user (account or device) is tracked as a **profile**. \

Profile IDs are unique and cannot be recovered once lost, so store them carefully.

| Function · Property | Description | Returns |
| - | - | - |
| `createProfile(attributes)` | Creates a profile from attributes | `Future<String>` (profile ID) |
| `identify(profileId:)` | Links this device's positioning to the profile.  Pass `null` to unlink | `Future<void>` |
| `fetchProfile(profileId)` | Fetches attributes by profile ID | `Future<Map<String, String>>` |
| `replaceProfile(profileId, attributes:)` | Replaces all attributes | `Future<void>` |
| `deleteProfile(profileId)` | Deletes the profile | `Future<void>` |

### Creating a profile

Creating a profile returns its profile ID. Attributes are used as segments in reports and metrics, and there are no restrictions on the key-value pairs.\

Store the returned ID in your app (or on your server) and link it with `identify` before positioning on every launch.

> [!WARNING]
>
> Do not put sensitive data such as names, phone numbers or member IDs in profile attributes.\\
>
> Use only non-identifying values such as an age band (`"20s"`).

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
final profileId = await OneS1ght.createProfile({'gender': 'F', 'ageBand': '20s'});   // create a profile → returns the profile ID
save(profileId);                                                                    // store in the app or on your server

await OneS1ght.identify(profileId: savedProfileId);   // link before positioning on every launch; null on logout
```

### Fetching a profile

Fetches the personalization attributes stored for a profile ID.

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
final attributes = await OneS1ght.fetchProfile(profileId);   // {gender: F, ageBand: 20s}
```

### Updating a profile

Personalization attributes are **replaced as a whole**. The previous values are deleted and cannot be restored.

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
await OneS1ght.replaceProfile(profileId, attributes: {'gender': 'F', 'ageBand': '30s'});   // replace all attributes
```

### Deleting a profile

Deletes the profile's personalization attributes. Deleted attributes cannot be restored.

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
await OneS1ght.deleteProfile(profileId);
```

## Selecting a building and floor

To receive position coordinates, select the building and floor where the locators are installed.

| Function · Property | Description | Returns |
| - | - | - |
| `floorSession()` | Positioning session | `Future<FloorSession>` |
| `session.onFloorDetected` | Emits when the engine finds a floor.  `null` when the floor is lost | `Stream<String?>` (floor ID) |
| `buildings()` | Buildings registered in the console | `Future<List<Building>>`  `id`: building ID  `name`: building name  `floorCount`: number of floors |
| `floors(buildingId:)` | Floors of a building (without floor plan image) | `Future<List<Floor>>` |
| `floor(buildingId:, floorId:)` | One floor (with floor plan image) | `Future<Floor>` |
| `setFloorMap(floor, buildingId:)` | Sets the floor  `null` to clear | `Future<void>` |
| `session.floor()` | Currently set floor | `Future<Floor?>` |

### Selecting from BLE

When the SDK's positioning engine finds a floor from locator BLE signals, the floor ID arrives in `onFloorDetected`. \

`null` arrives when the floor is lost.

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
final session = await OneS1ght.floorSession();                                   // get the positioning session
session.onFloorDetected.listen((floorId) async {                                  // the engine passes the floor ID when it finds a floor
  if (floorId == null) return;
  final floor = await OneS1ght.floor(buildingId: buildingId, floorId: floorId);   // fetch the floor
  await OneS1ght.setFloorMap(floor, buildingId: buildingId);                      // set the floor
});
```

### Selecting manually

Set the building and floor yourself when the user picks the floor, or when you want to draw the map before positioning starts.

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
final buildings = await OneS1ght.buildings();                               // buildings registered in the console
final floors    = await OneS1ght.floors(buildingId: buildings.first.id);    // floors of the building

await OneS1ght.setFloorMap(floors.first, buildingId: buildings.first.id);   // set the floor
await OneS1ght.setFloorMap(null);                                           // clear the floor
```

## Receiving live positions

Get a positioning session with `floorSession()`, start it with `begin()` and stop it with `end()`. \

Positions arrive in `onPosition` as coordinates in meters, up to 4 times per second.

| Situation | What to do |
| - | - |
| App goes to the background | Nothing — the native SDK stops positioning and restarts it automatically when the app returns to the foreground. The same applies while Dart is paused. |
| User leaves the screen briefly | Stop with `pause()` and continue with `resume()`. The engine stays on, so positioning picks up right away. |
| Stopping positioning for good | `end()`. The engine shuts down too, so the next `begin()` searches for locators from scratch. |

| Function · Property | Description | Returns |
| - | - | - |
| `session.onPosition` | Live coordinates | `Stream<Coordinates>`  `x` · `y`: meters on the floor plan  `z`: height |
| `session.begin()` | Starts positioning | `Future<void>` |
| `session.end()` | Stops positioning and uploads the remaining coordinates | `Future<void>` |
| `session.pause()` / `resume()` | Pause / resume | `Future<void>` |
| `session.isRunning()` / `isPaused()` | Whether positioning is running / paused | `Future<bool>` |

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
final session = await OneS1ght.floorSession();
session.onPosition.listen((coord) => updateMyLocationMarker(coord.x, coord.y));   // coord.z: height

await session.begin();   // start positioning
await session.end();     // stop positioning and upload the remaining coordinates
```

## Zone events

Receive enter, exit and dwell events for the zones drawn in the console's [Space Management](/en/console/areas). Detection happens on the device, so events arrive immediately. \

Campaigns attached to a zone (coupons, signage, etc.) are matched by the server and delivered through `onTriggers`.\

Every event is a `Stream`, so you can listen from several places at once.

| Function · Property | Description | Returns |
| - | - | - |
| `session.onZoneEnter` / `onZoneExit` | Zone entered / exited | `Stream<Zone>`  `id`: zone ID  `name`: zone name  `polygon`: zone shape (list of vertices)  `dwellSeconds`: dwell threshold (seconds) |
| `session.onZoneDwell` | Dwelling in a zone  Once per visit when `dwellSeconds` is reached | `Stream<ZoneDwell>`  `zone`: zone  `seconds`: dwell time (seconds) |
| `session.onTriggers` | Personalized actions matched by the server | `Stream`  `zoneId`: zone ID  `triggers`: list of triggers  (`triggerId`: trigger ID  `type`: type  `payload`: extra data per type) |
| `session.onConfigChanged` | Zone settings changed in the console | `Stream<ConfigChange>`  `ZonesChanged`: zones changed  `PlanChanged`: floor plan changed  `RulesChanged`: campaigns changed  `SdkConfigChanged`: SDK remote config changed  `ResyncNeeded`: refetch everything (after a reconnect, or when events may have been missed) |
| `refreshZones()` | Refreshes zone data | `Future<List<Zone>>` |

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
session.onZoneEnter.listen((zone) => showBanner('Entered: ${zone.name}'));
session.onZoneExit.listen((zone) => hideBanner());
session.onZoneDwell.listen((d) => showBanner('Dwell: ${d.zone.name} — ${d.seconds.toInt()}s'));
session.onTriggers.listen((t) => t.triggers.forEach(handle));   // type: signage · coupon · tracking · merch · generic
```

### Refreshing zone data

`onConfigChanged` emits when zone data changes in the console. If several arrive in a row, batch them for about a second and call `refreshZones()` once.

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
session.onConfigChanged.listen((change) {
  switch (change) {
    case ZonesChanged() || ResyncNeeded():
      OneS1ght.refreshZones();
    case PlanChanged():
      reloadFloorPlan();   // floor plan changed
    default:
      break;
  }
});
```

## Displaying the floor plan

You can display the floor plan the locators are installed on, together with the user's current position. \

If no floor plan is set, the background is shown as a grid.

| Function · Property | Description | Returns |
| - | - | - |
| `floor(buildingId:, floorId:)` | Floor details | `Future<Floor>` `image: Uint8List?`: floor plan image `hasPlan`: whether a floor plan is registered `originX`: floor plan origin X (meters) `originY`: floor plan origin Y (meters) `widthM`: width (meters) `heightM`: height (meters) `minX`: minimum X `minY`: minimum Y `maxX`: maximum X `maxY`: maximum Y |
| `locators(buildingId:, floorId:)` | Locators on the floor | `Future<FloorLocators>` `locators`: locator data (`address`: last 2 bytes of the UWB MAC (`int`), `x` · `y` · `z`: locator coordinates) `sessionId`: UWB session ID (differs per floor) `positioningReady`: whether positioning is possible |

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
final floor = await OneS1ght.floor(buildingId: buildingId, floorId: floorId);
if (floor.image != null) planWidget = Image.memory(floor.image!);
setBounds(floor.minX, floor.minY, floor.maxX, floor.maxY);
```

## Other options

| Function · Property | Description | Returns |
| - | - | - |
| `uploadPendingPositions()` | Uploads the coordinates buffered in the app right away | `Future<void>` |
| `discardPendingPositions()` | Discards the coordinates buffered in the app | `Future<void>` |
| `reset()` | Discards the session (no new session is created; call `initialize` again to continue) | `Future<void>` |
| `session.onStopped` | Session stopped | `Stream<StopReason>`: why the session stopped  `ended`: ended normally  `engineFailed`: engine failure |
| `deviceAvailability()` | Whether this device can position, and if not, why | `Future<DeviceAvailability>` — `available` · `osVersionTooLow` · `deviceNotSupported` |
| `isDeviceAvailable()` | Summary of the above | `Future<bool>` |
| `OneS1ght.onDebugLog` | SDK debugging hook | `Stream<DebugLog>` — `level` · `message` log output |

### Controlling data upload

Coordinates are uploaded automatically **when 300 have been buffered, or 60 seconds after the last upload**. If the app is force-quit, buffered coordinates are discarded.

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
await OneS1ght.uploadPendingPositions();    // upload the buffered coordinates now
await OneS1ght.discardPendingPositions();   // discard without uploading
```

### Resetting the session

Discards the current session. No new session is created; call `initialize` again to continue (a different key also works).

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
await OneS1ght.reset();
```

### Handling positioning shutdown

When the positioning engine shuts down, `onStopped` fires and you can bring the app back to a state where positioning can start again.

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
session.onStopped.listen((reason) {
  if (reason == StopReason.engineFailed) showRetrySearch();   // fix the cause, then call begin() again
});
```

### Checking device support

`deviceAvailability` can be read right away without a network (internet) connection.\

Devices that do not support positioning do not affect the rest of your app.

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
switch (await OneS1ght.deviceAvailability()) {
  case DeviceAvailability.available:
    break;
  case DeviceAvailability.osVersionTooLow:
    showNotice('Available after updating the OS');
  case DeviceAvailability.deviceNotSupported:
    hidePositioningFeatures();   // device without UWB
}
```

## Appendix

### Requesting permissions

`requestPermission()` requests every permission the SDK needs in one go. \

Once the user has denied a request, the app cannot ask again, so guide them to the Settings app.

| Function · Property | Description | Returns |
| - | - | - |
| `requestPermission()` | Requests positioning permissions | `Future<PermissionStatus>`  `authorized`: positioning available  `denied`: denied  `unsupported`: device without positioning support (no prompt is shown) |

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
switch (await OneS1ght.requestPermission()) {
  case PermissionStatus.authorized:
    break;
  case PermissionStatus.denied:
    showSettingsGuide();        // cannot ask again from the app — send the user to the Settings app
  case PermissionStatus.unsupported:
    showUnsupportedNotice();    // device without positioning support
}
```

### Debug logs

To see debug logs, register the listener **right before** calling `initialize`. \

Logs are printed in the terminal running `flutter run` or in the IDE's debug console. \

To collect them on the device itself, see the [iOS](/en/developers/sdk/integration/ios#debug-logs) and [Android](/en/developers/sdk/integration/android#debug-logs) guides.\

User positioning data cannot be checked on simulators or emulators.

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
import 'package:flutter/foundation.dart';   // kDebugMode · debugPrint

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

The language of SDK logs and messages is set with `setLanguage` — `'ko'` · `'ja'` · `'en'`; `null` follows the device language.

```dart Dart icon="https://mintcdn.com/onecheck/wv66PbaB8dD4xd9I/images/icons/dart.svg?fit=max&auto=format&n=wv66PbaB8dD4xd9I&q=85&s=02dee5dd767b4c76ef0e07bc8663c542" theme={null}
await OneS1ght.setLanguage('en');
```

### 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:)` |
| Permissions | Android uses `requestPermission(activity)` | The plugin passes the Activity — just `requestPermission()` |
| Not supported | — | Custom positioning provider (`begin(provider:)`) |

## Troubleshooting

| Symptom | Cause and fix |
| - | - |
| `pod install` stops with a OneS1ght message | Swift Package Manager is disabled.  Run `flutter config --enable-swift-package-manager` and build again |
| `requestPermission()` fails with an error on Android | Change `MainActivity` to `FlutterFragmentActivity` |
| `deviceNotSupported` on a supported Android device | It was read before `initialize()`. Read it again after initialization |

> **[Error codes](/en/developers/sdk/troubleshooting/error-codes)**
>
> Explains the errors you may run into during development.


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