<!-- Canonical: https://docs.shopstory.live/docs/reference/mobile-sdk-flutter -->

# Flutter SDK reference

Пара: `shopstory_player_flutter 0.2.0-preview.3` и native `ShopStoryPlayerSDK 0.2.0-preview.2`. Описание сверено с immutable tags. Установка, toolchain и starter: [Flutter на iOS](<https://docs.shopstory.live/docs/guide/mobile-sdk-flutter>). Android и Flutter Web в этот adapter contract не входят.

## Entry point и конфигурация

Используйте `ShopStoryPlayerFlutter.instance`. Перед конфигурацией проверяйте `isSupportedPlatform`: он возвращает `true` только на iOS вне Web. На других платформах методы конфигурации и player завершаются `UnsupportedError`.

```dart
await ShopStoryPlayerFlutter.instance.configure(
  applicationId: applicationId,
  hostAppVersion: hostAppVersion,
  isEnabled: true,
  followMePlayerEnabled: true,
  manualMinimizeEnabled: true,
);
```

`applicationId` обязателен. `hostAppVersion` — необязательная строка; три boolean-параметра по умолчанию равны `true`. Это локальные разрешения; remote policy может дополнительно запретить SDK UI. Успешное завершение `configure` не подтверждает доступность видео. Policy, её cache и refresh принадлежат native SDK.

## Публичные методы

| API | Результат | Назначение |
| --- | --- | --- |
| `configure(...)` | `Future<void>` | Конфигурация native SDK |
| `setCurrentSurface(surface)` | `Future<void>` | Передача реально видимого commerce-экрана |
| `showProductDetailPreview(feedProductId: ...)` | `Future<ShopStoryProductStreamResult>` | Низкоуровневый запрос и показ preview; заменяет предыдущую preview session |
| `clearProductDetailPreview()` | `Future<void>` | Снятие активного preview |
| `closePlayer()` | `Future<void>` | Закрытие SDK player surfaces |
| `registerProductActionHandlers(...)` | `ShopStoryProductActionHandlerRegistration` | Регистрация host callbacks для product actions |
| `lifecycleEvents` | `Stream<ShopStoryLifecycleEvent>` | Поток локальной диагностики |

Для обычной PDP используйте `ShopStoryProductDetailScope`: он управляет preview session за widget. Не дублируйте его запрос вызовом `showProductDetailPreview` для того же экрана.

## ShopStoryProductDetailScope

```dart
ShopStoryProductDetailScope(
  feedProductId: product.feedProductId,
  onResult: handlePreviewResult,
  onError: reportPreviewError,
  child: ProductDetailView(product: product),
)
```

`ProductDetailView`, `handlePreviewResult` и `reportPreviewError` принадлежат приложению. `feedProductId` и `child` обязательны; `player`, `onResult` и `onError` необязательны.

Scope запрашивает preview после первого frame, обновляет его при смене товара и снимает свою session при dispose. Освобождение старого scope не должно снимать preview нового. Scope не вызывает `setCurrentSurface`: видимый route по-прежнему сообщает host app.

### Результаты preview

| Тип | Поле | Действие приложения |
| --- | --- | --- |
| `ShopStoryProductStreamActive` | `streamId` | Сохранить ID для диагностики; SDK показывает preview |
| `ShopStoryProductStreamUnavailable` | `reason` | Оставить PDP без SDK placeholder |
| `ShopStoryProductStreamFailure` | `error` | Сохранить рабочую PDP и записать техническую причину |

Unavailable reasons: `noActiveStream`, `scheduled`, `archived`, `disabled`, `notEligible`. Эти варианты существуют в SDK для разбора состояния; опубликованный HTTP-контракт сейчас возвращает `no_active_stream` для недоступного видео.

Technical errors: `sdkNotConfigured`, `runtimePolicyDisabled`, `invalidProductIdentifier`, `networkFailure`, `invalidResponse`, `decodingFailed`, `requestSuperseded`, `presentationUnavailable`. Первые семь соответствуют [native lookup errors](<https://docs.shopstory.live/docs/reference/mobile-sdk-ios#%D0%BE%D1%88%D0%B8%D0%B1%D0%BA%D0%B8-lookup>). `presentationUnavailable` означает, что native UI не удалось представить; проверьте текущий экран и lifecycle приложения.

Типизированный `ShopStoryProductStreamFailure` приходит через результат. Ошибки вызова или platform channel обрабатываются через `onError`; без этого callback scope использует `FlutterError.reportError`.

## Navigation и surface

`ShopStorySurface` содержит `home`, `liveList`, `catalog`, `search`, `productDetail`, `cart`, `checkout`, `payment`, `auth`, `profile`, `unknown`.

`ShopStoryNavigatorObserver(surfaceForRoute: ...)` поддерживает один root `Navigator`. Callback получает `PageRoute<dynamic>` и возвращает `ShopStorySurface?`:

- обычный экран — его surface;
- неизвестный экран — `ShopStorySurface.unknown`;
- page-style overlay, сохраняющий нижележащий commerce-экран, — `null`.

Observer обрабатывает push, pop, replace и remove, пропускает pageless overlays и подавляет повтор одинаковой surface. Необязательный `onError(Object, StackTrace)` получает ошибки mapping и channel; без него используется `FlutterError`.

Для declarative router, tabs и nested navigators вызывайте `setCurrentSurface` из существующего owner видимого состояния. Observer не определяет бизнес-смысл маршрутов за приложение.

## Product actions

`ShopStoryProductAction` содержит `kind` и `payload`. Kind: `openProduct`, `addProduct`, `unknown`. Payload содержит необязательные `name`, `feedProductId`, `feedProductGroupId`, `url`, `vendorCode`, `source`. Строковый `feedProductId` должен совпадать с ID в каталоге и фиде; `url` не управляет native routing.

Регистрация принимает `onOpenProduct` и `onAddProduct`. Нужен хотя бы один callback; иначе возникает `ArgumentError`. Callback имеет тип `FutureOr<ShopStoryProductActionResult> Function(ShopStoryProductAction)`.

| Результат callback | Когда возвращать |
| --- | --- |
| `ShopStoryProductActionOpenedProduct()` | Router принял переход на native PDP; adapter запускает handoff к Follow Me |
| `ShopStoryProductActionSucceeded()` | Host подтвердил действие без открытия PDP, например успешную cart operation |
| `ShopStoryProductActionFailed(reason)` | Действие отклонено или не выполнено |

Не ожидайте завершения `Navigator.push`: его Future обычно завершается после возврата с открытой страницы. Результат `OpenedProduct` возвращается после принятого перехода.

Failure reasons: `invalidPayload`, `unsupportedAction`, `unavailable`, `rejected`, `operationFailed`. Неизвестное действие или отсутствие нужного handler не разрешает переход и изменение корзины. У `onAddProduct` нет автоматического fallback на `onOpenProduct`.

Root integration owner удерживает регистрацию и вызывает `dispose()` при освобождении. Новая регистрация заменяет callbacks для будущих действий; уже начавшееся действие использует прежний callback.

### Timeout и результат корзины

iOS plugin ограничивает срок ожидания product action 30 секундами. Истечение этого срока не отменяет cart operation магазина. При потерянном ответе сначала выясните её результат по operation ID; не повторяйте запись только из-за timeout.

Результат Flutter callback управляет host-side lifecycle adapter. Он не передаётся embedded player как подтверждение корзины. Merchant UI и аналитика магазина используют подтверждённый ответ Cart API независимо от состояния кнопки player.

## Lifecycle diagnostics

```dart
final subscription = ShopStoryPlayerFlutter.instance.lifecycleEvents.listen(
  (event) => reportShopStoryDiagnostic(event.kind, event.rawKind),
);

// При освобождении integration owner:
await subscription.cancel();
```

`reportShopStoryDiagnostic` — функция приложения. `kind` содержит типизированную категорию, `rawKind` сохраняет исходное имя, включая неизвестное новой версии. Необязательный `message` предназначен только для согласованной диагностики; не показывайте его пользователю и не отправляйте целиком в стороннюю аналитику.

| Группа | Примеры `ShopStoryLifecycleEventKind` | Реакция |
| --- | --- | --- |
| Player | `playerOpened`, `playerRestored`, `playerClosed`, `pageLoaded` | Наблюдать lifecycle; `pageLoaded` не подтверждает video frame |
| Background | `backgroundPaused`, `foregroundRestored` | Сверить видимую surface и текущую policy |
| Follow Me | `followMeShown`, `followMeHidden`, `followMeRestored`, `followMeSuppressed`, `followMeUnavailable` | Проверить route, контейнер и разрешение policy |
| Preview | `productPreviewShown`, `productPreviewHidden`, `presentationUnavailable` | Проверить актуальный товар и экран |
| Ошибки | `navigationFailed`, `playerLoadFailed`, `navigationBlocked`, `playerLoadBlocked`, `bridgeMessageBlocked`, `runtimePolicyDisabled` | Сохранить очищенную причину; не обходить origin и bridge checks |
| Product action | `productActionSuccess`, `productActionFailure`, `productActionTimeout`, `productActionIgnored`, `productActionDecodeFailed`, `callbackError` | Проверить handler и подтверждённый результат магазина |

Lifecycle diagnostics не заменяют [события аналитики](<https://docs.shopstory.live/docs/reference/events-metrics>). Для несовместимого bridge adapter может вызвать `ShopStoryBridgeContractException`; проверьте согласованную пару Flutter/native tags и чистую сборку.

## Migration notes

- `preview.2` → `preview.3`: root `Navigator` может использовать `ShopStoryNavigatorObserver` вместо собственного observer. Сохраните route-to-surface mapping; для tabs и nested navigation остаётся явный `setCurrentSurface`.
- `preview.1` → `preview.2`: обновите обе зависимости до соответствующих exact tags и lockfiles. Native entrypoint перестал раскрывать player URL; Dart API этой пары не изменился.

После обновления выполните [приёмку перед выпуском](<https://docs.shopstory.live/docs/guide/mobile-sdk-releases#%D0%BF%D1%80%D0%B8%D1%91%D0%BC%D0%BA%D0%B0-%D0%BF%D0%B5%D1%80%D0%B5%D0%B4-%D0%B2%D1%8B%D0%BF%D1%83%D1%81%D0%BA%D0%BE%D0%BC>). При проблеме используйте [диагностику Mobile SDK](<https://docs.shopstory.live/docs/operations/troubleshooting#mobile>).
