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

# SDK di động

> Đưa khung chat Live Chat vào ứng dụng Android và iOS bằng hai bộ SDK chính thức

Hai bộ SDK Android và iOS đưa đúng khung chat của kênh Live Chat vào ứng dụng của bạn. Cả hai đều là lớp bọc mỏng quanh một WebView, không khai báo thư viện phụ thuộc nào, và nhận đúng ba tham số; mọi thứ còn lại lấy từ cấu hình trên Console.

<Info icon="mobile-screen">
  SDK dùng chung cấu hình với widget web. Hãy cấu hình kênh Web widget trước theo hướng dẫn ở [Kênh Live Chat](/kenh-live-chat), rồi lấy `connectionKey` từ thẻ Config.
</Info>

## Ba tham số khởi tạo

| Tham số | Mô tả | Bắt buộc |
| - | - | - |
| `appOrigin` | Địa chỉ ứng dụng chat, kèm cả đường dẫn: `https://console-agents.fpt.ai/chat-widget` | Bắt buộc |
| `connectionKey` | Khóa kết nối của kênh Website, ví dụ `wgt_abc123` | Bắt buộc |
| `locale` | Ngôn ngữ: `vi`, `en`, `ja`, `id`, `zh` | Tùy chọn |

<Warning>
  `appOrigin` phải giữ nguyên phần đường dẫn `/chat-widget`. Nếu chỉ đưa tên miền gốc, WebView sẽ tải ứng dụng nằm ở gốc và khung chat hiện ra trắng.
</Warning>

<Warning>
  Khác với website, SDK di động không đọc **Ngôn ngữ mặc định** trên Console. Bỏ trống `locale` thì khung chat luôn hiển thị tiếng Việt. Hãy truyền ngôn ngữ của thiết bị vào `locale` để ứng dụng nói đúng thứ tiếng khách hàng đang dùng.
</Warning>

## Yêu cầu tối thiểu

| Hạng mục | Android | iOS |
| - | - | - |
| Phiên bản tối thiểu | Android 7.0 (API 24) | iOS 14 |
| Thư viện phụ thuộc | Không - chỉ Kotlin runtime | Không |
| Khai báo bắt buộc | `INTERNET`. SDK tự khai trong manifest của thư viện nên bạn không phải thêm. | `NSPhotoLibraryUsageDescription` và `NSCameraUsageDescription` trong `Info.plist` - xem cảnh báo bên dưới. |

## Nhận gói cài đặt SDK

Chúng tôi bàn giao SDK dưới dạng gói cài đặt, không qua kho công cộng. Bạn nhận hai tệp nén, mỗi tệp cho một nền tảng - chỉ cần lấy tệp tương ứng với nền tảng bạn phát triển.

<CardGroup cols={2}>
  <Card title="fpt-agent-chat-1.0.0-android.zip" icon="android">
    Giải nén rồi chép thư mục `android/maven` trong đó vào dự án của bạn, ví dụ vào `libs/fpt-agent-chat/maven`. Tệp `android/chat-1.0.0.aar` nằm cạnh là bản rời, dùng khi dự án của bạn không thêm được kho Maven.
  </Card>

  <Card title="fpt-agent-chat-1.0.0-ios.zip" icon="apple">
    Giải nén rồi kéo `ios/FptAgentChat.xcframework` vào target của bạn.
  </Card>
</CardGroup>

<Warning>
  Trên macOS hãy giải nén bằng Finder hoặc lệnh `unzip`, và không nén lại thư mục đã giải nén. `.xcframework` là một bundle; nén lại bằng công cụ khác có thể làm hỏng liên kết bên trong và Xcode sẽ báo lỗi khó hiểu.
</Warning>

<Note>
  Mỗi tệp nén đã kèm sẵn một tệp `README.md` ghi đầy đủ các bước cho riêng nền tảng đó.
</Note>

### Khai báo thư viện - Android

```kotlin theme={null}
// settings.gradle.kts - trỏ tới kho Maven đi kèm gói bàn giao
dependencyResolutionManagement {
    repositories {
        maven { url = uri("$rootDir/libs/fpt-agent-chat/maven") }
        google()
        mavenCentral()
    }
}
```

```kotlin theme={null}
// build.gradle.kts của module ứng dụng
implementation("vn.fptsmartcloud.agent:chat:1.0.0")
```

### Khai báo thư viện - iOS

Kéo `ios/FptAgentChat.xcframework` vào target, rồi kiểm tra nó đã nằm trong mục **Frameworks, Libraries, and Embedded Content** với chế độ **Embed & Sign**.

## Android

### Mở khung chat toàn màn hình

```kotlin theme={null}
val config = FptChatConfig(
    appOrigin     = "https://console-agents.fpt.ai/chat-widget",
    connectionKey = "wgt_abc123",
    locale        = "vi"
)
FptChat.open(context, config)
```

### Nhúng khung chat vào màn hình có sẵn

```kotlin theme={null}
val chat = findViewById<FptChatView>(R.id.chat)
chat.load(config)
```

### Bắt buộc khi bạn tự nhúng FptChatView

`FptChatActivity` đã nối sẵn hộp thoại chọn tệp. Nếu bạn đặt `FptChatView` vào màn hình của mình thì phải tự nối: không nối thì nút ghim tệp bấm vào không có phản ứng, và vì lời gọi lại bị bỏ lửng nên lần bấm thứ hai cũng không phản ứng nốt.

```kotlin theme={null}
private var pendingFiles: ValueCallback<Array<Uri>>? = null

chat.fileChooserHandler = { intent, callback ->
    pendingFiles?.onReceiveValue(null)   // đừng bỏ lửng lời gọi lại trước đó
    pendingFiles = callback
    startActivityForResult(intent, REQ_FILES)
    true
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode != REQ_FILES) return
    val cb = pendingFiles ?: return
    pendingFiles = null
    cb.onReceiveValue(
        if (resultCode == RESULT_OK && data?.data != null) arrayOf(data.data!!)
        else null
    )
}
```

<Note>
  Activity chứa `FptChatView` còn cần `android:windowSoftInputMode="adjustResize"` trong `AndroidManifest.xml`, nếu không bàn phím sẽ che ô soạn tin. `FptChatActivity` đã có sẵn thuộc tính này.
</Note>

### Xử lý nút quay lại

```kotlin theme={null}
override fun onBackPressed() {
    if (!chat.onBackPressed()) super.onBackPressed()
}
```

### Vòng đời

```kotlin theme={null}
override fun onPause()   { chat.pause();   super.onPause() }
override fun onResume()  { super.onResume(); chat.resume() }
override fun onDestroy() { chat.release(); super.onDestroy() }
```

<Note>
  Gọi đủ cả ba phương thức vòng đời. Bỏ `release()` thì WebView vẫn sống sau khi màn hình đóng và gây rò rỉ bộ nhớ.
</Note>

## iOS

### Bắt buộc: hai khóa trong Info.plist

Khung chat cho phép khách hàng gửi tệp đính kèm và `chat.allow_attachments` mặc định bật, nên đây là đường đi mặc định chứ không phải trường hợp hiếm. Nếu `Info.plist` thiếu `NSPhotoLibraryUsageDescription` hoặc `NSCameraUsageDescription`, hệ điều hành sẽ **đóng ứng dụng của bạn** ngay khi khách hàng bấm nút ghim tệp - không phải báo lỗi mà là thoát ứng dụng. Hãy khai cả hai, bằng câu chữ người dùng đọc được.

```xml theme={null}
<key>NSPhotoLibraryUsageDescription</key>
<string>Ứng dụng cần truy cập thư viện ảnh để bạn gửi ảnh trong cuộc trò chuyện.</string>
<key>NSCameraUsageDescription</key>
<string>Ứng dụng cần truy cập máy ảnh để bạn chụp và gửi ảnh trong cuộc trò chuyện.</string>
```

### Mở khung chat toàn màn hình

```swift theme={null}
let config = FptChatConfig(
    appOrigin: "https://console-agents.fpt.ai/chat-widget",
    connectionKey: "wgt_abc123",
    locale: "vi"
)
try FptChat.present(from: self, config: config)
```

### Nhúng khung chat vào màn hình có sẵn

```swift theme={null}
let chat = FptChatView()
try chat.load(config)
```

### Mở liên kết ngoài

```swift theme={null}
chat.externalLinkHandler = { url in UIApplication.shared.open(url) }
```

### Điều hướng lùi trong WebView

```swift theme={null}
if !chat.goBack() { navigationController?.popViewController(animated: true) }
```

## Những điều cần nhớ

<CardGroup cols={2}>
  <Card title="Cấu hình nằm trên Console" icon="sliders">
    Màu sắc, lời chào, câu gợi ý và chính sách đính kèm đều lấy từ thẻ Customize widget. Sửa trên Console là ứng dụng đổi theo ngay, không cần phát hành bản mới.
  </Card>

  <Card title="Ngôn ngữ phải truyền tay" icon="language">
    Đây là khác biệt duy nhất so với web. Hãy lấy ngôn ngữ hệ thống của thiết bị và truyền vào `locale`.
  </Card>

  <Card title="Một kênh, nhiều điểm chạm" icon="share-nodes">
    Website và ứng dụng di động dùng chung một `connectionKey`, nên hội thoại và cấu hình là một.
  </Card>

  <Card title="Đổi kênh là đổi khóa" icon="key">
    Xóa kênh Website rồi tạo lại sẽ sinh `connectionKey` mới. Ứng dụng đang chạy phải cập nhật lại khóa.
  </Card>
</CardGroup>

## Danh mục kiểm tra trước khi phát hành

* Đã cấu hình và lưu kênh Web widget ít nhất một lần trên Console.
* `appOrigin` lấy từ Console và giữ nguyên phần đường dẫn `/chat-widget`.
* `connectionKey` đúng với kênh của Agent bạn muốn dùng.
* Đã truyền `locale` của thiết bị, vì SDK không đọc ngôn ngữ mặc định trên Console.
* Với iOS: `Info.plist` đã có `NSPhotoLibraryUsageDescription` và `NSCameraUsageDescription` - thiếu là ứng dụng thoát khi khách bấm ghim tệp.
* Với Android, nếu bạn tự nhúng `FptChatView`: đã nối `fileChooserHandler` cùng `onActivityResult`, và Activity đã đặt `windowSoftInputMode="adjustResize"`.
* Đã thử mở, đóng và quay lại nhiều lần để chắc chắn vòng đời được xử lý đúng.
* Đã thử trên cả mạng yếu để xem màn hình chờ và thông báo lỗi có hợp lý không.

<Info icon="table-list">
  Giới hạn kích thước tệp đính kèm, định dạng được phép và các mã lỗi chung nằm ở [Phụ lục kỹ thuật](/phu-luc-ky-thuat).
</Info>


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