---
title: "Migrate Adapty Kotlin Multiplatform SDK sang v. 4.0"
description: "Migrate sang Adapty Kotlin Multiplatform SDK v4.0 (beta) bằng cách thay thế các API paywall bằng API flow, tương thích với cả Flow Builder và Paywall Builder."
---

Adapty Kotlin Multiplatform SDK 4.0 (beta) giới thiệu các flow và đổi tên các API paywall tương ứng. Các API mới hoạt động với cả Flow Builder mới lẫn Paywall Builder hiện có — không cần thay đổi cấu hình nào trên Adapty Dashboard.

## Tài liệu tham khảo nhanh \{#quick-reference\}

| v3 | v4 |
|---|---|
| `Adapty.getPaywall(placementId, locale)` | `Adapty.getFlow(placementId)` |
| `Adapty.getPaywallForDefaultAudience(placementId, locale)` | `Adapty.getFlowForDefaultAudience(placementId)` |
| `Adapty.getPaywallProducts(paywall)` | `Adapty.getPaywallProducts(flow)` |
| `Adapty.logShowPaywall(paywall)` | `Adapty.logShowFlow(flow)` |
| `AdaptyPaywall` | `AdaptyFlow` |
| `AdaptyUI.createPaywallView(paywall, ...)` | `AdaptyUI.createFlowView(flow, ...)` |
| `AdaptyUI.createNativePaywallView(...)` → `AdaptyNativePaywallView` | `AdaptyUI.createNativeFlowView(...)` → `AdaptyNativeFlowView` |
| `AdaptyUIPaywallView` | `AdaptyUIFlowView` |
| `AdaptyUI.presentPaywallView(view)` / `dismissPaywallView(view)` | `AdaptyUI.presentFlowView(view)` / `dismissFlowView(view)` |
| `AdaptyUI.setPaywallsEventsObserver(observer)` | `AdaptyUI.setFlowsEventsObserver(observer)` |
| `AdaptyUI.registerPaywallEventsListener` / `unregisterPaywallEventsListener` | `AdaptyUI.registerFlowEventsListener` / `unregisterFlowEventsListener` |
| `AdaptyUIPaywallsEventsObserver` | `AdaptyUIFlowsEventsObserver` |
| `AdaptyUIPaywallPlatformView(paywall, ...)` | `AdaptyUIFlowPlatformView(flow, ...)` |
| `paywallViewDidPerformAction`, `paywallViewDidAppear`, và các callback `paywallView...` khác | `flowViewDidPerformAction`, `flowViewDidAppear`, và các callback `flowView...` khác |
| `paywallViewDidFailRendering` | `flowViewDidReceiveError` |

`AdaptyPaywallProduct` giữ nguyên tên — sản phẩm vẫn thuộc về một flow, và `getPaywallProducts` cũng giữ nguyên tên, giờ nhận vào một `AdaptyFlow`. Các phương thức `getFlow` và `getFlowForDefaultAudience` không còn nhận tham số `locale` nữa. Các API mua hàng và hồ sơ người dùng (`makePurchase`, `restorePurchases`, `getProfile`, `identify`, `updateProfile`) cùng với fallback qua `setFallback` vẫn không thay đổi. Các phương thức onboarding vẫn hoạt động nhưng đã bị deprecated — xem [Deprecation của Onboarding API](#onboarding-api-deprecation). Một số hành vi mặc định đã thay đổi — xem [Thay đổi hành vi mặc định](#default-behavior-changes).

## Cài đặt \{#installation\}

v4.0 là bản pre-release, vì vậy hãy ghim đúng phiên bản — Gradle không tự chọn các phiên bản pre-release thông qua dynamic ranges:

```toml showLineNumbers title="libs.versions.toml"
[versions]
adapty-kmp = "4.0.0-beta.1"

[libraries]
adapty-kmp = { module = "io.adapty:adapty-kmp", version.ref = "adapty-kmp" }
adapty-kmp-ui = { module = "io.adapty:adapty-kmp-ui", version.ref = "adapty-kmp" }
```

Module `adapty-kmp-ui` chỉ cần thiết nếu bạn render flow và paywall bằng layer Compose Multiplatform (`view.present()`). Xem [Cài đặt Adapty SDK](sdk-installation-kotlin-multiplatform) để biết hướng dẫn thiết lập đầy đủ.

Các native Adapty SDK bên dưới đã được nâng lên phiên bản 4.x trên cả hai nền tảng và được resolve tự động — không cần thay đổi cấu hình build. iOS deployment target vẫn là **15.0**, không thay đổi trong bản phát hành này.

## Tải flows \{#fetching-flows\}

### getPaywall → getFlow

Kiểu trả về thay đổi từ `AdaptyPaywall` thành `AdaptyFlow`, và tham số `locale` bị loại bỏ — khi bạn render một flow, locale sẽ được tự động xác định; đối với paywall tùy chỉnh, tất cả các locale được trả về trong `flow.remoteConfigs`:

```diff showLineNumbers
- Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en")
-     .onSuccess { paywall ->
-         // use the paywall
+ Adapty.getFlow("YOUR_PLACEMENT_ID")
+     .onSuccess { flow ->
+         // use the flow
      }
      .onError { error ->
          // handle the error
      }
```

`getPaywallForDefaultAudience` được đổi tên tương tự:

```diff showLineNumbers
- Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en")
+ Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID")
```

### getPaywallProducts(paywall) → getPaywallProducts(flow)

`getPaywallProducts` giữ nguyên tên nhưng bây giờ nhận vào một `AdaptyFlow`:

```diff showLineNumbers
- Adapty.getPaywallProducts(paywall)
+ Adapty.getPaywallProducts(flow)
      .onSuccess { products ->
          // use the products
      }
```

## Mô hình dữ liệu \{#data-model\}

`getFlow` trả về một `AdaptyFlow` thay vì `AdaptyPaywall`, và cấu trúc object đã thay đổi:

| Thuộc tính `AdaptyPaywall` v3 | Thuộc tính `AdaptyFlow` v4 | Hành động |
|---|---|---|
| `remoteConfig: AdaptyRemoteConfig?` (đơn lẻ) | `remoteConfigs: List<AdaptyRemoteConfig>` | Một flow mang một Remote Config cho mỗi ngôn ngữ được cấu hình. Đọc cái phù hợp với người dùng: `flow.remoteConfigs.firstOrNull { it.locale == "en" }`. |
| _(mới)_ | `paywalls: List<AdaptyFlowPaywall>` | Mỗi mục là một biến thể paywall trong flow, với `name`, `variationId`, và `productIdentifiers` riêng. Các phương thức web paywall nhận một `AdaptyFlowPaywall` — xem [Phương thức web paywall](#web-paywall-methods). |
| `productIdentifiers` | đã chuyển | Định danh sản phẩm giờ nằm trên mỗi biến thể: `flow.paywalls[i].productIdentifiers`. Để lấy sản phẩm, tiếp tục gọi `getPaywallProducts(flow)`. |
| `hasViewConfiguration` | đã xóa | Xóa mọi kiểm tra `hasViewConfiguration` khỏi code của bạn — `createFlowView` sẽ trả về lỗi thay thế (xem [Hiển thị flow](#displaying-flows)). |

`hasViewConfiguration` vẫn còn trên `AdaptyOnboarding` — chỉ có flow model là bỏ nó đi.

## Phương thức Web paywall \{#web-paywall-methods\}

`openWebPaywall` và `createWebPaywallUrl` giữ nguyên tên, nhưng tham số `paywall` được thay thế bằng tham số `flowPaywall` nhận một `AdaptyFlowPaywall` — một trong các biến thể trong `flow.paywalls`. Bạn vẫn có thể truyền vào một `AdaptyPaywallProduct` thay thế:

```diff showLineNumbers
- Adapty.openWebPaywall(paywall = paywall)
+ flow.paywalls.firstOrNull()?.let { flowPaywall ->
+     Adapty.openWebPaywall(flowPaywall = flowPaywall)
+ }
```

## Theo dõi lượt xem flow \{#tracking-flow-views\}

### logShowPaywall → logShowFlow

`logShowPaywall` được đổi tên thành `logShowFlow` và nhận vào một `AdaptyFlow`. Sự kiện vẫn được ghi nhận theo cùng một biến thể, vì vậy các chỉ số funnel và A/B test hiện có vẫn hoạt động bình thường mà không cần thay đổi gì trên dashboard.

```diff showLineNumbers
- Adapty.logShowPaywall(paywall)
+ Adapty.logShowFlow(flow)
```

Giống như trong v3, bạn không cần gọi phương thức này khi hiển thị các flow hoặc paywall được dựng bởi [Flow Builder](adapty-flow-builder) hoặc [Paywall Builder](adapty-paywall-builder) — Adapty tự động theo dõi các lượt xem đó.

## Hiển thị flows \{#displaying-flows\}

### createPaywallView → createFlowView

Đổi tên phương thức factory và truyền vào `AdaptyFlow`. Kiểu view trả về được đổi tên từ `AdaptyUIPaywallView` thành `AdaptyUIFlowView`, nhưng các phương thức (`present`, `dismiss`) và các tham số tùy chọn (`loadTimeout`, `preloadProducts`, `customTags`, `customTimers`, `customAssets`, `productPurchaseParams`) vẫn không thay đổi:

```diff showLineNumbers
- AdaptyUI.createPaywallView(paywall)
+ AdaptyUI.createFlowView(flow)
      .onSuccess { view ->
          view.present()
      }
      .onError { error ->
          // handle the error
      }
```

Nếu bạn không sử dụng Compose Multiplatform, phương thức factory native cũng được đổi tên tương tự:

```diff showLineNumbers
- AdaptyUI.createNativePaywallView(paywall)
+ AdaptyUI.createNativeFlowView(flow)
```

`createFlowView` trả về `AdaptyResult.Error` nếu flow không có view được cấu hình — điều này thay thế cho kiểm tra `hasViewConfiguration` ở v3:

```diff showLineNumbers
- if (paywall.hasViewConfiguration) {
-     AdaptyUI.createPaywallView(paywall)
-         .onSuccess { view -> view.present() }
- }
+ AdaptyUI.createFlowView(flow)
+     .onSuccess { view -> view.present() }
+     .onError { error ->
+         // the flow has no view configured, or view creation failed
+     }
```

:::note
Flow view chỉ dùng được một lần: sau khi gọi `dismiss()`, view sẽ bị huỷ, vì vậy hãy gọi lại `createFlowView` nếu muốn hiển thị flow thêm lần nữa.
:::

## Xử lý sự kiện \{#handling-events\}

Observer sự kiện được đổi tên từ `AdaptyUIPaywallsEventsObserver` thành `AdaptyUIFlowsEventsObserver`, và các callback của nó thay đổi tiền tố `paywallView` thành `flowView`. Nội dung các handler hiện có không cần thay đổi code — chỉ cần đổi tên kiểu và các override:

```diff showLineNumbers
- AdaptyUI.setPaywallsEventsObserver(object : AdaptyUIPaywallsEventsObserver {
-     override fun paywallViewDidFinishPurchase(
-         view: AdaptyUIPaywallView,
+ AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver {
+     override fun flowViewDidFinishPurchase(
+         view: AdaptyUIFlowView,
          product: AdaptyPaywallProduct,
          purchaseResult: AdaptyPurchaseResult
      ) {
          // custom logic after purchase
      }
  })
```

Một callback cũng được đổi tên: `paywallViewDidFailRendering` thành `flowViewDidReceiveError`. Callback này kích hoạt cho các lỗi render như trước, cộng thêm các lỗi runtime không liên quan đến mua hàng:

```diff showLineNumbers
- override fun paywallViewDidFailRendering(view: AdaptyUIPaywallView, error: AdaptyError) {}
+ override fun flowViewDidReceiveError(view: AdaptyUIFlowView, error: AdaptyError) {}
```

Xem [Xử lý sự kiện flow & paywall](kmp-handling-events) để biết danh sách đầy đủ các callback.

### Compose platform view

Nếu bạn nhúng view bằng composable Compose Multiplatform, `AdaptyUIPaywallPlatformView(paywall, ...)` được đổi tên thành `AdaptyUIFlowPlatformView(flow, ...)`. Các callback sự kiện vẫn giữ tên `onDid...`, ngoại trừ `onDidFailRendering` được đổi thành `onDidReceiveError`:

```diff showLineNumbers
- AdaptyUIPaywallPlatformView(
-     paywall = paywall,
+ AdaptyUIFlowPlatformView(
+     flow = flow,
      onDidFinishPurchase = { view, product, result -> /* ... */ },
  )
```

Giống như trong v3, các callback bạn truyền vào đây (và bất kỳ observer nào được đăng ký qua `registerFlowEventsListener`) chạy **bổ sung vào** global observer, không thay thế nó — callback của bạn quan sát một sự kiện; nó không thay thế hành vi mặc định toàn cục. Hãy lưu ý các [thay đổi mặc định](#default-behavior-changes): ví dụ, hành vi mặc định toàn cục không còn tự động đóng view sau khi mua hàng nữa.

### API Mới \{#new-apis\}

- `AdaptyUI.setObserverModeResolver(...)` với `AdaptyUIObserverModeResolver` — điều khiển các giao dịch mua và khôi phục được khởi tạo từ flow khi SDK chạy ở [chế độ Observer](implement-observer-mode-kmp). Trước đây, tính năng này chỉ có trong các SDK native iOS và Android. Xem [Hiển thị flow trong chế độ Observer](kmp-present-flows-in-observer-mode).
- `AdaptyUI.setSystemRequestsHandler(...)` với `AdaptyUISystemRequestsHandler` — dành riêng cho các yêu cầu hệ thống từ flow (lời nhắc cấp quyền OS và yêu cầu đánh giá ứng dụng). Hiện tại flow chưa kích hoạt các yêu cầu này, nên bạn chưa cần đăng ký handler.
- Callback tùy chọn mới `flowViewDidReceiveAnalyticEvent` được dành riêng cho các sự kiện phân tích tùy chỉnh từ flow. Hiện tại flow chưa phát ra các sự kiện này cho code của bạn, nên bạn chưa cần triển khai.
- `AdaptyUI.openWebUrl(url, openIn)` và `AdaptyUI.requestAppReview()` — hỗ trợ xử lý mặc định `OpenUrlAction` và `handleAppReviewRequest` mặc định, giúp xử lý URL và lời nhắc đánh giá ứng dụng ngay lập tức mà không cần cấu hình thêm. Chỉ gọi trực tiếp khi bạn ghi đè các giá trị mặc định đó.
- `AdaptyConfig.ServerCluster.CN` — tùy chọn cụm máy chủ mới bên cạnh `DEFAULT` và `EU`, dùng để kết nối ứng dụng với [máy chủ China của Adapty](china-cluster).

## Thay đổi hành vi mặc định \{#default-behavior-changes\}

Những thay đổi này không gây ra lỗi biên dịch, vì vậy hãy kiểm tra chúng ở runtime:

- **Hoàn tất mua hàng**: Trong v3, `paywallViewDidFinishPurchase` mặc định sẽ đóng view sau mọi kết quả mua hàng ngoại trừ `AdaptyPurchaseResult.UserCanceled`. Trong v4, `flowViewDidFinishPurchase` mặc định là no-op, tức là **flow vẫn mở sau khi mua hàng cho đến khi bạn tự đóng nó** — giống hành vi trên iOS. Nếu bạn phụ thuộc vào tính năng tự đóng đó, hãy tự gọi `view.dismiss()` sau khi mua hàng xong.
- **Nút back hệ thống Android**: Trong v3, `paywallViewDidPerformAction` mặc định sẽ đóng view với cả `CloseAction` lẫn `AndroidSystemBackAction`. Trong v4, hành vi mặc định chỉ xử lý `CloseAction` — **nút back hệ thống không còn tự đóng flow nữa**, giống iOS, nơi flow không thể bị đóng bằng thao tác hệ thống. Hãy cung cấp cho người dùng cách thoát rõ ràng (nút **Close** hoặc hành động `on_device_back`), hoặc tự đóng view trong `flowViewDidPerformAction`.
- **Lỗi view**: Trong v3, `paywallViewDidFailRendering` mặc định không làm gì. Trong v4, `flowViewDidReceiveError` mặc định sẽ **đóng view** — hãy override nó nếu bạn muốn giữ view mở hoặc xử lý lỗi theo cách khác.
- **View chỉ dùng một lần**: Sau khi gọi `dismiss()`, view sẽ bị hủy. Gọi lại `createFlowView` để hiển thị flow thêm một lần nữa.

## Ngừng hỗ trợ Onboarding API \{#onboarding-api-deprecation\}

Onboarding API cũ đã bị ngừng hỗ trợ trong v4.0, thay thế bằng [Flow Builder](adapty-flow-builder). API này vẫn hoạt động, nhưng sẽ bị xóa trong phiên bản tương lai, vì vậy hãy lên kế hoạch migration các onboarding của bạn sang Flow Builder.

Các ký hiệu đã ngừng hỗ trợ: `getOnboarding`, `getOnboardingForDefaultAudience`, `AdaptyUI.createOnboardingView`, `AdaptyUI.createNativeOnboardingView`, và `AdaptyUIOnboardingsEventsObserver`.