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

# Flutter SDK

> 在你的 Flutter iOS / Android 应用中接入 Kontext 广告 SDK。

<Info>
  我们正在准备 Flutter SDK 的 v4 版本。下方文档对应当前正式版本——一旦 v4 发布，本页会同步更新。
</Info>

<Card title="Flutter SDK" icon="github" href="https://github.com/kontextso/sdk-flutter">
  看看用我们轻量的 SDK 接入高性能广告有多简单。
</Card>

## 环境要求

* **Flutter**：3.24.0 或更高版本
* **Dart**：3.5.0 或更高版本
* **Android**：`minSdkVersion >= 21`，`compileSdk >= 34`，[AGP](https://developer.android.com/build/releases/gradle-plugin) 版本 `>= 7.3.0`（可使用 [Android Studio 的 AGP 升级助手](https://developer.android.com/build/agp-upgrade-assistant) 协助升级），并支持 `androidx`（如需迁移已有项目，参考 [AndroidX Migration](https://flutter.dev/docs/development/androidx-migration)）
* **iOS**：`12.0+，--ios-language swift`，Xcode 版本 `>= 15.0`

## 快速开始

### WebView 前置条件（`flutter_inappwebview`）

Flutter SDK 使用 [flutter\_inappwebview](https://pub.dev/packages/flutter_inappwebview) 在 WebView 中渲染广告。为避免 WebView 初始化错误，请在应用入口添加：

```dart theme={null}
import 'package:flutter/widgets.dart';

void main() {
  // 必须先调用，确保插件已就绪。
  WidgetsFlutterBinding.ensureInitialized();

  runApp(const MyApp());
}
```

### 1. 安装

<Note>
  在开始之前，你需要先建立一个 [发布方账号](/publishers#getting-started-is-easy) 来获取 `publisherToken` 和 `code`。
</Note>

把包加入你的 `pubspec.yaml`：

```yaml theme={null}
dependencies:
  kontext_flutter_sdk: ^<latest_version>
```

安装依赖：

```bash theme={null}
flutter pub get
```

请确认项目满足上方列出的 Android min/compile SDK 与 iOS/Xcode 要求。若遇到问题，请核对插件的平台要求：[https://inappwebview.dev/docs/intro/](https://inappwebview.dev/docs/intro/)

### 2. 配置 `Character` 对象

定义助手的角色信息：

```dart theme={null}
final character = Character(
  id: 'id-123',
  name: 'Ava',
  avatarUrl: 'https://example.com/avatar.png',
  greeting: 'Hi there! How can I help you today?'
);
```

### 3. 配置 `Regulatory` 对象

接着定义用户的合规上下文：

```dart theme={null}
final regulatory = Regulatory(
  coppa: 0, 
  // ... 其他合规属性
);

```

### 4. 配置 IFA（广告标识符）

* **iOS：** 在 `ios/Runner/Info.plist` 中加入 `NSUserTrackingUsageDescription`。SDK 会自动触发 ATT 弹窗并据此读取 IDFA。
* **Android（13+）：** 在 `android/app/src/main/AndroidManifest.xml` 中声明 `com.google.android.gms.permission.AD_ID`——这是 install-time 权限，无需运行时弹窗。

完整设置见 [IFA 与 ATT](/guides/ifa)——必填 key、弹窗时机的坑，以及如何自己管理弹窗。

### 5. 配置 SKAdNetwork（仅 iOS）

请把 onboarding 时提供的 SKAdNetwork 标识符追加到 `ios/Runner/Info.plist` 中。SDK 会读取它们，并在每次 `/init` 时一并转发，以便 DSP 衡量转化。

完整指南见 [SKAdNetwork](/guides/skadnetwork)。

### 6. 配置 `AdsProvider`

把你的应用（或其中包含广告位的部分）用 `AdsProvider` 包裹起来。`AdsProvider` 负责获取与管理广告，并且必须能访问到当前聊天的 `messages`。

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

// 用户与助手之间的消息
final messages = [
  Message(
    id: 'msg-001',
    role: MessageRole.assistant,
    content: 'Hello! How can I help you today?',
    createdAt: DateTime.parse('2025-08-31T10:00:00Z'),
  ),
  Message(
    id: 'msg-002',
    role: MessageRole.user,
    content: 'Show me today's workout plan.',
    createdAt: DateTime.parse('2025-08-31T10:00:05Z'),
  ),
  Message(
    id: 'msg-003',
    role: MessageRole.assistant,
    content: 'Here's a 30-minute routine to start with.',
    createdAt: DateTime.parse('2025-08-31T10:00:10Z'),
  ),
];

Widget build(BuildContext context) {
  return AdsProvider(
    publisherToken: '<your-publisher-token>',
    userId: 'user-1234',                 
    conversationId: 'conv-5678', 
    enabledPlacementCodes: ['inlineAd'],
    messages: messages,
    character: character,  // 来自第 2 节
    regulatory: regulatory, // 来自第 3 节
    otherParams: {
      'theme': 'dark',
    },
    child: YourChatWidget(),
  );
}
```

### 7. 展示你的第一个广告

广告位（ad slot）是 UI 中专门用来渲染广告的区域，通常出现在聊天消息下方。Onboarding 时，你会拿到每个广告位的唯一 `code`。

使用 `InlineAd` 形式的示例：

```dart theme={null}
ListView.builder(
  itemCount: messages.length,
  itemBuilder: (context, index) {
    final message = messages[index];
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(message.content),
        InlineAd(
          code: '<your-code>',
          messageId: message.id,
        ),
      ],
    );
  },
)
```

> 💡 **说明：** `InlineAd` 并不总是会展示广告。是否展示取决于当前对话的上下文。
> 若没有可用的广告，`InlineAd` 会自动返回 `const SizedBox.shrink()`，不会在布局中占用任何额外空间。

## API 文档

### `AdsProvider` 属性

<ParamField path="publisherToken" type="String" required>
  你的 publisher token。
</ParamField>

<ParamField path="messages" type="List<Message>" required>
  用户和助手之间的消息列表。

  <Expandable title="字段">
    <ParamField path="id" type="String" required>
      消息的唯一 ID。
    </ParamField>

    <ParamField path="role" type="enum" required>
      消息角色（`MessageRole.user` 或 `MessageRole.assistant`）。
    </ParamField>

    <ParamField path="content" type="String" required>
      消息文本。
    </ParamField>

    <ParamField path="createdAt" type="DateTime" required>
      消息创建时间戳。
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="userId" type="String" required>
  在用户整个生命周期内保持不变的唯一标识（用于再营销与奖励广告）。
</ParamField>

<ParamField path="userEmail" type="String" optional>
  用户邮箱。
</ParamField>

<ParamField path="conversationId" type="String" required>
  对话的唯一 ID。
</ParamField>

<ParamField path="enabledPlacementCodes" type="List<String>" required>
  本次对话启用的 placement code。例如：`['inlineAd']`。
</ParamField>

<ParamField path="character" type="Character" required>
  对话中使用的 character 对象。

  <Expandable title="字段">
    <ParamField path="id" type="String" required>
      角色的唯一标识。
    </ParamField>

    <ParamField path="name" type="String" required>
      角色名称。
    </ParamField>

    <ParamField path="avatarUrl" type="String" required>
      角色头像图片的 URL。
    </ParamField>

    <ParamField path="isNsfw" type="bool" optional>
      角色是否面向成人受众（NSFW）。
    </ParamField>

    <ParamField path="greeting" type="String" optional>
      角色的问候语。
    </ParamField>

    <ParamField path="persona" type="String" optional>
      角色性格的描述。
    </ParamField>

    <ParamField path="tags" type="List<String>" optional>
      与角色相关的标签。
    </ParamField>

    <ParamField path="additionalProperties" type="Map<String, dynamic>" optional>
      可以附加到角色上的额外属性。
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="regulatory" type="Regulatory" optional>
  合规相关信息。

  如果你的应用集成了兼容 TCF 的 CMP（Consent Management Platform），SDK 会自动处理 **TCF（透明度与同意框架）** 信号（`gdpr` 与 `gdprConsent`）——你无需手动设置这两个字段。

  <Expandable title="字段">
    <ParamField path="gdpr" type="int" optional>
      请求是否受 GDPR 约束的标志（0 = 否，1 = 是，null = 未知）。
    </ParamField>

    <ParamField path="gdprConsent" type="String" optional>
      IAB 透明度与同意框架（TCF）的 consent 字符串。
    </ParamField>

    <ParamField path="coppa" type="int" optional>
      请求是否受 COPPA 约束（0 = 否，1 = 是，null = 未知）。
    </ParamField>

    <ParamField path="gpp" type="String" optional>
      Global Privacy Platform (GPP) consent 字符串。
    </ParamField>

    <ParamField path="gppSid" type="List<int>" optional>
      本次请求应用的 GPP section ID 列表。
    </ParamField>

    <ParamField path="usPrivacy" type="String" optional>
      美国隐私法规（CCPA、LSPA）相关的消费者隐私信号。
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="variantId" type="String" optional>
  发布方提供的用户分群标识（用于 A/B 测试）。
</ParamField>

<ParamField path="otherParams" type="Map<String, dynamic>" optional>
  用于把发布方自有信息透传给 Kontext。
</ParamField>

<ParamField path="isDisabled" type="bool" optional>
  是否禁用广告的标志位。
</ParamField>

<ParamField path="onEvent" type="void Function(AdEvent event)" optional>
  事件发生时的回调。详见下方的 AdEvent 类型章节。
</ParamField>

<ParamField path="child" type="Widget" required>
  被 `AdsProvider` 包裹的 widget 子树。
</ParamField>

### `InlineAd` 属性

<ParamField path="code" type="String" required>
  用于标识所展示广告的格式 code。
</ParamField>

<ParamField path="messageId" type="String" required>
  本广告所关联消息的唯一标识。
</ParamField>

### AdEvent 类型

#### `ad.clicked`

用户点击了广告。

<Expandable title="Payload">
  <ParamField path="id" type="String">
    广告 ID。
  </ParamField>

  <ParamField path="content" type="String">
    生成的广告内容。
  </ParamField>

  <ParamField path="messageId" type="String">
    消息 ID。
  </ParamField>

  <ParamField path="url" type="String">
    点击跳转的 URL。
  </ParamField>

  <ParamField path="format" type="String">
    广告形式。
  </ParamField>
</Expandable>

#### `ad.viewed`

用户已查看广告。

<Expandable title="Payload">
  <ParamField path="id" type="String">
    广告 ID。
  </ParamField>

  <ParamField path="content" type="String">
    生成的广告内容。
  </ParamField>

  <ParamField path="messageId" type="String">
    消息 ID。
  </ParamField>

  <ParamField path="format" type="String">
    广告形式。
  </ParamField>

  <ParamField path="revenue" type="Double">
    广告收入（美元计的 eCPM）。
  </ParamField>
</Expandable>

#### `ad.filled`

有可投放的广告。

<Expandable title="Payload">
  <ParamField path="revenue" type="Double">
    广告收入（美元计的 eCPM）。
  </ParamField>
</Expandable>

#### `ad.no-fill`

没有可投放的广告。

<Expandable title="Payload">
  <ParamField path="skipCode" type="String">
    指示广告被跳过原因的代码。
  </ParamField>
</Expandable>

#### `ad.render-started`

在第一个 token 到达之前触发。

<Expandable title="Payload">
  <ParamField path="id" type="String" required>
    广告 ID。
  </ParamField>
</Expandable>

#### `ad.render-completed`

在最后一个 token 到达之后触发。

<Expandable title="Payload">
  <ParamField path="id" type="String" required>
    广告 ID。
  </ParamField>
</Expandable>

#### `ad.error`

出现错误时触发。

<Expandable title="Payload">
  <ParamField path="message" type="String" required>
    错误信息。
  </ParamField>

  <ParamField path="errCode" type="String" required>
    错误代码。
  </ParamField>
</Expandable>

#### `reward.granted`

用户获得奖励时触发。

<Expandable title="Payload">
  <ParamField path="id" type="String" required>
    广告 ID。
  </ParamField>
</Expandable>

#### `video.started`

视频开始播放时触发。

<Expandable title="Payload">
  <ParamField path="id" type="String" required>
    广告 ID。
  </ParamField>
</Expandable>

#### `video.completed`

视频播放完成时触发。

<Expandable title="Payload">
  <ParamField path="id" type="String" required>
    广告 ID。
  </ParamField>
</Expandable>

## 实践指南

### 处理 no-fill 事件

可以通过 `onEvent` 回调并监听 `ad.no-fill` 事件感知广告何时不可用。

## 故障排查

### 提示插件缺失

如果看到 `MissingPluginException` 或某插件未注册的报错，可尝试：

```bash theme={null}
flutter clean
flutter pub get
```

这会清除构建缓存并确保插件被重新注册。如果问题仍未解决，请尝试 `flutter run` 重新构建，或重启 IDE。

## 链接

* [Flutter SDK GitHub](https://github.com/kontextso/sdk-flutter)
* [Pub](https://pub.dev/packages/kontext_flutter_sdk)
* [更新日志](https://github.com/kontextso/sdk-flutter/blob/main/CHANGELOG.md)
