---
title: "Kotlin Multiplatform - Hiển thị paywall mới được thiết kế bằng Paywall Builder"
description: "Tìm hiểu cách hiển thị paywall trên Kotlin Multiplatform để tối ưu hóa doanh thu."
---

Nếu bạn đã tùy chỉnh paywall bằng Paywall Builder, bạn không cần lo lắng về việc render nó trong code ứng dụng di động để hiển thị cho người dùng. Paywall đó đã chứa đầy đủ thông tin về nội dung cần hiển thị lẫn cách thức hiển thị.

:::warning

Hướng dẫn này chỉ dành cho **paywall mới được thiết kế bằng Paywall Builder**. Quy trình hiển thị paywall khác nhau đối với paywall được thiết kế bằng remote config và [Observer mode](observer-vs-full-mode).

Để hiển thị **paywall Remote config**, xem [Render paywall được thiết kế bằng remote config](present-remote-config-paywalls-kmp).

:::

Adapty Kotlin Multiplatform SDK cung cấp hai cách để hiển thị paywall:

- **Với Compose Multiplatform**
- **Không dùng Compose Multiplatform**

## Với Compose Multiplatform \{#with-compose-multiplatform\}

Để hiển thị paywall, sử dụng phương thức `view.present()` trên `view` được tạo bởi phương thức [`createPaywallView`](kmp-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). Mỗi `view` chỉ có thể được sử dụng một lần. Nếu bạn cần hiển thị lại paywall, hãy gọi `createPaywallView` một lần nữa để tạo một `view` instance mới.

:::warning
Việc tái sử dụng cùng một `view` mà không tạo lại có thể dẫn đến lỗi.
:::

```kotlin showLineNumbers title="Kotlin Multiplatform"

viewModelScope.launch {
    AdaptyUI.createPaywallView(paywall = paywall).onSuccess { view ->
        view.present()
    }.onError { error ->
        // handle the error
    }
}
```

### Hiển thị dialog \{#show-dialog\}

Sử dụng phương thức này thay vì các alert dialog gốc khi paywall view đang được hiển thị trên Android. Trên Android, các alert thông thường xuất hiện phía sau paywall view, khiến người dùng không thể nhìn thấy. Phương thức này đảm bảo dialog được hiển thị đúng cách phía trên paywall trên tất cả các nền tảng.

```kotlin showLineNumbers title="Kotlin Multiplatform"

viewModelScope.launch {
    view.showDialog(
        title = "Close paywall?",
        content = "You will lose access to exclusive offers.",
        primaryActionTitle = "Stay",
        secondaryActionTitle = "Close"
    ).onSuccess { action ->
        if (action == AdaptyUIDialogActionType.SECONDARY) {
            // User confirmed - close the paywall
            view.dismiss()
        }
        // If primary - do nothing, user stays
    }.onError { error ->
        // handle the error
    }
}
```

### Cấu hình kiểu trình bày trên iOS \{#configure-ios-presentation-style\}

Cấu hình cách paywall được hiển thị trên iOS bằng cách truyền tham số `iosPresentationStyle` vào phương thức `present()`. Tham số này chấp nhận các giá trị `AdaptyUIIOSPresentationStyle.FULLSCREEN` (mặc định) hoặc `AdaptyUIIOSPresentationStyle.PAGESHEET`.

```kotlin showLineNumbers

viewModelScope.launch {
    val view = AdaptyUI.createPaywallView(paywall = paywall).getOrNull()
    view?.present(iosPresentationStyle = AdaptyUIIOSPresentationStyle.PAGESHEET)
}
```

## Không dùng Compose Multiplatform \{#without-compose-multiplatform\}

:::note
`createNativePaywallView` là một phần của module `io.adapty:adapty-kmp` cốt lõi. Nếu dự án của bạn không sử dụng Compose Multiplatform, bạn không cần dependency `io.adapty:adapty-kmp-ui`.
:::

Để nhúng paywall mà không cần Compose Multiplatform, hãy gọi `createNativePaywallView`. Phương thức này trả về một `AdaptyNativePaywallView` mà bạn thêm vào layout của mình:

<Tabs>
<TabItem value="android" label="Android">
```kotlin showLineNumbers title="Kotlin Multiplatform (Android)"

val nativeView = AdaptyUI.createNativePaywallView(
    context = context,
    viewModelStoreOwner = activity,
    paywall = paywall,
    observer = myPaywallObserver,
)

// Embed in your Compose layout:
AndroidView(
    factory = { nativeView.view },
    modifier = Modifier.fillMaxSize()
)
```
</TabItem>
<TabItem value="ios" label="iOS">
Vì các phương thức mặc định của interface KMP trở thành `@required` trong Swift, bạn không thể implement `AdaptyUIPaywallsEventsObserver` trực tiếp từ Swift. Hãy khai báo một lớp base mở trong `iosMain` trước:

```kotlin showLineNumbers title="iosMain (Kotlin)"
open class BasePaywallObserver : AdaptyUIPaywallsEventsObserver
```

Sau đó kế thừa nó trong Swift, chỉ override những gì bạn cần:

```swift showLineNumbers title="Swift"
class MyPaywallObserver: BasePaywallObserver {
    override func paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: any AdaptyUIAction) {
        if action is AdaptyUIActionCloseAction {
            // remove nativeView from your view hierarchy
        }
    }
}

let nativeView = AdaptyUI.shared.createNativePaywallView(
    paywall: paywall,
    observer: MyPaywallObserver()
)
// nativeView.viewController is a UIViewController.
// Add it to your SwiftUI view or UIKit hierarchy.
```
</TabItem>
</Tabs>

### Hủy view \{#dispose-the-view\}

Gọi `dispose()` khi xóa view khỏi layout. Thao tác này sẽ hủy đăng ký event listener và giải phóng tài nguyên nội bộ.

```kotlin showLineNumbers title="Kotlin Multiplatform"
nativeView.dispose()
```

## Custom tags \{#custom-tags\}

Custom tags giúp bạn tránh phải tạo các paywall riêng biệt cho từng tình huống khác nhau. Hãy hình dung một paywall duy nhất có thể thích nghi linh hoạt dựa trên dữ liệu người dùng. Ví dụ, thay vì lời chào chung chung "Xin chào!", bạn có thể chào người dùng cá nhân hóa như "Xin chào, John!" hay "Xin chào, Ann!"

Dưới đây là một số cách bạn có thể sử dụng custom tags:

- Hiển thị tên hoặc email của người dùng trên paywall.
- Hiển thị ngày trong tuần hiện tại để thúc đẩy doanh số (ví dụ: "Thứ Năm vui vẻ").
- Thêm thông tin cá nhân hóa về sản phẩm bạn đang bán (như tên chương trình thể dục hoặc số điện thoại trong ứng dụng VoIP).

Custom tags giúp bạn tạo một paywall linh hoạt, thích ứng với nhiều tình huống khác nhau, giúp giao diện ứng dụng trở nên cá nhân hóa và hấp dẫn hơn.

:::warning
Trong một số trường hợp, ứng dụng của bạn có thể không biết cần thay thế custom tag bằng gì—đặc biệt khi người dùng đang dùng phiên bản cũ hơn của AdaptyUI SDK. Để tránh điều này, hãy luôn thêm văn bản dự phòng để thay thế các dòng chứa custom tag không xác định. Nếu không, người dùng có thể thấy các tag hiển thị dưới dạng code (`<USERNAME/>`).
:::

Để sử dụng custom tags trong paywall, hãy truyền chúng khi tạo paywall view:

<Tabs>
<TabItem value="standalone" label="With Compose Multiplatform" default>
```kotlin showLineNumbers title="Kotlin Multiplatform"

viewModelScope.launch {
    val customTags = mapOf(
        "USERNAME" to "John",
        "DAY_OF_WEEK" to "Thursday"
    )

    AdaptyUI.createPaywallView(
        paywall = paywall,
        customTags = customTags
    ).onSuccess { view ->
        view.present()
    }.onError { error ->
        // handle the error
    }
}
```
</TabItem>
<TabItem value="native" label="Without Compose Multiplatform">
```kotlin showLineNumbers title="Kotlin Multiplatform (Android)"

val customTags = mapOf(
    "USERNAME" to "John",
    "DAY_OF_WEEK" to "Thursday"
)

val nativeView = AdaptyUI.createNativePaywallView(
    context = context,
    viewModelStoreOwner = activity,
    paywall = paywall,
    observer = myPaywallObserver,
    customTags = customTags,
)
```

```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)"

val customTags = mapOf(
    "USERNAME" to "John",
    "DAY_OF_WEEK" to "Thursday"
)

val nativeView = AdaptyUI.createNativePaywallView(
    paywall = paywall,
    observer = myPaywallObserver,
    customTags = customTags,
)
```
</TabItem>
</Tabs>

## Custom timers \{#custom-timers\}

Bộ đếm thời gian paywall là một công cụ tuyệt vời để quảng bá các ưu đãi đặc biệt và theo mùa có thời hạn. Tuy nhiên, cần lưu ý rằng bộ đếm thời gian này không kết nối với thời hạn hiệu lực của ưu đãi hay thời gian chiến dịch. Đây chỉ đơn giản là một đồng hồ đếm ngược độc lập bắt đầu từ giá trị bạn đặt và giảm dần về không. Khi bộ đếm đạt về không, không có gì xảy ra—nó chỉ dừng ở không.

Bạn có thể tùy chỉnh văn bản trước và sau bộ đếm để tạo thông điệp mong muốn, chẳng hạn: "Ưu đãi kết thúc sau: 10:00 giây."

Để sử dụng custom timers trong paywall, hãy truyền chúng khi tạo paywall view:

<Tabs>
<TabItem value="standalone" label="With Compose Multiplatform" default>
```kotlin showLineNumbers title="Kotlin Multiplatform"

viewModelScope.launch {
    val customTimers = mapOf(
        "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0),
        "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59)
    )

    AdaptyUI.createPaywallView(
        paywall = paywall,
        customTimers = customTimers
    ).onSuccess { view ->
        view.present()
    }.onError { error ->
        // handle the error
    }
}
```
</TabItem>
<TabItem value="native" label="Without Compose Multiplatform">
```kotlin showLineNumbers title="Kotlin Multiplatform (Android)"

val customTimers = mapOf(
    "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0),
    "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59)
)

val nativeView = AdaptyUI.createNativePaywallView(
    context = context,
    viewModelStoreOwner = activity,
    paywall = paywall,
    observer = myPaywallObserver,
    customTimers = customTimers,
)
```

```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)"

val customTimers = mapOf(
    "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0),
    "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59)
)

val nativeView = AdaptyUI.createNativePaywallView(
    paywall = paywall,
    observer = myPaywallObserver,
    customTimers = customTimers,
)
```
</TabItem>
</Tabs>