` để ứng dụng của bạn cung cấp giá trị cuối cùng và yêu cầu manifest merger thay thế các giá trị của thư viện:
```xml
...
```
Nếu bất kỳ SDK nào cũng đặt `android:allowBackup`, hãy đưa nó vào `tools:replace` như sau:
```xml
tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules"
```
#### 3. Tạo các file quy tắc backup đã gộp \{#3-create-merged-backup-rules-files\}
Tạo các file XML trong `app/src/main/res/xml/` kết hợp các quy tắc của Adapty với quy tắc từ các SDK khác. Android sử dụng các định dạng quy tắc backup khác nhau tùy theo phiên bản hệ điều hành, vì vậy việc tạo cả hai file đảm bảo tính tương thích trên tất cả các phiên bản Android mà ứng dụng của bạn hỗ trợ.
:::note
Các ví dụ dưới đây dùng AppsFlyer làm SDK bên thứ ba mẫu. Hãy thay thế hoặc thêm các quy tắc cho bất kỳ SDK nào khác bạn đang sử dụng trong ứng dụng.
:::
**Dành cho Android 12 trở lên** (sử dụng định dạng data extraction rules mới):
```xml title="sample_data_extraction_rules.xml"
```
**Dành cho Android 11 trở xuống** (sử dụng định dạng full backup content cũ):
```xml title="sample_backup_rules.xml"
```
Với thiết lập này:
- Các quy tắc loại trừ backup của Adapty (`AdaptySDKPrefs.xml`) được giữ lại.
- Các quy tắc loại trừ của các SDK khác (ví dụ: `appsflyer-data`) cũng được áp dụng.
- Manifest merger sử dụng cấu hình của ứng dụng bạn và không còn thất bại do các thuộc tính backup xung đột.
#### Giao dịch mua thất bại sau khi quay lại từ ứng dụng khác \{#purchases-fail-after-returning-from-another-app\}
Nếu Activity khởi động flow mua hàng sử dụng `launchMode` không mặc định, Android có thể tạo lại hoặc tái sử dụng nó không đúng cách khi người dùng quay lại từ Google Play, ứng dụng ngân hàng, hoặc trình duyệt. Điều này có thể khiến kết quả mua hàng bị mất hoặc bị xử lý như đã hủy.
Để đảm bảo giao dịch mua hoạt động chính xác, chỉ sử dụng chế độ khởi động `standard` hoặc `singleTop` cho Activity khởi động flow mua hàng và tránh các chế độ khác.
Trong `AndroidManifest.xml`, đảm bảo Activity khởi động flow mua hàng được đặt thành `standard` hoặc `singleTop`:
```xml
```
---
# File: android-quickstart-paywalls
---
---
title: "Kích hoạt mua hàng bằng cách sử dụng paywall trong Android SDK"
description: "Hướng dẫn nhanh để thiết lập Adapty cho việc quản lý gói đăng ký trong ứng dụng."
---
Để kích hoạt in-app purchase, bạn cần nắm ba khái niệm chính:
- [**Sản phẩm**](product) – mọi thứ người dùng có thể mua (gói đăng ký, consumable, quyền truy cập trọn đời)
- [**Paywall**](paywalls) là các cấu hình xác định sản phẩm nào sẽ được cung cấp. Trong Adapty, paywall là cách duy nhất để lấy sản phẩm, nhưng thiết kế này cho phép bạn thay đổi ưu đãi, giá cả và tổ hợp sản phẩm mà không cần chỉnh sửa code.
- [**Placement**](placements) – vị trí và thời điểm hiển thị paywall trong ứng dụng (như `main`, `onboarding`, `settings`). Bạn thiết lập paywall cho các placement trên dashboard, sau đó gọi chúng bằng placement ID trong code. Điều này giúp dễ dàng chạy A/B test và hiển thị các paywall khác nhau cho từng nhóm người dùng.
Adapty cung cấp ba cách để kích hoạt mua hàng trong ứng dụng. Chọn một trong số đó tùy theo yêu cầu của ứng dụng:
| Cách triển khai | Độ phức tạp | Khi nào sử dụng |
|------------------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Adapty Paywall Builder | ✅ Dễ | Bạn [tạo paywall hoàn chỉnh, sẵn sàng để mua hàng trong trình tạo no-code](quickstart-paywalls). Adapty tự động render và xử lý toàn bộ luồng mua hàng, xác thực biên lai và quản lý gói đăng ký ở phía sau. |
| Paywall tự tạo | 🟡 Trung bình | Bạn tự tạo giao diện paywall trong code, nhưng vẫn lấy đối tượng paywall từ Adapty để linh hoạt trong việc cung cấp sản phẩm. Xem [hướng dẫn](android-quickstart-manual). |
| Chế độ Observer | 🔴 Khó | Bạn đã có hạ tầng xử lý mua hàng riêng và muốn tiếp tục sử dụng. Lưu ý rằng chế độ observer có những giới hạn nhất định trong Adapty. Xem [bài viết](observer-vs-full-mode). |
:::important
**Các bước dưới đây hướng dẫn cách triển khai paywall được tạo trong Adapty Paywall Builder.**
Nếu bạn không muốn dùng Paywall Builder, xem [hướng dẫn xử lý mua hàng trong paywall tự tạo](android-making-purchases).
:::
Để hiển thị paywall được tạo trong Adapty Paywall Builder, trong code ứng dụng, bạn chỉ cần:
1. **Lấy paywall**: Lấy paywall từ Adapty.
2. **Hiển thị paywall và Adapty sẽ xử lý mua hàng cho bạn**: Hiển thị container paywall bạn đã lấy trong ứng dụng.
3. **Xử lý các hành động nút**: Liên kết tương tác của người dùng với paywall với phản hồi của ứng dụng. Ví dụ, mở liên kết hoặc đóng paywall khi người dùng nhấn nút.
## Trước khi bắt đầu \{#before-you-start\}
Trước khi bắt đầu, hãy hoàn thành các bước sau:
1. [Kết nối ứng dụng với Google Play](initial-android) trong Adapty Dashboard.
2. [Tạo sản phẩm](create-product) trong Adapty.
3. [Tạo paywall và thêm sản phẩm vào đó](create-paywall).
4. [Tạo placement và thêm paywall vào đó](create-placement).
5. [Cài đặt và kích hoạt Adapty SDK](sdk-installation-android) trong code ứng dụng.
:::tip
Cách nhanh nhất để hoàn thành các bước này là làm theo [hướng dẫn bắt đầu nhanh](quickstart) hoặc tạo paywall và placement bằng [Developer CLI](developer-cli-quickstart).
:::
## 1. Lấy paywall \{#1-get-the-paywall\}
Các paywall của bạn được liên kết với các placement được cấu hình trên dashboard. Placement cho phép bạn chạy các paywall khác nhau cho các đối tượng khác nhau hoặc chạy [A/B test](ab-tests).
Để lấy paywall được tạo trong Adapty Paywall Builder, bạn cần:
1. Lấy đối tượng `paywall` theo [placement](placements) ID bằng phương thức `getPaywall` và kiểm tra xem đó có phải là paywall được tạo trong builder hay không.
2. Lấy cấu hình view của paywall bằng phương thức `getViewConfiguration`. Cấu hình view chứa các phần tử UI và kiểu dáng cần thiết để hiển thị paywall.
:::important
Để lấy cấu hình view, bạn phải bật toggle **Show on device** trong Paywall Builder. Nếu không, bạn sẽ nhận được cấu hình view rỗng và paywall sẽ không được hiển thị.
:::
```kotlin showLineNumbers
Adapty.getPaywall("YOUR_PLACEMENT_ID") { result ->
if (result is AdaptyResult.Success) {
val paywall = result.value
if (!paywall.hasViewConfiguration) {
return@getPaywall
}
AdaptyUI.getViewConfiguration(paywall) { configResult ->
if (configResult is AdaptyResult.Success) {
val viewConfiguration = configResult.value
}
}
}
}
```
```java showLineNumbers
Adapty.getPaywall("YOUR_PLACEMENT_ID", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
if (!paywall.hasViewConfiguration()) {
return;
}
AdaptyUI.getViewConfiguration(paywall, configResult -> {
if (configResult instanceof AdaptyResult.Success) {
AdaptyUI.LocalizedViewConfiguration viewConfiguration =
((AdaptyResult.Success) configResult).getValue();
// use loaded configuration
}
});
}
});
```
## 2. Hiển thị paywall \{#2-display-the-paywall\}
Bây giờ khi đã có cấu hình paywall, chỉ cần thêm vài dòng code để hiển thị paywall của bạn.
Để hiển thị paywall trên màn hình thiết bị, trước tiên bạn phải cấu hình nó. Để làm vậy, gọi phương thức `AdaptyUI.getPaywallView()` hoặc tạo trực tiếp `AdaptyPaywallView`:
```kotlin showLineNumbers
val paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
null, // products = null means auto-fetch
eventListener,
)
```
```kotlin showLineNumbers
val paywallView =
AdaptyPaywallView(activity) // or retrieve it from xml
...
with(paywallView) {
showPaywall(
viewConfiguration,
null, // products = null means auto-fetch
eventListener,
)
}
```
```java showLineNumbers
AdaptyPaywallView paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
null, // products = null means auto-fetch
eventListener,
);
```
```java showLineNumbers
AdaptyPaywallView paywallView =
new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml
...
paywallView.showPaywall(viewConfiguration, products, eventListener);
```
```xml showLineNumbers
```
Sau khi view được tạo thành công, bạn có thể thêm nó vào cây view hierarchy và hiển thị trên màn hình thiết bị.
:::tip
Để biết thêm chi tiết về cách hiển thị paywall, xem [hướng dẫn](android-present-paywalls) của chúng tôi.
:::
## 3. Xử lý các hành động nút \{#3-handle-button-actions\}
Khi người dùng nhấn nút trong paywall, Android SDK tự động xử lý mua hàng, khôi phục, đóng paywall và mở liên kết.
Tuy nhiên, các nút khác có ID tùy chỉnh hoặc được định nghĩa sẵn và cần xử lý hành động trong code của bạn. Hoặc, bạn có thể muốn ghi đè hành vi mặc định của chúng.
Ví dụ, đây là hành vi mặc định của nút đóng. Bạn không cần thêm nó vào code, nhưng ở đây bạn có thể thấy cách thực hiện nếu cần.
:::tip
Đọc hướng dẫn của chúng tôi về cách xử lý [hành động](android-handle-paywall-actions) và [sự kiện](android-handling-events) của nút.
:::
```kotlin showLineNumbers title="Kotlin"
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior
}
}
```
```java showLineNumbers
@Override
public void onActionPerformed(@NonNull AdaptyUI.Action action, @NonNull Context context) {
if (action instanceof AdaptyUI.Action.Close) {
if (context instanceof Activity) {
((Activity) context).onBackPressed();
}
}
}
```
## Các bước tiếp theo \{#next-steps\}
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
Bạn có câu hỏi hoặc gặp sự cố? Hãy xem [diễn đàn hỗ trợ](https://adapty.featurebase.app/) của chúng tôi — nơi bạn có thể tìm câu trả lời cho các câu hỏi thường gặp hoặc đặt câu hỏi của riêng mình. Đội ngũ và cộng đồng của chúng tôi luôn sẵn sàng giúp đỡ!
Paywall của bạn đã sẵn sàng để hiển thị trong ứng dụng. [Kiểm tra mua hàng trên Google Play Store](testing-on-android) để đảm bảo bạn có thể hoàn tất một giao dịch mua thử nghiệm từ paywall.
Tiếp theo, bạn cần [kiểm tra mức độ truy cập của người dùng](android-check-subscription-status) để đảm bảo hiển thị paywall hoặc cấp quyền truy cập tính năng trả phí cho đúng người dùng.
## Ví dụ đầy đủ \{#full-example\}
Dưới đây là cách tất cả các bước đó có thể được tích hợp cùng nhau trong ứng dụng của bạn.
```kotlin showLineNumbers title="Kotlin"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Adapty.getPaywall("YOUR_PLACEMENT_ID") { paywallResult ->
if (paywallResult is AdaptyResult.Success) {
val paywall = paywallResult.value
if (!paywall.hasViewConfiguration) {
// Use custom logic
return@getPaywall
}
AdaptyUI.getViewConfiguration(paywall) { configResult ->
if (configResult is AdaptyResult.Success) {
val viewConfiguration = configResult.value
val paywallView = AdaptyUI.getPaywallView(
this,
viewConfiguration,
null, // products = null means auto-fetch
object : AdaptyUIEventListener {
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
is AdaptyUI.Action.Close -> {
(context as? Activity)?.onBackPressed()
}
}
}
}
)
setContentView(paywallView)
}
}
}
}
}
}
```
```java showLineNumbers
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Adapty.getPaywall("YOUR_PLACEMENT_ID", paywallResult -> {
if (paywallResult instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) paywallResult).getValue();
if (!paywall.hasViewConfiguration()) {
// Use custom logic
return;
}
AdaptyUI.getViewConfiguration(paywall, configResult -> {
if (configResult instanceof AdaptyResult.Success) {
AdaptyUI.LocalizedViewConfiguration viewConfiguration =
((AdaptyResult.Success) configResult).getValue();
AdaptyPaywallView paywallView = AdaptyUI.getPaywallView(
this,
viewConfiguration,
null, // products = null means auto-fetch
new AdaptyUIEventListener() {
@Override
public void onActionPerformed(@NonNull AdaptyUI.Action action, @NonNull Context context) {
if (action instanceof AdaptyUI.Action.Close) {
if (context instanceof Activity) {
((Activity) context).onBackPressed();
}
}
}
}
);
setContentView(paywallView);
}
});
}
});
}
}
```
---
# File: android-check-subscription-status
---
---
title: "Kiểm tra trạng thái gói đăng ký trong Android SDK"
description: "Tìm hiểu cách kiểm tra trạng thái gói đăng ký trong ứng dụng Android của bạn với Adapty."
---
Để quyết định xem người dùng có thể truy cập nội dung trả phí hay cần hiển thị paywall, bạn cần kiểm tra [mức độ truy cập](access-level) của họ trong hồ sơ người dùng.
Bài viết này hướng dẫn cách truy cập trạng thái hồ sơ người dùng để quyết định hiển thị nội dung phù hợp — hiển thị paywall hay cấp quyền truy cập vào các tính năng trả phí.
## Lấy trạng thái gói đăng ký \{#get-subscription-status\}
Khi quyết định có nên hiển thị paywall hay nội dung trả phí cho người dùng, bạn kiểm tra [mức độ truy cập](access-level) trong hồ sơ người dùng của họ. Bạn có hai lựa chọn:
- Gọi `getProfile` nếu bạn cần dữ liệu hồ sơ mới nhất ngay lập tức (như khi khởi động ứng dụng) hoặc muốn buộc cập nhật.
- Thiết lập **cập nhật hồ sơ tự động** để lưu bản sao cục bộ được tự động làm mới mỗi khi trạng thái gói đăng ký thay đổi.
### Lấy hồ sơ người dùng \{#get-profile\}
Cách đơn giản nhất để lấy trạng thái gói đăng ký là sử dụng phương thức `getProfile` để truy cập hồ sơ người dùng:
```kotlin showLineNumbers
Adapty.getProfile { result ->
when (result) {
is AdaptyResult.Success -> {
val profile = result.value
// check the access
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getProfile(result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyProfile profile = ((AdaptyResult.Success) result).getValue();
// check the access
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
### Lắng nghe cập nhật gói đăng ký \{#listen-to-subscription-updates\}
Để tự động nhận cập nhật hồ sơ trong ứng dụng của bạn:
1. Dùng `Adapty.setOnProfileUpdatedListener()` để lắng nghe các thay đổi hồ sơ — Adapty sẽ tự động gọi phương thức này mỗi khi trạng thái gói đăng ký của người dùng thay đổi.
2. Lưu dữ liệu hồ sơ đã cập nhật khi phương thức này được gọi, để bạn có thể sử dụng trong toàn ứng dụng mà không cần thêm các yêu cầu mạng.
```kotlin
class SubscriptionManager {
private var currentProfile: AdaptyProfile? = null
init {
// Listen for profile updates
Adapty.setOnProfileUpdatedListener { profile ->
currentProfile = profile
// Update UI, unlock content, etc.
}
}
// Use stored profile instead of calling getProfile()
fun hasAccess(): Boolean {
return currentProfile?.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true
}
}
```
```java
public class SubscriptionManager {
private AdaptyProfile currentProfile;
public SubscriptionManager() {
// Listen for profile updates
Adapty.setOnProfileUpdatedListener(profile -> {
this.currentProfile = profile;
// Update UI, unlock content, etc.
});
}
// Use stored profile instead of calling getProfile()
public boolean hasAccess() {
if (currentProfile == null) {
return false;
}
AdaptyAccessLevel premiumAccess = currentProfile.getAccessLevels().get("YOUR_ACCESS_LEVEL");
return premiumAccess != null && premiumAccess.isActive();
}
}
```
:::note
Adapty tự động gọi listener cập nhật hồ sơ khi ứng dụng khởi động, cung cấp dữ liệu gói đăng ký đã được cache ngay cả khi thiết bị đang ngoại tuyến.
:::
## Kết nối hồ sơ với logic paywall \{#connect-profile-with-paywall-logic\}
Khi cần đưa ra quyết định ngay lập tức về việc hiển thị paywall hay cấp quyền truy cập vào các tính năng trả phí, bạn có thể kiểm tra trực tiếp hồ sơ người dùng. Cách tiếp cận này hữu ích trong các tình huống như: khi khởi động ứng dụng, khi vào các khu vực premium, hoặc trước khi hiển thị nội dung cụ thể.
```kotlin
private fun initializePaywall() {
loadPaywall { paywallView ->
checkAccessLevel { result ->
when (result) {
is AdaptyResult.Success -> {
if (!result.value && paywallView != null) {
setContentView(paywallView) // Show paywall if no access
}
}
is AdaptyResult.Error -> {
if (paywallView != null) {
setContentView(paywallView) // Show paywall if access check fails
}
}
}
}
}
}
private fun checkAccessLevel(callback: ResultCallback) {
Adapty.getProfile { result ->
when (result) {
is AdaptyResult.Success -> {
val hasAccess = result.value.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true
callback.onResult(AdaptyResult.Success(hasAccess))
}
is AdaptyResult.Error -> {
callback.onResult(AdaptyResult.Error(result.error))
}
}
}
}
```
```java
private void initializePaywall() {
loadPaywall(paywallView -> {
checkAccessLevel(result -> {
if (result instanceof AdaptyResult.Success) {
boolean hasAccess = ((AdaptyResult.Success) result).getValue();
if (!hasAccess && paywallView != null) {
setContentView(paywallView); // Show paywall if no access
}
} else if (result instanceof AdaptyResult.Error) {
if (paywallView != null) {
setContentView(paywallView); // Show paywall if access check fails
}
}
});
});
}
private void checkAccessLevel(ResultCallback callback) {
Adapty.getProfile(result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyProfile profile = ((AdaptyResult.Success) result).getValue();
AdaptyAccessLevel premiumAccess = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL");
boolean hasAccess = premiumAccess != null && premiumAccess.isActive();
callback.onResult(AdaptyResult.success(hasAccess));
} else if (result instanceof AdaptyResult.Error) {
callback.onResult(AdaptyResult.error(((AdaptyResult.Error) result).getError()));
}
});
}
```
## Các bước tiếp theo \{#next-steps\}
Bây giờ bạn đã biết cách theo dõi trạng thái gói đăng ký, hãy tìm hiểu cách [làm việc với hồ sơ người dùng](android-quickstart-identify) để đảm bảo họ có thể truy cập những gì họ đã trả tiền.
---
# File: android-quickstart-identify
---
---
title: "Xác định người dùng trong Android SDK"
description: "Hướng dẫn nhanh thiết lập Adapty để quản lý gói đăng ký in-app trên Android."
---
:::important
Hướng dẫn này dành cho bạn nếu bạn có hệ thống xác thực riêng. Tại đây, bạn sẽ học cách làm việc với hồ sơ người dùng trong Adapty để đảm bảo nó phù hợp với hệ thống xác thực hiện có của bạn.
:::
Cách bạn quản lý giao dịch mua của người dùng phụ thuộc vào mô hình xác thực của ứng dụng:
- Nếu ứng dụng không sử dụng xác thực backend và không lưu trữ dữ liệu người dùng, hãy xem [phần về người dùng ẩn danh](#anonymous-users).
- Nếu ứng dụng có (hoặc sẽ có) xác thực backend, hãy xem [phần về người dùng đã xác định](#identified-users).
**Các khái niệm chính**:
- **Hồ sơ người dùng** là các thực thể cần thiết để SDK hoạt động. Adapty tự động tạo chúng.
- Chúng có thể là ẩn danh **(không có customer user ID)** hoặc đã xác định **(có customer user ID)**.
- Bạn cung cấp **customer user ID** để liên kết chéo hồ sơ người dùng trong Adapty với hệ thống xác thực nội bộ của bạn.
Đây là sự khác biệt giữa người dùng ẩn danh và người dùng đã xác định:
| | Người dùng ẩn danh | Người dùng đã xác định |
|-------------------------|---------------------------------------------------|-------------------------------------------------------------------------|
| **Quản lý giao dịch mua** | Khôi phục giao dịch mua ở cấp độ cửa hàng | Duy trì lịch sử giao dịch mua trên các thiết bị thông qua customer user ID của họ |
| **Quản lý hồ sơ người dùng** | Hồ sơ người dùng mới sau mỗi lần cài đặt lại | Cùng một hồ sơ người dùng trên các phiên và thiết bị |
| **Lưu trữ dữ liệu** | Dữ liệu người dùng ẩn danh gắn với lần cài đặt ứng dụng | Dữ liệu người dùng đã xác định được lưu trữ qua các lần cài đặt ứng dụng |
## Người dùng ẩn danh \{#anonymous-users\}
Nếu bạn không có xác thực backend, **bạn không cần xử lý xác thực trong code ứng dụng**:
1. Khi SDK được kích hoạt lần đầu tiên khi khởi động ứng dụng, Adapty **tạo hồ sơ người dùng mới cho người dùng**.
2. Khi người dùng mua bất kỳ thứ gì trong ứng dụng, giao dịch mua này được **liên kết với hồ sơ người dùng Adapty và tài khoản cửa hàng của họ**.
3. Khi người dùng **cài đặt lại** ứng dụng hoặc cài đặt trên **thiết bị mới**, Adapty **tạo hồ sơ người dùng ẩn danh mới khi kích hoạt**.
4. Nếu người dùng đã từng mua hàng trong ứng dụng của bạn, theo mặc định, giao dịch mua của họ sẽ tự động được đồng bộ từ App Store khi kích hoạt SDK.
Với người dùng ẩn danh, hồ sơ người dùng mới sẽ được tạo sau mỗi lần cài đặt, nhưng điều đó không phải là vấn đề vì trong phân tích Adapty, bạn có thể [cấu hình những gì sẽ được tính là lần cài đặt mới](general#4-installs-definition-for-analytics).
Đối với người dùng ẩn danh, bạn cần đếm số lần cài đặt theo **ID thiết bị**. Trong trường hợp này, mỗi lần cài đặt ứng dụng trên thiết bị được tính là một lần cài đặt, bao gồm cả cài đặt lại.
## Người dùng đã xác định \{#identified-users\}
Bạn có hai lựa chọn để xác định người dùng trong ứng dụng:
- [**Trong quá trình đăng nhập/đăng ký:**](#during-loginsignup) Nếu người dùng đăng nhập sau khi ứng dụng khởi động, hãy gọi `identify()` với customer user ID khi họ xác thực.
- [**Trong quá trình kích hoạt SDK:**](#during-the-sdk-activation) Nếu bạn đã có customer user ID được lưu trữ khi ứng dụng khởi động, hãy gửi nó khi gọi `activate()`.
:::important
Theo mặc định, khi Adapty nhận được giao dịch mua từ Customer User ID hiện đang được liên kết với Customer User ID khác, mức độ truy cập sẽ được chia sẻ, vì vậy cả hai hồ sơ người dùng đều có quyền truy cập trả phí. Bạn có thể cấu hình cài đặt này để chuyển quyền truy cập trả phí từ hồ sơ người dùng này sang hồ sơ người dùng khác hoặc tắt hoàn toàn việc chia sẻ. Xem [bài viết](general#6-sharing-paid-access-between-user-accounts) để biết thêm chi tiết.
:::
### Trong quá trình đăng nhập/đăng ký \{#during-loginsignup\}
Nếu bạn xác định người dùng sau khi ứng dụng khởi động (ví dụ: sau khi họ đăng nhập vào ứng dụng hoặc đăng ký), hãy sử dụng phương thức `identify` để thiết lập customer user ID của họ.
- Nếu bạn **chưa từng sử dụng customer user ID này trước đây**, Adapty sẽ tự động liên kết nó với hồ sơ người dùng hiện tại.
- Nếu bạn **đã từng sử dụng customer user ID này để xác định người dùng trước đây**, Adapty sẽ chuyển sang làm việc với hồ sơ người dùng được liên kết với customer user ID này.
:::important
Customer user ID phải là duy nhất cho mỗi người dùng. Nếu bạn hardcode giá trị tham số, tất cả người dùng sẽ được coi là một người.
:::
Hãy chờ callback hoàn thành `identify` được kích hoạt trước khi gọi các phương thức SDK khác. Các cuộc gọi đồng thời có thể rơi vào hồ sơ người dùng ẩn danh thay vì hồ sơ người dùng đã xác định. Xem [Thứ tự gọi trong Android SDK](android-sdk-call-order).
```kotlin showLineNumbers
Adapty.identify("YOUR_USER_ID") { error -> // Unique for each user
if (error == null) {
// successful identify
}
}
```
```java showLineNumbers
// User IDs must be unique for each user
Adapty.identify("YOUR_USER_ID", error -> {
if (error == null) {
// successful identify
}
});
```
### Trong quá trình kích hoạt SDK \{#during-the-sdk-activation\}
Nếu bạn đã biết customer user ID khi kích hoạt SDK, bạn có thể gửi nó trong phương thức `activate` thay vì gọi `identify` riêng.
Nếu bạn biết customer user ID nhưng chỉ thiết lập nó sau khi kích hoạt, điều đó có nghĩa là khi kích hoạt, Adapty sẽ tạo hồ sơ người dùng ẩn danh mới và chỉ chuyển sang hồ sơ người dùng hiện có sau khi bạn gọi `identify`.
Bạn có thể truyền customer user ID đã có (cái bạn đã dùng trước đây) hoặc một cái mới. Nếu bạn truyền một cái mới, hồ sơ người dùng mới được tạo khi kích hoạt sẽ được tự động liên kết với customer user ID đó.
:::note
Theo mặc định, việc tạo hồ sơ người dùng ẩn danh không ảnh hưởng đến dashboard phân tích, vì số lần cài đặt được tính dựa trên ID thiết bị.
ID thiết bị đại diện cho một lần cài đặt ứng dụng duy nhất từ cửa hàng trên thiết bị và chỉ được tạo lại sau khi ứng dụng được cài đặt lại.
Nó không phụ thuộc vào việc đây là lần cài đặt đầu tiên hay lần cài đặt lặp lại, hay liệu có sử dụng customer user ID hiện có hay không.
Việc tạo hồ sơ người dùng (khi kích hoạt SDK hoặc đăng xuất), đăng nhập, hoặc nâng cấp ứng dụng mà không cài đặt lại không tạo ra sự kiện cài đặt bổ sung.
Nếu bạn muốn đếm số lần cài đặt dựa trên người dùng duy nhất thay vì thiết bị, hãy vào **App settings** và cấu hình [**Installs definition for analytics**](general#4-installs-definition-for-analytics).
:::
```kotlin showLineNumbers
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withCustomerUserId("user123") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one.
.build()
```
```java showLineNumbers
new AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withCustomerUserId("user123") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one.
.build();
```
### Đăng xuất người dùng \{#log-users-out\}
Nếu bạn có nút để đăng xuất người dùng, hãy sử dụng phương thức `logout`.
:::important
Đăng xuất người dùng sẽ tạo hồ sơ người dùng ẩn danh mới cho người dùng.
:::
```kotlin showLineNumbers
Adapty.logout { error ->
if (error == null) {
// successful logout
}
}
```
```java showLineNumbers
Adapty.logout(error -> {
if (error == null) {
// successful logout
}
});
```
:::info
Để đăng nhập lại người dùng vào ứng dụng, hãy sử dụng phương thức `identify`.
:::
### Cho phép mua hàng mà không cần đăng nhập \{#allow-purchases-without-login\}
Nếu người dùng có thể thực hiện giao dịch mua cả trước và sau khi đăng nhập vào ứng dụng, bạn cần đảm bảo rằng họ sẽ giữ quyền truy cập sau khi đăng nhập:
1. Khi người dùng chưa đăng nhập thực hiện giao dịch mua, Adapty liên kết nó với ID hồ sơ người dùng ẩn danh của họ.
2. Khi người dùng đăng nhập vào tài khoản, Adapty chuyển sang làm việc với hồ sơ người dùng đã xác định của họ.
- Nếu đây là customer user ID mới (ví dụ: giao dịch mua đã được thực hiện trước khi đăng ký), Adapty gán customer user ID cho hồ sơ người dùng hiện tại, vì vậy toàn bộ lịch sử giao dịch mua được duy trì.
- Nếu đây là customer user ID đã tồn tại (customer user ID đã được liên kết với hồ sơ người dùng), bạn cần lấy mức độ truy cập thực tế sau khi chuyển hồ sơ người dùng. Bạn có thể gọi [`getProfile`](android-check-subscription-status) ngay sau khi xác định, hoặc [lắng nghe cập nhật hồ sơ người dùng](android-check-subscription-status) để dữ liệu tự động đồng bộ.
## Bước tiếp theo \{#next-steps\}
Chúc mừng! Bạn đã triển khai logic thanh toán in-app trong ứng dụng của mình! Chúc bạn thành công với việc kiếm tiền từ ứng dụng!
Để khai thác tối đa Adapty, bạn có thể khám phá các chủ đề sau:
- [**Kiểm thử**](troubleshooting-test-purchases): Đảm bảo rằng mọi thứ hoạt động như mong đợi
- [**Onboardings**](android-onboardings): Thu hút người dùng bằng onboarding và tăng tỷ lệ giữ chân
- [**Tích hợp**](configuration): Tích hợp với dịch vụ attribution marketing và phân tích chỉ với một dòng code
- [**Thiết lập thuộc tính hồ sơ người dùng tùy chỉnh**](android-setting-user-attributes): Thêm thuộc tính tùy chỉnh vào hồ sơ người dùng và tạo phân khúc, để bạn có thể khởi chạy A/B test hoặc hiển thị các paywall khác nhau cho những người dùng khác nhau
---
# File: adapty-sdk-integration-skill-android
---
---
title: "Tích hợp Adapty vào ứng dụng Android với kỹ năng tích hợp SDK"
description: "Sử dụng kỹ năng adapty-sdk-integration để tích hợp Adapty SDK vào ứng dụng Android của bạn từ đầu đến cuối với công cụ lập trình AI."
---
:::important
Kỹ năng này đang trong giai đoạn beta. Nếu nó bị treo hoặc hoạt động không như mong đợi, hãy làm theo [hướng dẫn tích hợp từng bước](adapty-cursor-android) — hướng dẫn đó sẽ dẫn dắt công cụ AI của bạn qua từng giai đoạn với tài liệu phù hợp.
:::
---
no_index: true
---
[Skill adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) tự động hóa toàn bộ quá trình tích hợp Adapty: thiết lập dashboard, cài đặt SDK, paywall và xác minh từng giai đoạn. Skill tự động nhận diện nền tảng của bạn và tải tài liệu Adapty phù hợp ở mỗi giai đoạn.
**Công cụ được hỗ trợ**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI.
Để cài đặt, chọn lệnh phù hợp với công cụ của bạn. Danh sách đầy đủ có trong [README của skill](https://github.com/adaptyteam/adapty-sdk-integration-skill).
**Claude Code**
```
claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill
claude plugin install adapty-sdk-integration@adapty
```
**GitHub Copilot CLI**
```
gh skill install adaptyteam/adapty-sdk-integration-skill
```
**Gemini CLI**
```
gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill
```
**OpenAI Codex hoặc bất kỳ công cụ nào khác**: Clone repo và sao chép thư mục `plugins/adapty-sdk-integration/skills/adapty-sdk-integration/` vào thư mục skills của công cụ bạn đang dùng.
Sau khi cài đặt, chạy skill trong dự án của bạn:
```
/adapty-sdk-integration
```
Skill sẽ hỏi một vài câu hỏi thiết lập, sau đó hướng dẫn bạn qua các bước: thiết lập dashboard, cài đặt SDK, paywall và xác minh.
---
# File: adapty-cursor-android
---
---
title: "Tích hợp Adapty vào ứng dụng Android của bạn với sự hỗ trợ của AI"
description: "Hướng dẫn từng bước tích hợp Adapty vào ứng dụng Android của bạn bằng Cursor, Context7, ChatGPT, Claude, hoặc các công cụ AI khác."
---
Hướng dẫn này sẽ đưa bạn qua từng bước tích hợp Adapty vào ứng dụng Android với một công cụ AI — bạn chỉ cần cung cấp đúng tài liệu Adapty theo đúng thứ tự.
For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command.
## Trước khi bắt đầu: thiết lập dashboard \{#before-you-start-dashboard-setup\}
Adapty yêu cầu một số cấu hình trên dashboard trước khi bạn viết bất kỳ mã SDK nào. Bạn có thể thực hiện điều này bằng một LLM skill tương tác, hoặc thủ công thông qua Dashboard.
### Cách dùng Skill (khuyến nghị) \{#skill-approach-recommended\}
Adapty CLI skill cho phép LLM của bạn thiết lập ứng dụng, sản phẩm, mức độ truy cập, paywall và placement trực tiếp — mà không cần mở Dashboard cho từng bước. Bạn chỉ cần [kết nối cửa hàng của mình](integrate-payments) trong Dashboard.
```
npx skills add adaptyteam/adapty-cli --skill adapty-cli
```
Sau khi thêm skill, chạy `/adapty-cli` trong agent của bạn. Nó sẽ hướng dẫn bạn qua từng bước — bao gồm cả khi nào cần mở Dashboard để kết nối cửa hàng.
### Cách thủ công qua Dashboard \{#dashboard-approach\}
Nếu bạn muốn tự cấu hình mọi thứ, đây là những gì bạn cần trước khi viết code. LLM của bạn không thể tra cứu các giá trị từ dashboard — bạn sẽ phải tự cung cấp chúng.
1. **Kết nối cửa hàng ứng dụng của bạn**: Trong Adapty Dashboard, vào **App settings → General**. Đây là bước bắt buộc để các giao dịch mua hoạt động.
[Kết nối Google Play](integrate-payments)
2. **Sao chép Public SDK key của bạn**: Trong Adapty Dashboard, vào **App settings → General**, rồi tìm phần **API keys**. Trong code, đây là chuỗi bạn truyền vào Adapty configuration builder.
3. **Tạo ít nhất một sản phẩm**: Trong Adapty Dashboard, vào trang **Products**. Bạn không tham chiếu sản phẩm trực tiếp trong code — Adapty phân phối chúng thông qua paywall.
[Thêm sản phẩm](quickstart-products)
4. **Tạo một paywall và một placement**: Trong Adapty Dashboard, tạo paywall trên trang **Paywalls**, rồi gán nó vào một placement trên trang **Placements**. Trong code, placement ID là chuỗi bạn truyền vào `Adapty.getPaywall("YOUR_PLACEMENT_ID")`.
[Tạo paywall](quickstart-paywalls)
5. **Thiết lập mức độ truy cập**: Trong Adapty Dashboard, cấu hình theo từng sản phẩm trên trang **Products**. Trong code, chuỗi được kiểm tra trong `profile.accessLevels["premium"]?.isActive`. Mức độ truy cập `premium` mặc định phù hợp với hầu hết các ứng dụng. Nếu người dùng trả phí được truy cập các tính năng khác nhau tùy theo sản phẩm (ví dụ: gói `basic` so với gói `pro`), hãy [tạo thêm mức độ truy cập](assigning-access-level-to-a-product) trước khi bắt đầu viết code.
:::tip
Khi đã có đủ năm điều trên, bạn đã sẵn sàng viết code. Hãy cho LLM của bạn biết: "Public SDK key của tôi là X, placement ID của tôi là Y" để nó có thể tạo code khởi tạo và lấy paywall chính xác.
:::
### Thiết lập khi sẵn sàng \{#set-up-when-ready\}
Những mục này không bắt buộc để bắt đầu viết code, nhưng bạn sẽ cần chúng khi tích hợp trưởng thành hơn:
- **A/B test**: Cấu hình trên trang **Placements**. Không cần thay đổi code.
[A/B test](ab-tests)
- **Thêm paywall và placement**: Thêm các lời gọi `getPaywall` với các placement ID khác nhau.
- **Tích hợp analytics**: Cấu hình trên trang **Integrations**. Cài đặt khác nhau tùy theo tích hợp. Xem [tích hợp analytics](analytics-integration) và [tích hợp attribution](attribution-integration).
## Cung cấp tài liệu Adapty cho LLM của bạn \{#feed-adapty-docs-to-your-llm\}
### Dùng Context7 (khuyến nghị) \{#use-context7-recommended\}
[Context7](https://context7.com) là một MCP server cung cấp cho LLM của bạn quyền truy cập trực tiếp vào tài liệu Adapty mới nhất. LLM của bạn tự động lấy đúng tài liệu dựa trên những gì bạn hỏi — không cần dán URL thủ công.
Context7 hoạt động với **Cursor**, **Claude Code**, **Windsurf**, và các công cụ tương thích MCP khác. Để thiết lập, chạy:
```
npx ctx7 setup
```
Lệnh này tự động phát hiện editor của bạn và cấu hình Context7 server. Để thiết lập thủ công, xem [Context7 GitHub repository](https://github.com/upstash/context7).
Sau khi cấu hình, tham chiếu thư viện Adapty trong các prompt của bạn:
```
Use the adaptyteam/adapty-docs library to look up how to install the Android SDK
```
:::warning
Dù Context7 giúp bạn không cần dán link tài liệu thủ công, thứ tự triển khai vẫn rất quan trọng. Hãy làm theo [hướng dẫn triển khai](#implementation-walkthrough) bên dưới từng bước để đảm bảo mọi thứ hoạt động.
:::
### Dùng tài liệu dạng văn bản thuần \{#use-plain-text-docs\}
Bạn có thể truy cập bất kỳ tài liệu Adapty nào dưới dạng Markdown thuần. Thêm `.md` vào cuối URL, hoặc nhấp **Copy for LLM** bên dưới tiêu đề bài viết. Ví dụ: [adapty-cursor-android.md](https://adapty.io/docs/vi/adapty-cursor-android.md).
Mỗi bước trong [hướng dẫn triển khai](#implementation-walkthrough) bên dưới đều có một khối "Gửi cho LLM của bạn" với các link `.md` để dán vào.
Để có thêm tài liệu cùng một lúc, xem [file index và các tập con theo nền tảng](#plain-text-doc-index-files) bên dưới.
## Hướng dẫn triển khai \{#implementation-walkthrough\}
Phần còn lại của hướng dẫn này sẽ đi qua tích hợp Adapty theo đúng thứ tự triển khai. Mỗi bước bao gồm tài liệu cần gửi cho LLM, kết quả mong đợi khi hoàn thành, và các vấn đề thường gặp.
### Lên kế hoạch tích hợp \{#plan-your-integration\}
Trước khi bắt tay vào code, hãy yêu cầu LLM phân tích dự án của bạn và tạo một kế hoạch triển khai. Nếu công cụ AI của bạn hỗ trợ chế độ lên kế hoạch (như chế độ plan của Cursor hoặc Claude Code), hãy sử dụng nó để LLM có thể đọc cả cấu trúc dự án lẫn tài liệu Adapty trước khi viết code.
Hãy cho LLM biết bạn dùng cách tiếp cận nào cho việc mua hàng — điều này ảnh hưởng đến các hướng dẫn nó cần theo:
- [**Adapty Paywall Builder**](adapty-paywall-builder): Bạn tạo paywall trong trình tạo không cần code của Adapty, và SDK tự động render chúng.
- [**Paywall tự tạo**](android-making-purchases): Bạn tự xây dựng giao diện paywall trong code nhưng vẫn dùng Adapty để lấy sản phẩm và xử lý giao dịch mua.
- [**Observer mode**](observer-vs-full-mode): Bạn giữ nguyên hạ tầng mua hàng hiện có và chỉ dùng Adapty cho analytics và tích hợp.
Chưa biết chọn cái nào? Đọc [bảng so sánh trong quickstart](android-quickstart-paywalls).
### Cài đặt và cấu hình SDK \{#install-and-configure-the-sdk\}
Thêm dependency Adapty SDK qua Gradle trong Android Studio và kích hoạt nó bằng Public SDK key của bạn. Đây là nền tảng — mọi thứ khác đều không hoạt động nếu thiếu bước này.
**Hướng dẫn:** [Cài đặt & cấu hình Adapty SDK](sdk-installation-android)
Gửi nội dung này cho LLM của bạn:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/vi/sdk-installation-android.md
```
:::tip[Checkpoint]
- **Kết quả mong đợi:** Ứng dụng build và chạy được. Logcat hiển thị log kích hoạt Adapty.
- **Lưu ý:** "Public API key is missing" → kiểm tra xem bạn đã thay placeholder bằng key thật từ App settings chưa.
:::
### Hiển thị paywall và xử lý giao dịch mua \{#show-paywalls-and-handle-purchases\}
Lấy paywall theo placement ID, hiển thị nó và xử lý các sự kiện mua hàng. Các hướng dẫn bạn cần phụ thuộc vào cách bạn xử lý giao dịch mua.
Hãy test từng giao dịch mua trong sandbox khi bạn làm — đừng đợi đến cuối. Xem [Kiểm tra giao dịch mua trong sandbox](test-purchases-in-sandbox) để biết hướng dẫn thiết lập.
**Hướng dẫn:**
- [Kích hoạt giao dịch mua bằng paywall (quickstart)](android-quickstart-paywalls)
- [Lấy paywall Paywall Builder và cấu hình của chúng](android-get-pb-paywalls)
- [Hiển thị paywall](android-present-paywalls)
- [Xử lý sự kiện paywall](android-handling-events)
- [Phản hồi các hành động nút](android-handle-paywall-actions)
Gửi nội dung này cho LLM của bạn:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/vi/android-quickstart-paywalls.md
- https://adapty.io/docs/vi/android-get-pb-paywalls.md
- https://adapty.io/docs/vi/android-present-paywalls.md
- https://adapty.io/docs/vi/android-handling-events.md
- https://adapty.io/docs/vi/android-handle-paywall-actions.md
```
:::tip[Checkpoint]
- **Kết quả mong đợi:** Paywall hiển thị với các sản phẩm bạn đã cấu hình. Nhấn vào một sản phẩm sẽ kích hoạt hộp thoại mua hàng sandbox.
- **Lưu ý:** Paywall trống hoặc lỗi `getPaywall` → kiểm tra xem placement ID có khớp chính xác với dashboard không và placement đã được gán audience chưa.
:::
**Hướng dẫn:**
- [Kích hoạt giao dịch mua trong paywall tự tạo (quickstart)](android-quickstart-manual)
- [Lấy paywall và sản phẩm](fetch-paywalls-and-products-android)
- [Render paywall được thiết kế bằng Remote Config](present-remote-config-paywalls-android)
- [Thực hiện giao dịch mua](android-making-purchases)
- [Khôi phục giao dịch mua](android-restore-purchase)
Gửi nội dung này cho LLM của bạn:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/vi/android-quickstart-manual.md
- https://adapty.io/docs/vi/fetch-paywalls-and-products-android.md
- https://adapty.io/docs/vi/present-remote-config-paywalls-android.md
- https://adapty.io/docs/vi/android-making-purchases.md
- https://adapty.io/docs/vi/android-restore-purchase.md
```
:::tip[Checkpoint]
- **Kết quả mong đợi:** Paywall tự tạo của bạn hiển thị các sản phẩm lấy từ Adapty. Nhấn vào một sản phẩm sẽ kích hoạt hộp thoại mua hàng sandbox.
- **Lưu ý:** Mảng sản phẩm trống → kiểm tra xem paywall đã được gán sản phẩm trong dashboard chưa và placement đã có audience chưa.
:::
**Hướng dẫn:**
- [Tổng quan Observer mode](observer-vs-full-mode)
- [Triển khai Observer mode](implement-observer-mode-android)
- [Báo cáo giao dịch trong Observer mode](report-transactions-observer-mode-android)
Gửi nội dung này cho LLM của bạn:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/vi/observer-vs-full-mode.md
- https://adapty.io/docs/vi/implement-observer-mode-android.md
- https://adapty.io/docs/vi/report-transactions-observer-mode-android.md
```
:::tip[Checkpoint]
- **Kết quả mong đợi:** Sau khi mua hàng sandbox bằng flow mua hàng hiện có, giao dịch xuất hiện trong **Event Feed** trên Adapty dashboard.
- **Lưu ý:** Không có sự kiện → kiểm tra xem bạn đã báo cáo giao dịch cho Adapty chưa và Google Play Real-Time Developer Notifications đã được cấu hình chưa.
:::
### Kiểm tra trạng thái gói đăng ký \{#check-subscription-status\}
Sau khi mua hàng, kiểm tra hồ sơ người dùng để tìm mức độ truy cập đang hoạt động nhằm kiểm soát nội dung premium.
**Hướng dẫn:** [Kiểm tra trạng thái gói đăng ký](android-check-subscription-status)
Gửi nội dung này cho LLM của bạn:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/vi/android-check-subscription-status.md
```
:::tip[Checkpoint]
- **Kết quả mong đợi:** Sau khi mua hàng sandbox, `profile.accessLevels["premium"]?.isActive` trả về `true`.
- **Lưu ý:** `accessLevels` trống sau khi mua → kiểm tra xem sản phẩm đã được gán mức độ truy cập trong dashboard chưa.
:::
### Xác định người dùng \{#identify-users\}
Liên kết tài khoản người dùng trong ứng dụng của bạn với hồ sơ Adapty để giao dịch mua được duy trì trên nhiều thiết bị.
:::important
Bỏ qua bước này nếu ứng dụng của bạn không có xác thực.
:::
**Hướng dẫn:** [Xác định người dùng](android-quickstart-identify)
Gửi nội dung này cho LLM của bạn:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/vi/android-quickstart-identify.md
```
:::tip[Checkpoint]
- **Kết quả mong đợi:** Sau khi gọi `Adapty.identify("your-user-id")`, phần **Profiles** trên dashboard hiển thị custom user ID của bạn.
- **Lưu ý:** Gọi `identify` sau khi kích hoạt nhưng trước khi lấy paywall để tránh attribution hồ sơ ẩn danh.
:::
### Chuẩn bị phát hành \{#prepare-for-release\}
Khi tích hợp đã hoạt động trong sandbox, hãy đi qua danh sách kiểm tra phát hành để đảm bảo mọi thứ sẵn sàng cho môi trường production.
**Hướng dẫn:** [Danh sách kiểm tra phát hành](release-checklist)
Gửi nội dung này cho LLM của bạn:
```
Read these Adapty docs before releasing:
- https://adapty.io/docs/vi/release-checklist.md
```
:::tip[Checkpoint]
- **Kết quả mong đợi:** Tất cả các mục trong danh sách đã xác nhận: kết nối cửa hàng, thông báo server, flow mua hàng, kiểm tra mức độ truy cập và các yêu cầu về quyền riêng tư.
- **Lưu ý:** Thiếu Google Play Real-Time Developer Notifications → cấu hình trong **App settings → Android SDK** nếu không các sự kiện sẽ không xuất hiện trên dashboard.
:::
## File index tài liệu dạng văn bản thuần \{#plain-text-doc-index-files\}
Nếu bạn cần cung cấp cho LLM ngữ cảnh rộng hơn ngoài các trang riêng lẻ, chúng tôi cung cấp các file index tổng hợp toàn bộ tài liệu Adapty:
- [`llms.txt`](https://adapty.io/docs/vi/llms.txt): Liệt kê tất cả các trang với link `.md`. Đây là một [tiêu chuẩn đang nổi lên](https://llmstxt.org/) giúp website dễ tiếp cận hơn với LLM. Lưu ý rằng với một số AI agent (ví dụ: ChatGPT) bạn sẽ cần tải `llms.txt` về và tải lên chat dưới dạng file.
- [`llms-full.txt`](https://adapty.io/docs/vi/llms-full.txt): Toàn bộ tài liệu của Adapty được kết hợp thành một file duy nhất. Rất lớn — chỉ dùng khi bạn cần toàn cảnh.
- Dành riêng cho Android: [`android-llms.txt`](https://adapty.io/docs/vi/android-llms.txt) và [`android-llms-full.txt`](https://adapty.io/docs/vi/android-llms-full.txt): Các tập con theo nền tảng giúp tiết kiệm token so với toàn bộ site.
---
# File: android-get-pb-paywalls
---
---
title: "Lấy paywall được thiết kế bằng Paywall Builder và cấu hình của nó trong Android SDK"
description: "Tìm hiểu cách lấy PB paywalls trong Adapty để kiểm soát gói đăng ký tốt hơn trong ứng dụng Android của bạn."
---
Sau khi [thiết kế phần giao diện cho paywall](adapty-paywall-builder) bằng Paywall Builder mới trong Adapty Dashboard, bạn có thể hiển thị nó trong ứng dụng mobile của mình. Bước đầu tiên trong quy trình này là lấy paywall gắn với placement cùng cấu hình view của nó như mô tả bên dưới.
:::warning
Paywall Builder mới hoạt động với Android SDK phiên bản 3.0 trở lên.
:::
Lưu ý rằng chủ đề này đề cập đến các paywall được tùy chỉnh bằng Paywall Builder. Nếu bạn đang triển khai paywall theo cách thủ công, vui lòng tham khảo chủ đề [Lấy paywalls và sản phẩm cho remote config paywalls trong ứng dụng mobile](fetch-paywalls-and-products-android).
:::tip
Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác.
:::
Trước khi bắt đầu hiển thị paywalls trong ứng dụng mobile của bạn (nhấn để mở rộng)
1. [Tạo sản phẩm](create-product) trong Adapty Dashboard.
2. [Tạo một paywall và thêm sản phẩm vào đó](create-paywall) trong Adapty Dashboard.
3. [Tạo các placement và thêm paywall vào đó](create-placement) trong Adapty Dashboard.
4. Cài đặt [Adapty SDK](sdk-installation-android) trong ứng dụng mobile của bạn.
## Lấy paywall được thiết kế bằng Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\}
Nếu bạn đã [thiết kế paywall bằng Paywall Builder](adapty-paywall-builder), bạn không cần lo lắng về việc render nó trong code ứng dụng để hiển thị cho người dùng. Một paywall như vậy chứa cả nội dung cần hiển thị lẫn cách thức hiển thị. Tuy nhiên, bạn vẫn cần lấy ID của nó qua placement, cấu hình view, rồi trình bày nó trong ứng dụng mobile.
Để đảm bảo hiệu suất tốt nhất, việc lấy paywall và [cấu hình view](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) càng sớm càng tốt là rất quan trọng, cho phép đủ thời gian để tải hình ảnh trước khi hiển thị cho người dùng.
Để lấy một paywall, sử dụng phương thức `getPaywall`:
```kotlin showLineNumbers
...
Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en", loadTimeout = 10.seconds) { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
// the requested paywall
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
...
Adapty.getPaywall("YOUR_PLACEMENT_ID", "en", TimeInterval.seconds(10), result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
// the requested paywall
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
Tham số:
| Tham số | Bắt buộc | Mô tả |
|---------|--------|-----------|
| **placementId** | bắt buộc | Định danh của [Placement](placements) mong muốn. Đây là giá trị bạn đã chỉ định khi tạo placement trong Adapty Dashboard. |
| **locale** | tùy chọn
mặc định: `en`
| Định danh của [bản địa hóa paywall](add-paywall-locale-in-adapty-paywall-builder). Tham số này cần là mã ngôn ngữ gồm một hoặc hai thẻ con phân tách bằng ký tự gạch ngang (**-**). Thẻ con đầu tiên là ngôn ngữ, thẻ con thứ hai là vùng.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha Brazil.
Xem [Bản địa hóa và mã locale](localizations-and-locale-codes) để biết thêm thông tin về mã locale và cách chúng tôi khuyến nghị sử dụng.
|
| **fetchPolicy** | mặc định: `.reloadRevalidatingCacheData` | Theo mặc định, SDK sẽ cố tải dữ liệu từ server và trả về dữ liệu đã cache trong trường hợp thất bại. Chúng tôi khuyến nghị cách này vì nó đảm bảo người dùng luôn nhận được dữ liệu mới nhất.
Tuy nhiên, nếu bạn cho rằng người dùng của mình có kết nối internet không ổn định, hãy cân nhắc sử dụng `.returnCacheDataElseLoad` để trả về dữ liệu đã cache nếu có. Trong trường hợp này, người dùng có thể không nhận được dữ liệu mới nhất tuyệt đối, nhưng sẽ có thời gian tải nhanh hơn, bất kể kết nối internet của họ có chập chờn đến đâu. Cache được cập nhật thường xuyên, nên an toàn khi sử dụng trong suốt phiên để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn còn nguyên sau khi khởi động lại ứng dụng và chỉ bị xóa khi gỡ cài đặt ứng dụng hoặc dọn dẹp thủ công.
Adapty SDK lưu trữ paywalls cục bộ theo hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và [paywall dự phòng](fallback-paywalls). Chúng tôi cũng dùng CDN để tải paywalls nhanh hơn và một server dự phòng độc lập trong trường hợp CDN không thể truy cập. Hệ thống này được thiết kế để đảm bảo bạn luôn nhận được phiên bản mới nhất của paywalls trong khi vẫn đảm bảo độ tin cậy ngay cả khi kết nối internet kém.
|
| **loadTimeout** | mặc định: 5 giây | Giá trị này giới hạn thời gian chờ cho phương thức này. Nếu hết thời gian chờ, dữ liệu đã cache hoặc fallback cục bộ sẽ được trả về.
Lưu ý rằng trong một số trường hợp hiếm gặp, phương thức này có thể hết thời gian chờ muộn hơn một chút so với `loadTimeout` đã chỉ định, vì thao tác có thể bao gồm nhiều yêu cầu bên dưới.
Đối với Android: Bạn có thể tạo `TimeInterval` với các hàm mở rộng (như `5.seconds`, trong đó `.seconds` được import từ `import com.adapty.utils.seconds`), hoặc `TimeInterval.seconds(5)`. Để không giới hạn, sử dụng `TimeInterval.INFINITE`.
|
Tham số phản hồi:
| Tham số | Mô tả |
| :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Paywall | Một đối tượng [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) với danh sách ID sản phẩm, định danh paywall, Remote Config và một số thuộc tính khác. |
## Lấy cấu hình view của paywall được thiết kế bằng Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\}
:::important
Đảm bảo bật toggle **Show on device** trong paywall builder. Nếu tùy chọn này chưa được bật, cấu hình view sẽ không thể lấy được.
:::
Sau khi lấy paywall, hãy kiểm tra xem nó có chứa `ViewConfiguration` không — điều này cho biết paywall được tạo bằng Paywall Builder. Thông tin này sẽ hướng dẫn bạn cách hiển thị paywall. Nếu có `ViewConfiguration`, hãy xử lý nó như một paywall Paywall Builder; nếu không, [xử lý nó như một remote config paywall](present-remote-config-paywalls).
Sử dụng phương thức `getViewConfiguration` để tải cấu hình view.
```kotlin showLineNumbers
if (!paywall.hasViewConfiguration) {
// use your custom logic
return
}
AdaptyUI.getViewConfiguration(paywall, loadTimeout = 10.seconds) { result ->
when(result) {
is AdaptyResult.Success -> {
val viewConfiguration = result.value
// use loaded configuration
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
| Tham số | Bắt buộc | Mô tả |
| :-------------- | :------------- | :----------------------------------------------------------- |
| **paywall** | bắt buộc | Một đối tượng `AdaptyPaywall` để lấy controller cho paywall mong muốn. |
| **loadTimeout** | mặc định: 5 giây | Giá trị này giới hạn thời gian chờ cho phương thức này. Nếu hết thời gian chờ, dữ liệu đã cache hoặc fallback cục bộ sẽ được trả về. Lưu ý rằng trong một số trường hợp hiếm gặp, phương thức này có thể hết thời gian chờ muộn hơn một chút so với `loadTimeout` đã chỉ định, vì thao tác có thể bao gồm nhiều yêu cầu bên dưới. |
Sử dụng phương thức `getViewConfiguration` để tải cấu hình view.
```java showLineNumbers
if (!paywall.hasViewConfiguration()) {
// use your custom logic
return;
}
AdaptyUI.getViewConfiguration(paywall, TimeInterval.seconds(10), result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyUI.LocalizedViewConfiguration viewConfiguration =
((AdaptyResult.Success) result).getValue();
// use loaded configuration
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
| Tham số | Bắt buộc | Mô tả |
| :----------------------- | :------------- | :----------------------------------------------------------- |
| **paywall** | bắt buộc | Một đối tượng `AdaptyPaywall` để lấy controller cho paywall mong muốn. |
| **loadTimeout** | mặc định: 5 giây | Giá trị này giới hạn thời gian chờ cho phương thức này. Nếu hết thời gian chờ, dữ liệu đã cache hoặc fallback cục bộ sẽ được trả về. Lưu ý rằng trong một số trường hợp hiếm gặp, phương thức này có thể hết thời gian chờ muộn hơn một chút so với `loadTimeout` đã chỉ định, vì thao tác có thể bao gồm nhiều yêu cầu bên dưới. |
:::note
Nếu bạn đang sử dụng nhiều ngôn ngữ, hãy tìm hiểu cách thêm [bản địa hóa Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) và cách sử dụng mã locale đúng cách [tại đây](android-localizations-and-locale-codes).
:::
Sau khi tải xong, [trình bày paywall](android-present-paywalls).
## Lấy paywall cho đối tượng mặc định để tải nhanh hơn \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\}
Thông thường, paywalls được tải gần như ngay lập tức, nên bạn không cần lo lắng về việc tăng tốc quá trình này. Tuy nhiên, trong trường hợp bạn có nhiều đối tượng và paywalls, và người dùng có kết nối internet yếu, việc tải paywall có thể mất nhiều thời gian hơn mong muốn. Trong tình huống đó, bạn có thể muốn hiển thị một paywall mặc định để đảm bảo trải nghiệm người dùng mượt mà thay vì không hiển thị paywall nào cả.
Để giải quyết vấn đề này, bạn có thể sử dụng phương thức `getPaywallForDefaultAudience`, phương thức này lấy paywall của placement được chỉ định cho đối tượng **All Users**. Tuy nhiên, điều quan trọng cần hiểu là cách tiếp cận được khuyến nghị là lấy paywall bằng phương thức `getPaywall`, như đã mô tả chi tiết trong phần [Lấy thông tin Paywall](#fetch-paywall-designed-with-paywall-builder) ở trên.
:::warning
Tại sao chúng tôi khuyến nghị sử dụng `getPaywall`
Phương thức `getPaywallForDefaultAudience` có một số nhược điểm đáng kể:
- **Vấn đề tương thích ngược tiềm ẩn**: Nếu bạn cần hiển thị các paywall khác nhau cho các phiên bản ứng dụng khác nhau (hiện tại và tương lai), bạn có thể gặp khó khăn. Bạn sẽ phải thiết kế các paywall hỗ trợ phiên bản hiện tại (cũ) hoặc chấp nhận rằng người dùng có phiên bản hiện tại (cũ) có thể gặp sự cố với các paywall không được render.
- **Mất khả năng targeting**: Tất cả người dùng sẽ thấy cùng một paywall được thiết kế cho đối tượng **All Users**, có nghĩa là bạn mất khả năng targeting cá nhân hóa (bao gồm theo quốc gia, marketing attribution hoặc các thuộc tính tùy chỉnh của bạn).
Nếu bạn sẵn sàng chấp nhận những nhược điểm này để được lợi từ việc tải paywall nhanh hơn, hãy sử dụng phương thức `getPaywallForDefaultAudience` như sau. Nếu không, hãy tiếp tục dùng `getPaywall` như đã mô tả [ở trên](#fetch-paywall-designed-with-paywall-builder).
:::
```kotlin showLineNumbers
Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
// the requested paywall
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
// the requested paywall
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
:::note
Phương thức `getPaywallForDefaultAudience` có sẵn từ Android SDK 2.11.3 trở lên
:::
| Tham số | Bắt buộc | Mô tả |
|---------|--------|-----------|
| **placementId** | bắt buộc | Định danh của [Placement](placements). Đây là giá trị bạn đã chỉ định khi tạo placement trong Adapty Dashboard. |
| **locale** | tùy chọn
mặc định: `en`
| Định danh của [bản địa hóa paywall](add-remote-config-locale). Tham số này cần là mã ngôn ngữ gồm một hoặc nhiều thẻ con phân tách bằng ký tự gạch ngang (**-**). Thẻ con đầu tiên là ngôn ngữ, thẻ con thứ hai là vùng.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha Brazil.
Xem [Bản địa hóa và mã locale](localizations-and-locale-codes) để biết thêm thông tin về mã locale và cách chúng tôi khuyến nghị sử dụng.
|
| **fetchPolicy** | mặc định: `.reloadRevalidatingCacheData` | Theo mặc định, SDK sẽ cố tải dữ liệu từ server và trả về dữ liệu đã cache trong trường hợp thất bại. Chúng tôi khuyến nghị cách này vì nó đảm bảo người dùng luôn nhận được dữ liệu mới nhất.
Tuy nhiên, nếu bạn cho rằng người dùng của mình có kết nối internet không ổn định, hãy cân nhắc sử dụng `.returnCacheDataElseLoad` để trả về dữ liệu đã cache nếu có. Trong trường hợp này, người dùng có thể không nhận được dữ liệu mới nhất tuyệt đối, nhưng sẽ có thời gian tải nhanh hơn, bất kể kết nối internet của họ có chập chờn đến đâu. Cache được cập nhật thường xuyên, nên an toàn khi sử dụng trong suốt phiên để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn còn nguyên sau khi khởi động lại ứng dụng và chỉ bị xóa khi gỡ cài đặt ứng dụng hoặc dọn dẹp thủ công.
|
## Tùy chỉnh assets \{#customize-assets\}
Để tùy chỉnh hình ảnh và video trong paywall, hãy triển khai các custom assets.
Hình ảnh hero và video có các ID được định nghĩa sẵn: `hero_image` và `hero_video`. Trong một bundle custom asset, bạn nhắm đến các phần tử này bằng ID của chúng và tùy chỉnh hành vi của chúng.
Đối với các hình ảnh và video khác, bạn cần [đặt ID tùy chỉnh](custom-media) trong Adapty dashboard.
Ví dụ, bạn có thể:
- Hiển thị hình ảnh hoặc video khác cho một số người dùng.
- Hiển thị ảnh xem trước cục bộ trong khi hình ảnh chính từ xa đang tải.
- Hiển thị ảnh xem trước trước khi chạy video.
:::important
Để sử dụng tính năng này, hãy cập nhật Adapty Android SDK lên phiên bản 3.7.0 trở lên.
:::
Đây là ví dụ về cách bạn có thể cung cấp custom assets thông qua một dictionary đơn giản:
```kotlin showLineNumbers
val customAssets = AdaptyCustomAssets.of(
"hero_image" to
AdaptyCustomImageAsset.remote(
url = "https://example.com/image.jpg",
preview = AdaptyCustomImageAsset.file(
FileLocation.fromAsset("images/hero_image_preview.png"),
)
),
"hero_video" to
AdaptyCustomVideoAsset.file(
FileLocation.fromResId(requireContext(), R.raw.custom_video),
preview = AdaptyCustomImageAsset.file(
FileLocation.fromResId(requireContext(), R.drawable.video_preview),
),
),
)
val paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
products,
eventListener,
insets,
customAssets,
)
```
:::note
Nếu không tìm thấy asset, paywall sẽ quay về giao diện mặc định của nó.
:::
---
# File: android-present-paywalls
---
---
title: "Android - Hiển thị paywall mới với Paywall Builder"
description: "Tìm hiểu cách hiển thị paywall trên Android để tối ưu hóa monetization."
---
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 để hiển thị cho người dùng. Paywall đó đã bao gồm cả nội dung cần hiển thị lẫn cách hiển thị.
:::warning
Hướng dẫn này chỉ dành cho **paywall Paywall Builder mới** yêu cầu SDK v3.0. Quy trình hiển thị paywall khác nhau tùy theo phiên bản Paywall Builder được sử dụng, paywall remote config, và [chế độ Observer](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).
- Để hiển thị **paywall ở chế độ Observer**, xem [Android - Hiển thị paywall Paywall Builder trong chế độ Observer](android-present-paywall-builder-paywalls-in-observer-mode)
:::
Để lấy đối tượng `viewConfiguration` được dùng bên dưới, xem [Fetch paywall Paywall Builder và cấu hình của chúng](android-get-pb-paywalls).
Để hiển thị paywall trực quan trên màn hình thiết bị, bạn phải cấu hình nó trước. Để làm điều này, gọi phương thức `AdaptyUI.getPaywallView()` hoặc tạo `AdaptyPaywallView` trực tiếp:
```kotlin showLineNumbers
val paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
products,
eventListener,
insets,
personalizedOfferResolver,
tagResolver,
timerResolver,
)
```
```kotlin showLineNumbers
val paywallView =
AdaptyPaywallView(activity) // or retrieve it from xml
...
with(paywallView) {
showPaywall(
viewConfiguration,
products,
eventListener,
insets,
personalizedOfferResolver,
tagResolver,
timerResolver,
)
}
```
```java showLineNumbers
AdaptyPaywallView paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
products,
eventListener,
insets,
personalizedOfferResolver,
tagResolver,
timerResolver
);
```
```java showLineNumbers
AdaptyPaywallView paywallView =
new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml
...
paywallView.showPaywall(viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver);
```
```xml showLineNumbers
```
Sau khi view được tạo thành công, bạn có thể thêm nó vào hệ thống view và hiển thị trên màn hình thiết bị.
Nếu bạn lấy `AdaptyPaywallView` _không_ thông qua `AdaptyUI.getPaywallView()`, bạn cũng cần gọi phương thức `.showPaywall()`.
Để hiển thị paywall trực quan trên màn hình thiết bị, bạn phải cấu hình nó trước. Để làm điều này, sử dụng composable function sau:
```kotlin showLineNumbers
AdaptyPaywallScreen(
viewConfiguration,
products,
eventListener,
insets,
personalizedOfferResolver,
tagResolver,
timerResolver,
)
```
Các tham số yêu cầu:
| Tham số | Bắt buộc | Mô tả |
| :---------------------------- | :------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **viewConfiguration** | bắt buộc | Cung cấp đối tượng `AdaptyUI.LocalizedViewConfiguration` chứa thông tin trực quan của paywall. Dùng phương thức `Adapty.getViewConfiguration(paywall)` để tải nó. Tham khảo chủ đề [Fetch cấu hình trực quan của paywall](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) để biết thêm chi tiết. |
| **products** | tùy chọn | Cung cấp mảng `AdaptyPaywallProduct` để tối ưu thời điểm hiển thị sản phẩm trên màn hình. Nếu truyền `null`, AdaptyUI sẽ tự động fetch các sản phẩm cần thiết. |
| **eventListener** | tùy chọn | Cung cấp `AdaptyUiEventListener` để theo dõi các sự kiện paywall. Nên extend `AdaptyUiDefaultEventListener` để dễ sử dụng hơn. Tham khảo chủ đề [Xử lý sự kiện paywall](android-handling-events) để biết thêm chi tiết. |
| **insets** | tùy chọn | Insets là các khoảng cách xung quanh paywall để ngăn các phần tử tương tác bị ẩn sau thanh hệ thống.
Mặc định: `UNSPECIFIED` — Adapty sẽ tự động điều chỉnh insets, phù hợp với paywall edge-to-edge.
Nếu paywall của bạn không phải edge-to-edge, bạn có thể muốn đặt insets tùy chỉnh. Cách thực hiện được mô tả trong phần [Thay đổi insets paywall](android-present-paywalls#change-paywall-insets) bên dưới.
|
| **personalizedOfferResolver** | tùy chọn | Để chỉ định giá cá nhân hóa ([đọc thêm](https://developer.android.com/google/play/billing/integrate#personalized-price)), hãy implement `AdaptyUiPersonalizedOfferResolver` và truyền logic của bạn để ánh xạ `AdaptyPaywallProduct` thành `true` nếu giá sản phẩm được cá nhân hóa, ngược lại là `false`. |
| **tagResolver** | tùy chọn | Dùng `AdaptyUiTagResolver` để resolve các custom tag trong văn bản paywall. Resolver này nhận tham số tag và resolve thành chuỗi tương ứng. Tham khảo chủ đề Custom tags trong Paywall Builder để biết thêm chi tiết. |
| **timerResolver** | tùy chọn | Truyền resolver vào đây nếu bạn sử dụng chức năng timer tùy chỉnh. |
:::tip
Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác.
:::
## Thay đổi insets paywall \{#change-paywall-insets\}
Insets là các khoảng cách xung quanh paywall để ngăn các phần tử tương tác bị ẩn sau thanh hệ thống. Mặc định, Adapty sẽ tự động điều chỉnh insets, phù hợp với paywall edge-to-edge.
Nếu paywall của bạn không phải edge-to-edge, bạn có thể muốn đặt insets tùy chỉnh:
- Nếu cả thanh trạng thái lẫn thanh điều hướng đều không che `AdaptyPaywallView`, dùng `AdaptyPaywallInsets.NONE`.
- Với các cấu hình tùy chỉnh hơn, ví dụ nếu paywall của bạn chồng lên thanh trạng thái trên cùng nhưng không chồng lên phần dưới, bạn có thể chỉ đặt `bottomInset` thành `0` như ví dụ bên dưới:
```kotlin showLineNumbers
//create extension function
fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) {
ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
ViewCompat.setOnApplyWindowInsetsListener(this, null)
action(systemBarInsets)
insets
}
}
//and then use it with the view
paywallView.onReceiveSystemBarsInsets { insets ->
val paywallInsets = AdaptyPaywallInsets.vertical(insets.top, 0)
paywallView.showPaywall(
viewConfiguration,
products,
eventListener,
paywallInsets,
personalizedOfferResolver,
tagResolver,
timerResolver,
)
}
```
```java showLineNumbers
...
ViewCompat.setOnApplyWindowInsetsListener(paywallView, (view, insets) -> {
Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars());
ViewCompat.setOnApplyWindowInsetsListener(paywallView, null);
AdaptyPaywallInsets paywallInsets =
AdaptyPaywallInsets.of(systemBarInsets.top, 0);
paywallView.showPaywall(paywall, products, viewConfiguration, paywallInsets, productTitleResolver);
return insets;
});
```
## Sử dụng timer do lập trình viên định nghĩa \{#use-developer-defined-timer\}
Để sử dụng timer tùy chỉnh trong ứng dụng, hãy tạo đối tượng `timerResolver`—một dictionary hoặc map ánh xạ các timer tùy chỉnh với giá trị chuỗi sẽ thay thế chúng khi paywall được render. Ví dụ:
```kotlin showLineNumbers
...
val customTimers = mapOf(
"CUSTOM_TIMER_NY" to Calendar.getInstance(TimeZone.getDefault()).apply { set(2025, 0, 1) }.time, // New Year 2025
)
val timerResolver = AdaptyUiTimerResolver { timerId ->
customTimers.getOrElse(timerId, { Date(System.currentTimeMillis() + 3600 * 1000L) /* in 1 hour */ } )
}
```
```java showLineNumbers
...
Map customTimers = new HashMap<>();
customTimers.put(
"CUSTOM_TIMER_NY",
new Calendar.Builder().setTimeZone(TimeZone.getDefault()).setDate(2025, 0, 1).build().getTime()
);
AdaptyUiTimerResolver timerResolver = new AdaptyUiTimerResolver() {
@NonNull
@Override
public Date timerEndAtDate(@NonNull String timerId) {
Date date = customTimers.get(timerId);
return date != null ? date : new Date(System.currentTimeMillis() + 3600 * 1000L); /* in 1 hour */
}
};
```
Trong ví dụ này, `CUSTOM_TIMER_NY` là **Timer ID** của timer do lập trình viên định nghĩa mà bạn đã thiết lập trong Adapty dashboard. `timerResolver` đảm bảo ứng dụng của bạn cập nhật động timer với giá trị chính xác—ví dụ như `13d 09h 03m 34s` (được tính bằng thời điểm kết thúc timer, chẳng hạn Ngày Đầu Năm Mới, trừ đi thời gian hiện tại).
## Sử dụng custom tag \{#use-custom-tags\}
Để sử dụng custom tag trong ứng dụng, hãy tạo đối tượng `tagResolver`—một dictionary hoặc map ánh xạ các custom tag với giá trị chuỗi sẽ thay thế chúng khi paywall được render. Ví dụ:
```kotlin showLineNumbers
val customTags = mapOf("USERNAME" to "John")
val tagResolver = AdaptyUiTagResolver { tag -> customTags[tag] }
```
```java showLineNumbers
Map customTags = new HashMap<>();
customTags.put("USERNAME", "John");
AdaptyUiTagResolver tagResolver = customTags::get;
```
Trong ví dụ này, `USERNAME` là custom tag bạn đã nhập trong Adapty dashboard dưới dạng ``. `tagResolver` đảm bảo ứng dụng của bạn tự động thay thế custom tag này bằng giá trị được chỉ định—ví dụ như `John`.
Chúng tôi khuyến nghị tạo và điền vào `tagResolver` ngay trước khi hiển thị paywall. Khi đã sẵn sàng, truyền nó vào phương thức AdaptyUI mà bạn dùng để hiển thị paywall.
## Thay đổi màu loading indicator của paywall \{#change-paywall-loading-indicator-color\}
Bạn có thể ghi đè màu mặc định của loading indicator theo cách sau:
```xml showLineNumbers title = "XML"
```
---
# File: android-handle-paywall-actions
---
---
title: "Xử lý hành động nút trong Android SDK"
description: "Xử lý các hành động nút trên paywall trong Android sử dụng Adapty để tối ưu hóa việc kiếm tiền từ ứng dụng."
---
Nếu bạn đang xây dựng paywall bằng Adapty Paywall Builder, việc thiết lập các nút đúng cách là rất quan trọng:
1. Thêm [nút trong paywall builder](paywall-buttons) và gán cho nó một hành động có sẵn hoặc tạo một ID hành động tùy chỉnh.
2. Viết code trong ứng dụng của bạn để xử lý từng hành động đã gán.
Hướng dẫn này hướng dẫn cách xử lý các hành động tùy chỉnh và hành động có sẵn trong code của bạn.
:::warning
**Chỉ các hành động mua hàng, khôi phục, đóng paywall và mở URL được xử lý tự động.** Tất cả các hành động nút khác đều yêu cầu triển khai xử lý phù hợp trong code ứng dụng.
:::
## Đóng paywall \{#close-paywalls\}
Để thêm nút đóng paywall của bạn:
1. Trong paywall builder, thêm một nút và gán cho nó hành động **Close**.
2. Trong code ứng dụng, triển khai một handler cho hành động `close` để đóng paywall.
:::info
Trong Android SDK, hành động `close` mặc định sẽ kích hoạt việc đóng paywall. Tuy nhiên, bạn có thể ghi đè hành vi này trong code nếu cần. Ví dụ, đóng một paywall có thể kích hoạt mở paywall khác.
:::
```kotlin
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior
}
}
```
## Mở URL từ paywall \{#open-urls-from-paywalls\}
:::tip
Nếu bạn muốn thêm một nhóm liên kết (ví dụ: điều khoản sử dụng và khôi phục mua hàng), hãy thêm phần tử **Link** trong paywall builder và xử lý nó theo cách tương tự như các nút với hành động **Open URL**.
:::
Để thêm nút mở liên kết từ paywall của bạn (ví dụ: **Terms of use** hoặc **Privacy policy**):
1. Trong paywall builder, thêm một nút, gán cho nó hành động **Open URL**, và nhập URL bạn muốn mở.
2. Trong code ứng dụng, triển khai một handler cho hành động `openUrl` để mở URL nhận được trong trình duyệt.
:::info
Trong Android SDK, hành động `openUrl` mặc định sẽ kích hoạt việc mở URL. Tuy nhiên, bạn có thể ghi đè hành vi này trong code nếu cần.
:::
```kotlin
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
is AdaptyUI.Action.OpenUrl -> {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(action.url)) // default behavior
context.startActivity(intent)
}
}
}
```
## Đăng nhập vào ứng dụng \{#log-into-the-app\}
Để thêm nút cho phép người dùng đăng nhập vào ứng dụng:
1. Trong paywall builder, thêm một nút và gán cho nó hành động **Login**.
2. Trong code ứng dụng, triển khai một handler cho hành động `login` để xác thực người dùng.
```kotlin
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
AdaptyUI.Action.Login -> {
val intent = Intent(context, LoginActivity::class.java)
context.startActivity(intent)
}
}
}
```
## Xử lý hành động tùy chỉnh \{#handle-custom-actions\}
Để thêm nút xử lý các hành động khác:
1. Trong paywall builder, thêm một nút, gán cho nó hành động **Custom**, và gán cho nó một ID.
2. Trong code ứng dụng, triển khai một handler cho ID hành động bạn đã tạo.
Ví dụ, nếu bạn có một bộ ưu đãi gói đăng ký khác hoặc sản phẩm mua một lần, bạn có thể thêm một nút để hiển thị paywall khác:
```kotlin
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
is AdaptyUI.Action.Custom -> {
if (action.customId == "openNewPaywall") {
// Display another paywall
}
}
}
}
```
---
# File: android-handling-events
---
---
title: "Android - Xử lý sự kiện paywall"
description: "Xử lý hiệu quả các sự kiện đăng ký trên Android với các công cụ theo dõi sự kiện của Adapty."
---
:::important
Hướng dẫn này đề cập đến việc xử lý sự kiện cho các giao dịch mua, khôi phục, chọn sản phẩm và hiển thị paywall. Bạn cũng cần triển khai xử lý nút bấm (đóng paywall, mở liên kết, v.v.). Xem [hướng dẫn xử lý hành động nút bấm](android-handle-paywall-actions) để biết thêm chi tiết.
:::
Các paywall được cấu hình bằng [Paywall Builder](adapty-paywall-builder) không cần thêm code để thực hiện và khôi phục giao dịch mua. Tuy nhiên, chúng tạo ra một số sự kiện mà ứng dụng của bạn có thể phản hồi. Những sự kiện đó bao gồm các thao tác nhấn nút (nút đóng, URL, chọn sản phẩm, v.v.) cũng như thông báo về các hành động liên quan đến giao dịch mua được thực hiện trên paywall. Tìm hiểu cách phản hồi các sự kiện này bên dưới.
:::warning
Hướng dẫn này chỉ dành cho **paywall Paywall Builder mới** yêu cầu Adapty SDK v3.0 trở lên.
:::
:::tip
Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác.
:::
Nếu bạn muốn kiểm soát hoặc theo dõi các quy trình diễn ra trên màn hình mua hàng, hãy triển khai các phương thức `AdaptyUiEventListener`.
Nếu bạn muốn giữ nguyên hành vi mặc định trong một số trường hợp, bạn có thể kế thừa `AdaptyUiDefaultEventListener` và chỉ ghi đè những phương thức bạn muốn thay đổi.
Dưới đây là các giá trị mặc định từ `AdaptyUiDefaultEventListener`.
### Sự kiện do người dùng tạo ra \{#user-generated-events\}
#### Chọn sản phẩm \{#product-selection\}
Phương thức này sẽ được gọi khi một sản phẩm được chọn để mua (bởi người dùng hoặc hệ thống):
```kotlin showLineNumbers title="Kotlin"
public override fun onProductSelected(
product: AdaptyPaywallProduct,
context: Context,
) {}
```
Ví dụ sự kiện (Nhấp để mở rộng)
```javascript
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
```
#### Bắt đầu mua hàng \{#started-purchase\}
Phương thức này sẽ được gọi khi người dùng bắt đầu quá trình mua hàng:
```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseStarted(
product: AdaptyPaywallProduct,
context: Context,
) {}
```
Ví dụ sự kiện (Nhấp để mở rộng)
```javascript
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
```
Phương thức này sẽ không được gọi trong chế độ Observer. Tham khảo chủ đề [Android - Hiển thị paywall Paywall Builder trong chế độ Observer](android-present-paywall-builder-paywalls-in-observer-mode) để biết chi tiết.
#### Mua hàng thành công, đã hủy hoặc đang chờ xử lý \{#successful-canceled-or-pending-purchase\}
Phương thức này sẽ được gọi khi giao dịch mua thành công:
```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseFinished(
purchaseResult: AdaptyPurchaseResult,
product: AdaptyPaywallProduct,
context: Context,
) {
if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
context.getActivityOrNull()?.onBackPressed()
}
```
Ví dụ sự kiện (Nhấp để mở rộng)
```javascript
// Successful purchase
{
"purchaseResult": {
"type": "Success",
"profile": {
"accessLevels": {
"premium": {
"id": "premium",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
}
}
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
// Cancelled purchase
{
"purchaseResult": {
"type": "UserCanceled"
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
// Pending purchase
{
"purchaseResult": {
"type": "Pending"
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
```
Chúng tôi khuyến nghị đóng màn hình trong trường hợp này.
Phương thức này sẽ không được gọi trong chế độ Observer. Tham khảo chủ đề [Android - Hiển thị paywall Paywall Builder trong chế độ Observer](android-present-paywall-builder-paywalls-in-observer-mode) để biết chi tiết.
#### Mua hàng thất bại \{#failed-purchase\}
Phương thức này sẽ được gọi khi giao dịch mua thất bại do lỗi. Điều này bao gồm các lỗi Google Play Billing (hạn chế thanh toán, sản phẩm không hợp lệ, lỗi mạng), lỗi xác minh giao dịch và lỗi hệ thống. Lưu ý rằng khi người dùng hủy, `onPurchaseFinished` sẽ được gọi với kết quả đã hủy thay vào đó, và các khoản thanh toán đang chờ xử lý không kích hoạt phương thức này.
```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseFailure(
error: AdaptyError,
product: AdaptyPaywallProduct,
context: Context,
) {}
```
Ví dụ sự kiện (Nhấp để mở rộng)
```javascript
{
"error": {
"code": "purchase_failed",
"message": "Purchase failed due to insufficient funds",
"details": {
"underlyingError": "Insufficient funds in account"
}
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
```
Phương thức này sẽ không được gọi trong chế độ Observer. Tham khảo chủ đề [Android - Hiển thị paywall Paywall Builder trong chế độ Observer](android-present-paywall-builder-paywalls-in-observer-mode) để biết chi tiết.
#### Hoàn tất điều hướng thanh toán web \{#finished-web-payment-navigation\}
Phương thức này được gọi sau khi có thao tác mở [web paywall](web-paywall) cho một sản phẩm cụ thể. Điều này bao gồm cả các lần điều hướng thành công và thất bại:
```kotlin showLineNumbers title="Kotlin"
public override fun onFinishWebPaymentNavigation(
product: AdaptyPaywallProduct?,
error: AdaptyError?,
context: Context,
) {}
```
**Tham số:**
| Tham số | Mô tả |
|:------------|:---------------------------------------------------------------------------------------------------------------|
| **product** | Một `AdaptyPaywallProduct` mà web paywall được mở cho. Có thể là `null`. |
| **error** | Một đối tượng `AdaptyError` nếu điều hướng web paywall thất bại; `null` nếu điều hướng thành công. |
Ví dụ sự kiện (Nhấp để mở rộng)
```javascript
// Successful navigation
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"error": null
}
// Failed navigation
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"error": {
"code": "web_navigation_failed",
"message": "Failed to open web paywall",
"details": {
"underlyingError": "Browser unavailable"
}
}
}
```
#### Khôi phục thành công \{#successful-restore\}
Phương thức này sẽ được gọi khi khôi phục giao dịch mua thành công:
```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreSuccess(
profile: AdaptyProfile,
context: Context,
) {}
```
Ví dụ sự kiện (Nhấp để mở rộng)
```javascript
{
"profile": {
"accessLevels": {
"premium": {
"id": "premium",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
},
"subscriptions": [
{
"vendorProductId": "premium_monthly",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
]
}
}
```
Chúng tôi khuyến nghị đóng màn hình nếu người dùng có `accessLevel` cần thiết. Tham khảo chủ đề [Trạng thái đăng ký](android-listen-subscription-changes) để tìm hiểu cách kiểm tra.
#### Khôi phục thất bại \{#failed-restore\}
Phương thức này sẽ được gọi nếu `Adapty.restorePurchases()` thất bại:
```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreFailure(
error: AdaptyError,
context: Context,
) {}
```
Ví dụ sự kiện (Nhấp để mở rộng)
```javascript
{
"error": {
"code": "restore_failed",
"message": "Purchase restoration failed",
"details": {
"underlyingError": "No previous purchases found"
}
}
}
```
#### Nâng cấp gói đăng ký \{#upgrade-subscription\}
Khi người dùng cố gắng mua một gói đăng ký mới trong khi đang có gói đăng ký khác hoạt động, bạn có thể kiểm soát cách xử lý giao dịch mua mới bằng cách ghi đè phương thức này. Bạn có hai tùy chọn:
1. **Thay thế gói đăng ký hiện tại** bằng gói mới:
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingPurchaseParams(
product: AdaptyPaywallProduct,
context: Context,
onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback,
): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked {
onPurchaseParamsReceived(
AdaptyPurchaseParameters.Builder()
.withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...))
.build()
)
return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
2. **Giữ cả hai gói đăng ký** (thêm gói mới riêng biệt):
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingPurchaseParams(
product: AdaptyPaywallProduct,
context: Context,
onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback,
): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked {
onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty)
return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
:::note
Nếu bạn không ghi đè phương thức này, hành vi mặc định là giữ cả hai gói đăng ký hoạt động (tương đương với việc sử dụng `AdaptyPurchaseParameters.Empty`).
:::
Bạn cũng có thể đặt các tham số mua hàng bổ sung nếu cần:
```kotlin
AdaptyPurchaseParameters.Builder()
.withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // tùy chọn - để thay thế gói đăng ký hiện tại
.withOfferPersonalized(true) // tùy chọn - nếu sử dụng giá cá nhân hóa
.build()
```
Nếu một gói đăng ký mới được mua trong khi gói khác vẫn còn hoạt động, hãy ghi đè phương thức này để thay thế gói hiện tại bằng gói mới. Nếu gói đăng ký đang hoạt động vẫn cần tiếp tục và gói mới được thêm riêng biệt, hãy gọi `onSubscriptionUpdateParamsReceived(null)`:
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingSubscriptionUpdateParams(
product: AdaptyPaywallProduct,
context: Context,
onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback,
) {
onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...))
}
```
Ví dụ sự kiện (Nhấp để mở rộng)
```javascript
{
"product": {
"vendorProductId": "premium_yearly",
"localizedTitle": "Premium Yearly",
"localizedDescription": "Premium subscription for 1 year",
"localizedPrice": "$99.99",
"price": 99.99,
"currencyCode": "USD"
},
"subscriptionUpdateParams": {
"replacementMode": "with_time_proration"
}
}
```
### Tải dữ liệu và hiển thị \{#data-fetching-and-rendering\}
#### Lỗi tải sản phẩm \{#product-loading-errors\}
Nếu bạn không truyền các sản phẩm trong quá trình khởi tạo, AdaptyUI sẽ tự động lấy các đối tượng cần thiết từ máy chủ. Nếu thao tác này thất bại, AdaptyUI sẽ báo cáo lỗi bằng cách gọi phương thức này:
```kotlin showLineNumbers title="Kotlin"
public override fun onLoadingProductsFailure(
error: AdaptyError,
context: Context,
): Boolean = false
```
Ví dụ sự kiện (Nhấp để mở rộng)
```javascript
{
"error": {
"code": "products_loading_failed",
"message": "Failed to load products from the server",
"details": {
"underlyingError": "Network timeout"
}
}
}
```
Nếu bạn trả về `true`, AdaptyUI sẽ thử lại yêu cầu sau 2 giây.
#### Lỗi hiển thị \{#rendering-errors\}
Nếu xảy ra lỗi trong quá trình hiển thị giao diện, lỗi đó sẽ được báo cáo bằng cách gọi phương thức này:
```kotlin showLineNumbers title="Kotlin"
public override fun onRenderingError(
error: AdaptyError,
context: Context,
) {}
```
Ví dụ sự kiện (Nhấp để mở rộng)
```javascript
{
"error": {
"code": "rendering_failed",
"message": "Failed to render paywall interface",
"details": {
"underlyingError": "Invalid paywall configuration"
}
}
}
```
Trong điều kiện bình thường, các lỗi như vậy không nên xảy ra, vì vậy nếu bạn gặp phải, vui lòng cho chúng tôi biết.
---
# File: android-use-fallback-paywalls
---
---
title: "Android - Sử dụng paywall dự phòng"
description: "Xử lý các trường hợp người dùng ngoại tuyến hoặc máy chủ Adapty không khả dụng."
---
:::warning
Paywall dự phòng được hỗ trợ bởi Android SDK v2.11 trở lên.
:::
Để duy trì trải nghiệm người dùng mượt mà, điều quan trọng là phải thiết lập [paywall dự phòng](/fallback-paywalls) cho các flow, [paywall](paywalls) và [onboarding](onboardings) của bạn. Biện pháp phòng ngừa này giúp mở rộng khả năng của ứng dụng trong trường hợp mất kết nối internet một phần hoặc hoàn toàn.
* **Nếu ứng dụng không thể kết nối đến máy chủ Adapty:**
Ứng dụng vẫn có thể hiển thị flow hoặc paywall dự phòng, và truy cập cấu hình onboarding đã lưu cục bộ.
* **Nếu ứng dụng không thể kết nối internet:**
Ứng dụng vẫn có thể hiển thị flow hoặc paywall dự phòng. Onboarding chứa nội dung từ xa và cần có kết nối internet để hoạt động.
:::important
Trước khi thực hiện các bước trong hướng dẫn này, hãy [tải xuống](/local-fallback-paywalls) các file cấu hình dự phòng từ Adapty.
:::
## Cấu hình \{#configuration\}
1. Di chuyển file cấu hình dự phòng vào thư mục `assets` hoặc `res/raw` trong dự án Android của bạn.
2. Gọi phương thức `.setFallback` **trước khi** bạn lấy paywall hoặc onboarding mục tiêu.
```kotlin showLineNumbers
//if you put the 'android_fallback.json' file to the 'assets' directory
val location = FileLocation.fromAsset("android_fallback.json")
//or `FileLocation.fromAsset("/android_fallback.json")` if you placed it in a child folder of 'assets')
//if you put the 'android_fallback.json' file to the 'res/raw' directory
val location = FileLocation.fromResId(context, R.raw.android_fallback)
//you can also pass a file URI
val fileUri: Uri = //get Uri for the file with fallback paywalls
val location = FileLocation.fromFileUri(fileUri)
//pass the file location
Adapty.setFallback(location, callback)
```
```java showLineNumbers
//if you put the 'android_fallback.json' file to the 'assets' directory
FileLocation location = FileLocation.fromAsset("android_fallback.json");
//or `FileLocation.fromAsset("/android_fallback.json");` if you placed it in a child folder of 'assets')
//if you put the 'android_fallback.json' file to the 'res/raw' directory
FileLocation location = FileLocation.fromResId(context, R.raw.android_fallback);
//you can also pass a file URI
Uri fileUri = //get Uri for the file with fallback paywalls
FileLocation location = FileLocation.fromFileUri(fileUri);
//pass the file location
Adapty.setFallback(location, callback);
```
Tham số:
| Tham số | Mô tả |
| :----------- | :----------------------------------------------------------- |
| **location** | Đối tượng [FileLocation](https://android.adapty.io/adapty/com.adapty.utils/-file-location/-companion/) cho file cấu hình dự phòng |
---
# File: android-localizations-and-locale-codes
---
---
title: "Sử dụng localizations và locale codes trong Android SDK"
description: "Quản lý localizations và locale codes của ứng dụng để tiếp cận người dùng toàn cầu (Android)."
---
## Tại sao điều này quan trọng \{#why-this-is-important\}
Có một vài trường hợp mà locale codes phát huy tác dụng — ví dụ, khi bạn cần lấy đúng paywall cho localization hiện tại của ứng dụng.
Vì locale codes khá phức tạp và có thể khác nhau tùy nền tảng, chúng tôi sử dụng một chuẩn nội bộ thống nhất cho tất cả các nền tảng được hỗ trợ. Tuy nhiên, chính vì sự phức tạp đó, bạn cần hiểu rõ mình đang gửi gì lên server để nhận đúng localization — như vậy bạn sẽ luôn nhận được kết quả như mong đợi.
## Chuẩn locale code tại Adapty \{#locale-code-standard-at-adapty\}
Đối với locale codes, Adapty sử dụng phiên bản điều chỉnh nhẹ của [chuẩn BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): mỗi code gồm các subtag viết thường, phân cách bằng dấu gạch ngang. Một số ví dụ: `en` (tiếng Anh), `pt-br` (tiếng Bồ Đào Nha (Brazil)), `zh` (tiếng Trung giản thể), `zh-hant` (tiếng Trung phồn thể).
## Khớp locale code \{#locale-code-matching\}
Khi Adapty nhận được lời gọi từ SDK phía client kèm theo locale code và bắt đầu tìm kiếm localization tương ứng của một paywall, quá trình diễn ra như sau:
1. Chuỗi locale đầu vào được chuyển thành chữ thường và tất cả dấu gạch dưới (`_`) được thay bằng dấu gạch ngang (`-`)
2. Chúng tôi tìm kiếm localization có locale code khớp hoàn toàn
3. Nếu không tìm thấy, chúng tôi lấy phần chuỗi trước dấu gạch ngang đầu tiên (`pt` với `pt-br`) và tìm kiếm localization khớp
4. Nếu vẫn không tìm thấy, chúng tôi trả về localization mặc định `en`
Nhờ đó, một thiết bị iOS gửi `'pt_BR'`, một thiết bị Android gửi `pt-BR`, và một thiết bị khác gửi `pt-br` đều nhận được kết quả giống nhau.
## Cách triển khai localizations được khuyến nghị \{#implementing-localizations-recommended-way\}
Nếu bạn đang quan tâm đến localizations, rất có thể bạn đã làm việc với các file chuỗi đã được dịch trong dự án. Trong trường hợp đó, chúng tôi khuyến nghị bạn thêm một cặp key-value chứa Adapty locale code tương ứng vào từng file localization. Sau đó, trích xuất giá trị của key đó khi gọi SDK, như sau:
```kotlin showLineNumbers
// 1. Modify your strings.xml files
/*
strings.xml - Spanish
*/
es
/*
strings.xml - Portuguese (Brazil)
*/
pt-br
// 2. Extract and use the locale code
val localeCode = context.getString(R.string.adapty_paywalls_locale)
// pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method
```
Cách này giúp bạn kiểm soát hoàn toàn localization nào sẽ được tải về cho từng người dùng trong ứng dụng của bạn.
## Cách triển khai localizations khác \{#implementing-localizations-the-other-way\}
Bạn cũng có thể đạt được kết quả tương tự (nhưng không hoàn toàn giống) mà không cần định nghĩa tường minh locale codes cho từng localization. Cách này có nghĩa là trích xuất locale code từ các đối tượng mà nền tảng của bạn cung cấp, như sau:
```kotlin showLineNumbers
val locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
context.resources.configuration.locales[0]
else
context.resources.configuration.locale
val localeCode = locale.toLanguageTag()
// pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method
```
Lưu ý rằng chúng tôi không khuyến nghị cách tiếp cận này vì khó dự đoán chính xác server của Adapty sẽ nhận được gì.
Nếu bạn vẫn quyết định sử dụng cách này — hãy đảm bảo bạn đã xử lý tất cả các trường hợp liên quan.
---
# File: android-web-paywall
---
---
title: "Triển khai web paywall trong Android SDK"
description: "Thiết lập web paywall để nhận thanh toán mà không cần qua phí và kiểm duyệt của Play Store."
---
:::important
Trước khi bắt đầu, hãy đảm bảo bạn đã [cấu hình web paywall trên dashboard](web-paywall) và cài đặt Adapty SDK phiên bản 3.15 trở lên.
:::
## Mở web paywall \{#open-web-paywalls\}
Nếu bạn đang làm việc với paywall do bạn tự phát triển, bạn cần xử lý web paywall bằng phương thức SDK. Phương thức `.openWebPaywall`:
1. Tạo một URL duy nhất giúp Adapty liên kết paywall cụ thể được hiển thị cho người dùng với trang web mà họ được chuyển hướng đến.
2. Theo dõi khi người dùng quay lại ứng dụng, sau đó gọi `.getProfile` theo các khoảng thời gian ngắn để xác định xem quyền truy cập của hồ sơ người dùng có được cập nhật hay không.
Nhờ đó, nếu thanh toán thành công và quyền truy cập được cập nhật, gói đăng ký sẽ kích hoạt trong ứng dụng gần như ngay lập tức.
:::note
Sau khi người dùng quay lại ứng dụng, hãy làm mới giao diện để phản ánh các cập nhật từ hồ sơ người dùng. Adapty sẽ nhận và xử lý các sự kiện cập nhật hồ sơ người dùng.
:::
```kotlin showLineNumbers
Adapty.openWebPaywall(
activity = activity,
product = product,
) { error ->
if (error == null) {
// the web paywall was opened successfully
} else {
// handle the error
}
}
```
:::note
Có hai phiên bản của phương thức `openWebPaywall`:
1. `openWebPaywall(product)` — tạo URL theo paywall và đồng thời thêm dữ liệu sản phẩm vào URL.
2. `openWebPaywall(paywall)` — tạo URL theo paywall mà không thêm dữ liệu sản phẩm vào URL. Dùng phiên bản này khi các sản phẩm trong Adapty paywall khác với các sản phẩm trong web paywall.
:::
## Mở web paywall trong trình duyệt trong ứng dụng \{#open-web-paywalls-in-an-in-app-browser\}
Theo mặc định, web paywall mở trong trình duyệt bên ngoài.
Để mang lại trải nghiệm liền mạch cho người dùng, bạn có thể mở web paywall trong trình duyệt trong ứng dụng. Cách này hiển thị trang mua hàng ngay bên trong ứng dụng của bạn, cho phép người dùng hoàn tất giao dịch mà không cần chuyển sang ứng dụng khác.
Để bật tính năng này, đặt tham số `presentation` thành `AdaptyWebPresentation.InAppBrowser`:
```kotlin showLineNumbers
Adapty.openWebPaywall(
activity = activity,
product = product,
presentation = AdaptyWebPresentation.InAppBrowser,
) { error ->
if (error == null) {
// the web paywall was opened successfully
} else {
// handle the error
val adaptyError = error
}
}
```
---
# File: android-troubleshoot-paywall-builder
---
---
title: "Khắc phục sự cố Paywall Builder trong Android SDK"
description: "Khắc phục sự cố Paywall Builder trong Android SDK"
---
Hướng dẫn này giúp bạn giải quyết các sự cố thường gặp khi sử dụng paywall được thiết kế trong Adapty Paywall Builder trên Android SDK.
## Lấy cấu hình paywall thất bại \{#getting-a-paywall-configuration-fails\}
**Vấn đề**: Phương thức `getViewConfiguration` không thể truy xuất cấu hình paywall.
**Nguyên nhân**: Paywall chưa được bật hiển thị trên thiết bị trong Paywall Builder.
**Giải pháp**: Bật toggle **Show on device** trong Paywall Builder.
## Số lượt xem paywall quá lớn \{#the-paywall-view-number-is-too-big\}
**Vấn đề**: Số lượt xem paywall hiển thị gấp đôi so với mong đợi.
**Nguyên nhân**: Bạn có thể đang gọi `logShowPaywall` trong code, điều này làm nhân đôi số lượt xem nếu bạn đang dùng Paywall Builder. Với các paywall được thiết kế bằng Paywall Builder, analytics được theo dõi tự động, vì vậy bạn không cần dùng phương thức này.
**Giải pháp**: Đảm bảo bạn không gọi `logShowPaywall` trong code khi đang sử dụng Paywall Builder.
## Các vấn đề khác \{#other-issues\}
**Vấn đề**: Bạn đang gặp các sự cố liên quan đến Paywall Builder chưa được đề cập ở trên.
**Giải pháp**: Migrate SDK lên phiên bản mới nhất bằng cách sử dụng [hướng dẫn migration](android-sdk-migration-guides) nếu cần. Nhiều vấn đề đã được giải quyết trong các phiên bản SDK mới hơn.
---
# File: android-quickstart-manual
---
---
title: "Bật tính năng mua hàng trong custom paywall trên Android SDK"
description: "Tích hợp Adapty SDK vào custom paywall Android để bật tính năng in-app purchase."
---
Hướng dẫn này mô tả cách tích hợp Adapty vào custom paywall của bạn. Bạn toàn quyền kiểm soát việc triển khai paywall, trong khi Adapty SDK lo việc lấy sản phẩm, xử lý giao dịch mới và khôi phục giao dịch cũ.
:::important
**Hướng dẫn này dành cho các nhà phát triển đang triển khai custom paywall.** Nếu bạn muốn cách đơn giản nhất để bật tính năng mua hàng, hãy sử dụng [Adapty Paywall Builder](android-quickstart-paywalls). Với Paywall Builder, bạn tạo paywall bằng trình chỉnh sửa trực quan không cần code, Adapty tự động xử lý toàn bộ logic mua hàng, và bạn có thể thử nghiệm các thiết kế khác nhau mà không cần phát hành lại ứng dụng.
:::
## Trước khi bắt đầu \{#before-you-start\}
### Thiết lập sản phẩm \{#set-up-products\}
Để bật tính năng in-app purchase, bạn cần hiểu ba khái niệm chính:
- [**Sản phẩm**](product) – bất cứ thứ gì người dùng có thể mua (gói đăng ký, consumable, quyền truy cập trọn đời)
- [**Paywall**](paywalls) – các cấu hình xác định sản phẩm nào sẽ được hiển thị. Trong Adapty, paywall là cách duy nhất để lấy sản phẩm, nhưng thiết kế này cho phép bạn thay đổi sản phẩm, giá cả và ưu đãi mà không cần chỉnh sửa code ứng dụng.
- [**Placement**](placements) – nơi và thời điểm bạn hiển thị paywall trong ứng dụng (như `main`, `onboarding`, `settings`). Bạn thiết lập paywall cho các placement trên dashboard, sau đó gọi chúng bằng placement ID trong code. Điều này giúp dễ dàng chạy A/B test và hiển thị paywall khác nhau cho từng nhóm người dùng.
Hãy đảm bảo bạn hiểu các khái niệm này ngay cả khi làm việc với custom paywall. Về cơ bản, đây chỉ là cách bạn quản lý các sản phẩm bán trong ứng dụng.
Để triển khai custom paywall, bạn cần tạo một **paywall** và thêm nó vào một **placement**. Thiết lập này cho phép bạn lấy các sản phẩm. Để hiểu những gì cần làm trên dashboard, hãy theo dõi hướng dẫn khởi đầu nhanh [tại đây](quickstart).
### Quản lý người dùng \{#manage-users\}
Bạn có thể làm việc có hoặc không có xác thực backend ở phía mình.
Tuy nhiên, Adapty SDK xử lý người dùng ẩn danh và người dùng đã xác định theo các cách khác nhau. Đọc [hướng dẫn khởi đầu nhanh về nhận dạng người dùng](android-quickstart-identify) để hiểu rõ sự khác biệt và đảm bảo bạn đang làm việc với người dùng đúng cách.
## Bước 1. Lấy sản phẩm \{#step-1-get-products\}
Để lấy sản phẩm cho custom paywall, bạn cần:
1. Lấy đối tượng `paywall` bằng cách truyền ID [placement](placements) vào phương thức `getPaywall`.
2. Lấy mảng sản phẩm cho paywall này bằng phương thức `getPaywallProducts`.
```kotlin showLineNumbers
fun loadPaywall() {
Adapty.getPaywall("YOUR_PLACEMENT_ID") { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
Adapty.getPaywallProducts(paywall) { productResult ->
when (productResult) {
is AdaptyResult.Success -> {
val products = productResult.value
// Use products to build your custom paywall UI
}
is AdaptyResult.Error -> {
val error = productResult.error
// Handle the error
}
}
}
}
is AdaptyResult.Error -> {
val error = result.error
// Handle the error
}
}
}
}
```
```java showLineNumbers
public void loadPaywall() {
Adapty.getPaywall("YOUR_PLACEMENT_ID", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
Adapty.getPaywallProducts(paywall, productResult -> {
if (productResult instanceof AdaptyResult.Success) {
List products = ((AdaptyResult.Success>) productResult).getValue();
// Use products to build your custom paywall UI
} else if (productResult instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) productResult).getError();
// Handle the error
}
});
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// Handle the error
}
});
}
```
## Bước 2. Nhận giao dịch mua hàng \{#step-2-accept-purchases\}
Khi người dùng nhấn vào một sản phẩm trong custom paywall, hãy gọi phương thức `makePurchase` với sản phẩm đã chọn. Thao tác này sẽ xử lý luồng mua hàng và trả về hồ sơ người dùng đã được cập nhật.
```kotlin showLineNumbers
fun purchaseProduct(activity: Activity, product: AdaptyPaywallProduct) {
Adapty.makePurchase(activity, product) { result ->
when (result) {
is AdaptyResult.Success -> {
when (val purchaseResult = result.value) {
is AdaptyPurchaseResult.Success -> {
val profile = purchaseResult.profile
// Purchase successful, profile updated
}
is AdaptyPurchaseResult.UserCanceled -> {
// User canceled the purchase
}
is AdaptyPurchaseResult.Pending -> {
// Purchase is pending (e.g., user will pay offline with cash)
}
}
}
is AdaptyResult.Error -> {
val error = result.error
// Handle the error
}
}
}
}
```
```java showLineNumbers
public void purchaseProduct(Activity activity, AdaptyPaywallProduct product) {
Adapty.makePurchase(activity, product, null, result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue();
if (purchaseResult instanceof AdaptyPurchaseResult.Success) {
AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile();
// Purchase successful, profile updated
} else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) {
// User canceled the purchase
} else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) {
// Purchase is pending (e.g., user will pay offline with cash)
}
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// Handle the error
}
});
}
```
## Bước 3. Khôi phục giao dịch mua hàng \{#step-3-restore-purchases\}
Google Play và các cửa hàng ứng dụng khác yêu cầu tất cả ứng dụng có gói đăng ký phải cung cấp cách để người dùng khôi phục giao dịch mua hàng của họ.
Gọi phương thức `restorePurchases` khi người dùng nhấn nút khôi phục. Thao tác này sẽ đồng bộ lịch sử mua hàng của họ với Adapty và trả về hồ sơ người dùng đã được cập nhật.
```kotlin showLineNumbers
fun restorePurchases() {
Adapty.restorePurchases { result ->
when (result) {
is AdaptyResult.Success -> {
val profile = result.value
// Restore successful, profile updated
}
is AdaptyResult.Error -> {
val error = result.error
// Handle the error
}
}
}
}
```
```java showLineNumbers
public void restorePurchases() {
Adapty.restorePurchases(result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyProfile profile = ((AdaptyResult.Success) result).getValue();
// Restore successful, profile updated
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// Handle the error
}
});
}
```
## Các bước tiếp theo \{#next-steps\}
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
Bạn có câu hỏi hoặc gặp sự cố? Hãy xem [diễn đàn hỗ trợ](https://adapty.featurebase.app/) của chúng tôi — nơi bạn có thể tìm câu trả lời cho các câu hỏi thường gặp hoặc đặt câu hỏi của riêng mình. Đội ngũ và cộng đồng của chúng tôi luôn sẵn sàng giúp đỡ!
Paywall của bạn đã sẵn sàng để hiển thị trong ứng dụng. [Kiểm thử giao dịch mua hàng trên Google Play Store](testing-on-android) để đảm bảo bạn có thể hoàn thành một giao dịch thử nghiệm từ paywall. Để xem cách hoạt động trong một triển khai sẵn sàng cho môi trường production, hãy xem [ProductListFragment.kt](https://github.com/adaptyteam/AdaptySDK-Android/blob/master/app/src/main/java/com/adapty/example/ProductListFragment.kt) trong ứng dụng ví dụ của chúng tôi, minh họa cách xử lý giao dịch mua hàng với error handling đầy đủ, phản hồi giao diện người dùng và quản lý gói đăng ký.
Tiếp theo, [kiểm tra xem người dùng đã hoàn tất giao dịch mua hàng chưa](android-check-subscription-status) để quyết định có hiển thị paywall hay cấp quyền truy cập vào các tính năng trả phí.
---
# File: fetch-paywalls-and-products-android
---
---
title: "Lấy thông tin paywall và sản phẩm cho paywall remote config trong Android SDK"
description: "Lấy thông tin paywall và sản phẩm trong Adapty Android SDK để tối ưu hóa doanh thu."
---
Trước khi hiển thị remote config và các paywall tùy chỉnh, bạn cần lấy thông tin về chúng. Lưu ý rằng chủ đề này đề cập đến remote config và các paywall tùy chỉnh. Để biết hướng dẫn lấy paywall cho các paywall được tùy chỉnh bằng Paywall Builder, vui lòng xem [Lấy paywall Paywall Builder và cấu hình của chúng](android-get-pb-paywalls).
:::tip
Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác.
:::
Trước khi bắt đầu lấy paywall và sản phẩm trong ứng dụng (nhấn để mở rộng)
1. [Tạo sản phẩm](create-product) trong Adapty Dashboard.
2. [Tạo paywall và thêm sản phẩm vào paywall](create-paywall) trong Adapty Dashboard.
3. [Tạo placement và thêm paywall vào placement](create-placement) trong Adapty Dashboard.
4. [Cài đặt Adapty SDK](sdk-installation-android) trong ứng dụng của bạn.
## Lấy thông tin paywall \{#fetch-paywall-information\}
Trong Adapty, một [sản phẩm](product) là sự kết hợp của các sản phẩm từ cả App Store và Google Play. Các sản phẩm đa nền tảng này được tích hợp vào các paywall, cho phép bạn hiển thị chúng tại các placement cụ thể trong ứng dụng.
Để hiển thị các sản phẩm, bạn cần lấy một [Paywall](paywalls) từ một trong các [placement](placements) của mình bằng phương thức `getPaywall`.
:::important
**Đừng hardcode ID sản phẩm.** ID duy nhất bạn nên hardcode là placement ID. Paywall được cấu hình từ xa, vì vậy số lượng sản phẩm và ưu đãi có thể thay đổi bất kỳ lúc nào. Ứng dụng của bạn phải xử lý các thay đổi này một cách linh động — nếu hôm nay paywall trả về hai sản phẩm và ngày mai trả về ba, hãy hiển thị tất cả mà không cần thay đổi code.
:::
```kotlin showLineNumbers
Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
// the requested paywall
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getPaywall("YOUR_PLACEMENT_ID", "en", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
// the requested paywall
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
| Tham số | Bắt buộc | Mô tả |
|---------|--------|-----------|
| **placementId** | bắt buộc | Định danh của [Placement](placements). Đây là giá trị bạn đã chỉ định khi tạo placement trong Adapty Dashboard. |
| **locale** | tùy chọn
mặc định: `en`
| Định danh của [bản địa hóa paywall](add-remote-config-locale). Tham số này được xác định là mã ngôn ngữ gồm một hoặc nhiều thẻ con phân cách bởi dấu trừ (**-**). Thẻ đầu tiên là ngôn ngữ, thẻ thứ hai là vùng.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha Brazil.
Xem [Bản địa hóa và mã locale](android-localizations-and-locale-codes) để biết thêm thông tin về mã locale và cách chúng tôi khuyến nghị sử dụng.
|
| **fetchPolicy** | mặc định: `.reloadRevalidatingCacheData` | Theo mặc định, SDK sẽ cố tải dữ liệu từ server và trả về dữ liệu đã lưu trong cache nếu thất bại. Chúng tôi khuyến nghị cách này vì nó đảm bảo người dùng luôn nhận được dữ liệu mới nhất.
Tuy nhiên, nếu bạn cho rằng người dùng của mình có kết nối internet không ổn định, hãy cân nhắc dùng `.returnCacheDataElseLoad` để trả về dữ liệu cache nếu có. Trong trường hợp này, người dùng có thể không nhận được dữ liệu mới nhất tuyệt đối, nhưng sẽ trải nghiệm thời gian tải nhanh hơn bất kể chất lượng kết nối. Cache được cập nhật thường xuyên, nên an toàn khi dùng trong phiên để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn còn sau khi khởi động lại ứng dụng và chỉ bị xóa khi ứng dụng được cài đặt lại hoặc dọn dẹp thủ công.
Adapty SDK lưu trữ paywall theo hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và [paywall dự phòng](android-use-fallback-paywalls). Chúng tôi cũng dùng CDN để lấy paywall nhanh hơn và một server dự phòng riêng trong trường hợp CDN không thể truy cập. Hệ thống này được thiết kế để đảm bảo bạn luôn nhận được phiên bản mới nhất của paywall đồng thời đảm bảo độ tin cậy ngay cả khi kết nối internet yếu.
|
| **loadTimeout** | mặc định: 5 giây | Giá trị này giới hạn thời gian chờ cho phương thức này. Nếu hết thời gian chờ, dữ liệu cache hoặc fallback cục bộ sẽ được trả về.
Lưu ý rằng trong một số trường hợp hiếm gặp, phương thức này có thể hết thời gian chờ muộn hơn một chút so với giá trị đã chỉ định trong `loadTimeout`, vì thao tác có thể bao gồm nhiều yêu cầu khác nhau bên dưới.
|
Đừng hardcode ID sản phẩm! Vì paywall được cấu hình từ xa, các sản phẩm khả dụng, số lượng sản phẩm và ưu đãi đặc biệt (như dùng thử miễn phí) có thể thay đổi theo thời gian. Hãy đảm bảo code của bạn xử lý được các tình huống này.
Ví dụ, nếu ban đầu bạn lấy được 2 sản phẩm, ứng dụng nên hiển thị 2 sản phẩm đó. Tuy nhiên, nếu sau này bạn lấy được 3 sản phẩm, ứng dụng nên hiển thị cả 3 mà không cần thay đổi code. Thứ duy nhất bạn phải hardcode là placement ID.
Tham số phản hồi:
| Tham số | Mô tả |
| :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Paywall | Đối tượng [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) với: danh sách ID sản phẩm, định danh paywall, Remote Config và một số thuộc tính khác. |
## Lấy sản phẩm \{#fetch-products\}
Sau khi có paywall, bạn có thể truy vấn mảng sản phẩm tương ứng với nó:
```kotlin showLineNumbers
Adapty.getPaywallProducts(paywall) { result ->
when (result) {
is AdaptyResult.Success -> {
val products = result.value
// the requested products
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getPaywallProducts(paywall, result -> {
if (result instanceof AdaptyResult.Success) {
List products = ((AdaptyResult.Success>) result).getValue();
// the requested products
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
Tham số phản hồi:
| Tham số | Mô tả |
| :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Products | Danh sách các đối tượng [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) với: định danh sản phẩm, tên sản phẩm, giá, tiền tệ, thời hạn gói đăng ký và một số thuộc tính khác. |
Khi tự xây dựng giao diện paywall, bạn có thể cần truy cập các thuộc tính từ đối tượng [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/). Dưới đây là các thuộc tính được dùng phổ biến nhất, nhưng hãy tham khảo tài liệu được liên kết để xem đầy đủ tất cả các thuộc tính.
| Thuộc tính | Mô tả |
|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Title** | Để hiển thị tiêu đề sản phẩm, dùng `product.localizedTitle`. Lưu ý rằng bản địa hóa dựa trên quốc gia cửa hàng mà người dùng đã chọn, không phải locale của thiết bị. |
| **Price** | Để hiển thị phiên bản đã được bản địa hóa của giá, dùng `product.price.localizedString`. Bản địa hóa này dựa trên thông tin locale của thiết bị. Bạn cũng có thể truy cập giá dưới dạng số bằng `product.price.amount`. Giá trị sẽ được cung cấp theo đơn vị tiền tệ địa phương. Để lấy ký hiệu tiền tệ tương ứng, dùng `product.price.currencySymbol`. |
| **Subscription Period** | Để hiển thị chu kỳ (ví dụ: tuần, tháng, năm, v.v.), dùng `product.subscriptionDetails?.localizedSubscriptionPeriod`. Bản địa hóa này dựa trên locale của thiết bị. Để lấy chu kỳ gói đăng ký theo kiểu lập trình, dùng `product.subscriptionDetails?.subscriptionPeriod`. Từ đó bạn có thể truy cập enum `unit` để lấy độ dài (tức là DAY, WEEK, MONTH, YEAR, hoặc UNKNOWN). Giá trị `numberOfUnits` sẽ cho bạn biết số đơn vị chu kỳ. Ví dụ, với gói đăng ký hàng quý, bạn sẽ thấy `MONTH` trong thuộc tính unit và `3` trong thuộc tính numberOfUnits. |
| **Introductory Offer** | Để hiển thị huy hiệu hoặc chỉ báo cho thấy gói đăng ký có ưu đãi giới thiệu, hãy xem thuộc tính `product.subscriptionDetails?.introductoryOfferPhases`. Đây là danh sách có thể chứa tối đa hai giai đoạn giảm giá: giai đoạn dùng thử miễn phí và giai đoạn giá ưu đãi giới thiệu. Trong mỗi đối tượng giai đoạn có các thuộc tính hữu ích sau:
• `paymentMode`: enum với các giá trị `FREE_TRIAL`, `PAY_AS_YOU_GO`, `PAY_UPFRONT` và `UNKNOWN`. Dùng thử miễn phí sẽ có kiểu `FREE_TRIAL`.
• `price`: Giá giảm dưới dạng số. Với dùng thử miễn phí, hãy tìm `0` ở đây.
• `localizedNumberOfPeriods`: chuỗi được bản địa hóa theo locale của thiết bị mô tả độ dài ưu đãi. Ví dụ, ưu đãi dùng thử ba ngày hiển thị `3 days` trong trường này.
• `subscriptionPeriod`: Ngoài ra, bạn có thể lấy chi tiết riêng lẻ của chu kỳ ưu đãi với thuộc tính này. Nó hoạt động tương tự cho ưu đãi như phần trước đã mô tả.
• `localizedSubscriptionPeriod`: Chu kỳ gói đăng ký đã được định dạng theo locale của người dùng. |
## Tăng tốc lấy paywall với paywall đối tượng mặc định \{#speed-up-paywall-fetching-with-default-audience-paywall\}
Thông thường, paywall được lấy gần như ngay lập tức, vì vậy bạn không cần lo lắng về việc tăng tốc quá trình này. Tuy nhiên, trong trường hợp bạn có nhiều đối tượng và paywall, và người dùng có kết nối internet yếu, việc lấy paywall có thể mất nhiều thời gian hơn mong muốn. Trong những tình huống như vậy, bạn có thể muốn hiển thị một paywall mặc định để đảm bảo trải nghiệm người dùng mượt mà thay vì không hiển thị paywall nào.
Để giải quyết điều này, bạn có thể dùng phương thức `getPaywallForDefaultAudience`, phương thức này lấy paywall của placement đã chỉ định cho đối tượng **All Users**. Tuy nhiên, điều quan trọng cần hiểu là cách tiếp cận được khuyến nghị là lấy paywall bằng phương thức `getPaywall`, như đã trình bày chi tiết trong phần [Lấy thông tin paywall](fetch-paywalls-and-products-android#fetch-paywall-information) ở trên.
:::warning
Tại sao chúng tôi khuyến nghị dùng `getPaywall`
Phương thức `getPaywallForDefaultAudience` có một vài nhược điểm đáng kể:
- **Vấn đề tương thích ngược tiềm ẩn**: Nếu bạn cần hiển thị các paywall khác nhau cho các phiên bản ứng dụng khác nhau (hiện tại và tương lai), bạn có thể gặp khó khăn. Bạn sẽ phải thiết kế các paywall hỗ trợ phiên bản hiện tại (cũ) hoặc chấp nhận rằng người dùng với phiên bản hiện tại (cũ) có thể gặp sự cố với paywall không hiển thị được.
- **Mất khả năng nhắm mục tiêu**: Tất cả người dùng sẽ thấy cùng một paywall được thiết kế cho đối tượng **All Users**, nghĩa là bạn mất khả năng nhắm mục tiêu cá nhân hóa (bao gồm theo quốc gia, attribution marketing hoặc các thuộc tính tùy chỉnh của bạn).
Nếu bạn sẵn sàng chấp nhận những nhược điểm này để có lợi ích từ việc lấy paywall nhanh hơn, hãy dùng phương thức `getPaywallForDefaultAudience` như sau. Nếu không, hãy tiếp tục dùng `getPaywall` được mô tả [ở trên](fetch-paywalls-and-products-android#fetch-paywall-information).
:::
```kotlin showLineNumbers
Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
// the requested paywall
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
// the requested paywall
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
:::note
Phương thức `getPaywallForDefaultAudience` khả dụng từ Android SDK phiên bản 2.11.3 trở lên.
:::
| Tham số | Bắt buộc | Mô tả |
|---------|--------|-----------|
| **placementId** | bắt buộc | Định danh của [Placement](placements). Đây là giá trị bạn đã chỉ định khi tạo placement trong Adapty Dashboard. |
| **locale** | tùy chọn
mặc định: `en`
| Định danh của [bản địa hóa paywall](add-remote-config-locale). Tham số này được xác định là mã ngôn ngữ gồm một hoặc nhiều thẻ con phân cách bởi dấu trừ (**-**). Thẻ đầu tiên là ngôn ngữ, thẻ thứ hai là vùng.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha Brazil.
Xem [Bản địa hóa và mã locale](android-localizations-and-locale-codes) để biết thêm thông tin về mã locale và cách chúng tôi khuyến nghị sử dụng.
|
| **fetchPolicy** | mặc định: `.reloadRevalidatingCacheData` | Theo mặc định, SDK sẽ cố tải dữ liệu từ server và trả về dữ liệu đã lưu trong cache nếu thất bại. Chúng tôi khuyến nghị cách này vì nó đảm bảo người dùng luôn nhận được dữ liệu mới nhất.
Tuy nhiên, nếu bạn cho rằng người dùng của mình có kết nối internet không ổn định, hãy cân nhắc dùng `.returnCacheDataElseLoad` để trả về dữ liệu cache nếu có. Trong trường hợp này, người dùng có thể không nhận được dữ liệu mới nhất tuyệt đối, nhưng sẽ trải nghiệm thời gian tải nhanh hơn bất kể chất lượng kết nối. Cache được cập nhật thường xuyên, nên an toàn khi dùng trong phiên để tránh các yêu cầu mạng.
Lưu ý rằng cache vẫn còn sau khi khởi động lại ứng dụng và chỉ bị xóa khi ứng dụng được cài đặt lại hoặc dọn dẹp thủ công.
|
---
# File: present-remote-config-paywalls-android
---
---
title: "Hiển thị paywall được thiết kế bằng Remote Config trong Android SDK"
description: "Khám phá cách hiển thị paywall Remote Config trong Adapty Android SDK để cá nhân hóa trải nghiệm người dùng."
---
Nếu bạn đã tùy chỉnh paywall bằng Remote Config, bạn cần tự implement phần hiển thị trong code của ứng dụng để người dùng có thể thấy nó. Vì Remote Config linh hoạt và tùy biến theo nhu cầu của bạn, bạn hoàn toàn kiểm soát những gì được đưa vào và giao diện paywall sẽ trông như thế nào. Chúng tôi cung cấp một phương thức để lấy cấu hình remote, giúp bạn tự do trình bày paywall tùy chỉnh đã được cấu hình qua Remote Config.
## Lấy Remote Config của paywall và hiển thị nó \{#get-paywall-remote-config-and-present-it\}
Để lấy Remote Config của một paywall, truy cập thuộc tính `remoteConfig` và trích xuất các giá trị cần thiết.
```kotlin showLineNumbers
Adapty.getPaywall("YOUR_PLACEMENT_ID") { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
val headerText = paywall.remoteConfig?.dataMap?.get("header_text") as? String
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getPaywall("YOUR_PLACEMENT_ID", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
AdaptyPaywall.RemoteConfig remoteConfig = paywall.getRemoteConfig();
if (remoteConfig != null) {
if (remoteConfig.getDataMap().get("header_text") instanceof String) {
String headerText = (String) remoteConfig.getDataMap().get("header_text");
}
}
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
Sau khi đã nhận được tất cả các giá trị cần thiết, đã đến lúc render và ghép chúng thành một trang trực quan hấp dẫn. Hãy đảm bảo thiết kế tương thích với nhiều kích thước màn hình và hướng xoay của điện thoại, mang lại trải nghiệm mượt mà và thân thiện với người dùng trên các thiết bị khác nhau.
:::warning
Hãy đảm bảo [ghi nhận sự kiện xem paywall](present-remote-config-paywalls-android#track-paywall-view-events) như mô tả bên dưới, để Adapty analytics có thể thu thập thông tin cho funnel và A/B test.
:::
Sau khi hoàn tất việc hiển thị paywall, hãy tiếp tục thiết lập luồng mua hàng. Khi người dùng thực hiện mua hàng, chỉ cần gọi `.makePurchase()` với sản phẩm từ paywall của bạn. Để biết thêm chi tiết về phương thức `.makePurchase()`, hãy đọc [Thực hiện mua hàng](android-making-purchases).
Chúng tôi khuyến nghị [tạo một paywall dự phòng](android-use-fallback-paywalls). Paywall dự phòng này sẽ hiển thị cho người dùng khi không có kết nối internet hoặc không có cache, đảm bảo trải nghiệm mượt mà ngay cả trong những tình huống đó.
## Ghi nhận sự kiện xem paywall \{#track-paywall-view-events\}
Adapty giúp bạn đo lường hiệu quả của các paywall. Trong khi chúng tôi tự động thu thập dữ liệu về các giao dịch mua, việc ghi lại lượt xem paywall cần có sự tham gia của bạn vì chỉ bạn mới biết khi nào khách hàng nhìn thấy paywall.
Để ghi nhận một sự kiện xem paywall, chỉ cần gọi `.logShowPaywall(paywall)`, và nó sẽ được phản ánh trong các chỉ số paywall của bạn trong funnel và A/B test.
:::important
Không cần gọi `.logShowPaywall(paywall)` nếu bạn đang hiển thị paywall được tạo trong [paywall builder](adapty-paywall-builder).
:::
```kotlin showLineNumbers
Adapty.logShowPaywall(paywall)
```
Tham số yêu cầu:
| Tham số | Bắt buộc | Mô tả |
| :---------- | :------- |:------------------------------------------------------------------------------------------------------------|
| **paywall** | bắt buộc | Một đối tượng [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). |
---
# File: android-making-purchases
---
---
title: "Thực hiện mua hàng trong ứng dụng Android SDK"
description: "Hướng dẫn xử lý in-app purchase và gói đăng ký bằng Adapty."
---
Hiển thị paywall trong ứng dụng là bước quan trọng để cung cấp cho người dùng quyền truy cập vào nội dung hoặc dịch vụ cao cấp. Tuy nhiên, chỉ cần hiển thị paywall là đủ để hỗ trợ thanh toán nếu bạn dùng [Paywall Builder](adapty-paywall-builder) để tùy chỉnh paywall.
Nếu không dùng Paywall Builder, bạn phải sử dụng một phương thức riêng là `.makePurchase()` để hoàn tất giao dịch và mở khóa nội dung. Phương thức này là cổng để người dùng tương tác với paywall và thực hiện giao dịch mong muốn.
Nếu paywall của bạn có ưu đãi đang hoạt động cho sản phẩm mà người dùng đang mua, Adapty sẽ tự động áp dụng ưu đãi đó tại thời điểm thanh toán.
:::warning
Lưu ý rằng ưu đãi giới thiệu chỉ được áp dụng tự động nếu bạn sử dụng paywall được thiết lập bằng Paywall Builder.
Trong các trường hợp khác, bạn cần [xác minh tính đủ điều kiện của người dùng cho ưu đãi giới thiệu trên iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Bỏ qua bước này có thể khiến ứng dụng bị từ chối khi phát hành. Hơn nữa, điều này có thể dẫn đến việc tính giá đầy đủ cho những người dùng đủ điều kiện nhận ưu đãi giới thiệu.
:::
Hãy đảm bảo bạn đã [hoàn thành cấu hình ban đầu](quickstart) mà không bỏ sót bước nào. Nếu không, chúng tôi không thể xác thực các giao dịch mua hàng.
## Thực hiện mua hàng \{#make-purchase\}
:::note
**Đang dùng [Paywall Builder](adapty-paywall-builder)?** Các giao dịch mua hàng được xử lý tự động — bạn có thể bỏ qua bước này.
**Cần hướng dẫn từng bước?** Xem [hướng dẫn quickstart](android-implement-paywalls-manually) để biết cách triển khai đầy đủ từ đầu đến cuối.
:::
```kotlin showLineNumbers
Adapty.makePurchase(activity, product, null) { result ->
when (result) {
is AdaptyResult.Success -> {
when (val purchaseResult = result.value) {
is AdaptyPurchaseResult.Success -> {
val profile = purchaseResult.profile
if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) {
// Grant access to the paid features
}
}
is AdaptyPurchaseResult.UserCanceled -> {
// Handle the case where the user canceled the purchase
}
is AdaptyPurchaseResult.Pending -> {
// Handle deferred purchases (e.g., the user will pay offline with cash)
}
}
}
is AdaptyResult.Error -> {
val error = result.error
// Handle the error
}
}
}
```
```java showLineNumbers
Adapty.makePurchase(activity, product, null, result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue();
if (purchaseResult instanceof AdaptyPurchaseResult.Success) {
AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile();
AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL");
if (premium != null && premium.isActive()) {
// Grant access to the paid features
}
} else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) {
// Handle the case where the user canceled the purchase
} else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) {
// Handle deferred purchases (e.g., the user will pay offline with cash)
}
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// Handle the error
}
});
```
Tham số yêu cầu:
| Tham số | Bắt buộc | Mô tả |
| :---------- | :------- | :-------------------------------------------------------------------------------------------------- |
| **Product** | bắt buộc | Đối tượng [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) lấy từ paywall. |
Tham số phản hồi:
| Tham số | Mô tả |
|-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Profile** | Nếu yêu cầu thành công, phản hồi sẽ chứa đối tượng này. Đối tượng [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/) cung cấp thông tin toàn diện về mức độ truy cập, gói đăng ký và các sản phẩm mua một lần của người dùng trong ứng dụng.
Kiểm tra trạng thái mức độ truy cập để xác định xem người dùng có quyền truy cập cần thiết vào ứng dụng hay không.
|
:::warning
**Lưu ý:** nếu bạn vẫn đang dùng StoreKit của Apple phiên bản thấp hơn v2.0 và Adapty SDK phiên bản thấp hơn v2.9.0, bạn cần cung cấp [Apple App Store shared secret](app-store-connection-configuration#step-5-enter-app-store-shared-secret) thay thế. Phương thức này hiện đã bị Apple ngừng hỗ trợ.
:::
## Thay đổi gói đăng ký khi mua hàng \{#change-subscription-when-making-a-purchase\}
Khi người dùng chọn gói đăng ký mới thay vì gia hạn gói hiện tại, cách thức hoạt động phụ thuộc vào cửa hàng. Với Google Play, gói đăng ký không được cập nhật tự động. Bạn cần xử lý việc chuyển đổi trong code của ứng dụng như mô tả bên dưới.
Để thay thế gói đăng ký bằng gói khác trên Android, hãy gọi phương thức `.makePurchase()` với tham số bổ sung:
```kotlin showLineNumbers
Adapty.makePurchase(
activity,
product,
AdaptyPurchaseParameters.Builder()
.withSubscriptionUpdateParams(subscriptionUpdateParams)
.build()
) { result ->
when (result) {
is AdaptyResult.Success -> {
when (val purchaseResult = result.value) {
is AdaptyPurchaseResult.Success -> {
val profile = purchaseResult.profile
// successful cross-grade
}
is AdaptyPurchaseResult.UserCanceled -> {
// user canceled the purchase flow
}
is AdaptyPurchaseResult.Pending -> {
// the purchase has not been finished yet, e.g. user will pay offline by cash
}
}
}
is AdaptyResult.Error -> {
val error = result.error
// Handle the error
}
}
}
```
Tham số yêu cầu bổ sung:
| Tham số | Bắt buộc | Mô tả |
| :--------------------------- | :------- | :----------------------------------------------------------- |
| **subscriptionUpdateParams** | bắt buộc | Đối tượng [`AdaptySubscriptionUpdateParameters`](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/). |
```java showLineNumbers
Adapty.makePurchase(
activity,
product,
new AdaptyPurchaseParameters.Builder()
.withSubscriptionUpdateParams(subscriptionUpdateParams)
.build(),
result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue();
if (purchaseResult instanceof AdaptyPurchaseResult.Success) {
AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile();
// successful cross-grade
} else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) {
// user canceled the purchase flow
} else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) {
// the purchase has not been finished yet, e.g. user will pay offline by cash
}
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// Handle the error
}
});
```
Tham số yêu cầu bổ sung:
| Tham số | Bắt buộc | Mô tả |
| :--------------------------- | :------- | :----------------------------------------------------------- |
| **subscriptionUpdateParams** | bắt buộc | Đối tượng [`AdaptySubscriptionUpdateParameters`](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/). |
Bạn có thể đọc thêm về gói đăng ký và các chế độ thay thế trong tài liệu Google Developer:
- [Về các chế độ thay thế](https://developer.android.com/google/play/billing/subscriptions#replacement-modes)
- [Khuyến nghị của Google về các chế độ thay thế](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations)
- Chế độ thay thế [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Lưu ý: phương thức này chỉ khả dụng để nâng cấp gói đăng ký. Hạ cấp không được hỗ trợ.
- Chế độ thay thế [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Lưu ý: Thay đổi gói đăng ký thực sự chỉ xảy ra khi chu kỳ thanh toán của gói đăng ký hiện tại kết thúc.
### Quản lý gói trả trước \{#manage-prepaid-plans\}
Nếu người dùng ứng dụng của bạn có thể mua [gói trả trước](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (ví dụ: mua gói đăng ký không tự gia hạn trong vài tháng), bạn có thể bật [giao dịch đang chờ xử lý](https://developer.android.com/google/play/billing/subscriptions#pending) cho các gói trả trước.
```kotlin showLineNumbers
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withEnablePendingPrepaidPlans(true)
.build()
```
```java showLineNumbers
new AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withEnablePendingPrepaidPlans(true)
.build();
```
---
# File: android-restore-purchase
---
---
title: "Khôi phục giao dịch mua trong ứng dụng Android SDK"
description: "Tìm hiểu cách khôi phục giao dịch mua trong Adapty để đảm bảo trải nghiệm người dùng liền mạch."
---
Khôi phục giao dịch mua là tính năng cho phép người dùng lấy lại quyền truy cập vào nội dung đã mua trước đó — chẳng hạn như các gói đăng ký hoặc in-app purchase — mà không bị tính tiền thêm lần nữa. Tính năng này đặc biệt hữu ích cho những người đã gỡ cài đặt rồi cài lại ứng dụng, hoặc chuyển sang thiết bị mới và muốn truy cập lại nội dung đã mua mà không cần thanh toán lại.
:::note
Trong các paywall được xây dựng bằng [Paywall Builder](adapty-paywall-builder), giao dịch mua sẽ được khôi phục tự động mà không cần thêm code từ phía bạn. Nếu đó là trường hợp của bạn — bạn có thể bỏ qua bước này.
:::
Để khôi phục giao dịch mua khi bạn không sử dụng [Paywall Builder](adapty-paywall-builder) để tùy chỉnh paywall, hãy gọi phương thức `.restorePurchases()`:
```kotlin showLineNumbers
Adapty.restorePurchases { result ->
when (result) {
is AdaptyResult.Success -> {
val profile = result.value
if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) {
// successful access restore
}
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.restorePurchases(result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyProfile profile = ((AdaptyResult.Success) result).getValue();
if (profile != null) {
AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL");
if (premium != null && premium.isActive()) {
// successful access restore
}
}
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
Tham số phản hồi:
| Tham số | Mô tả |
|---------|-----------|
| **Profile** | Đối tượng [`AdaptyProfile`](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/). Model này chứa thông tin về mức độ truy cập, gói đăng ký, và các sản phẩm mua một lần.
Kiểm tra **trạng thái mức độ truy cập** để xác định xem người dùng có quyền truy cập vào ứng dụng hay không.
|
:::tip
Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác.
:::
---
# File: implement-observer-mode-android
---
---
title: "Triển khai Observer mode trong Android SDK"
description: "Triển khai Observer mode trong Adapty để theo dõi các sự kiện gói đăng ký của người dùng trong Android SDK."
---
Nếu bạn đã có hạ tầng mua hàng riêng và chưa sẵn sàng chuyển hoàn toàn sang Adapty, bạn có thể khám phá [Observer mode](observer-vs-full-mode). Ở dạng cơ bản, Observer Mode cung cấp phân tích nâng cao và tích hợp liền mạch với các hệ thống attribution và analytics.
Nếu điều này đáp ứng nhu cầu của bạn, bạn chỉ cần:
1. Bật tính năng này khi cấu hình Adapty SDK bằng cách đặt tham số `observerMode` thành `true`. Làm theo hướng dẫn cài đặt cho [Android](sdk-installation-android#activate-adapty-module-of-adapty-sdk).
2. [Báo cáo giao dịch](report-transactions-observer-mode-android) từ hạ tầng mua hàng hiện có của bạn sang Adapty.
## Thiết lập Observer mode \{#observer-mode-setup\}
Bật Observer mode nếu bạn tự xử lý việc mua hàng và trạng thái gói đăng ký, đồng thời sử dụng Adapty để gửi các sự kiện gói đăng ký và analytics.
:::important
Khi chạy ở Observer mode, Adapty SDK sẽ không đóng bất kỳ giao dịch nào, vì vậy hãy đảm bảo bạn tự xử lý điều đó.
:::
```kotlin showLineNumbers
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Adapty.activate(
applicationContext,
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withObserverMode(true) //default false
.build()
)
}
```
```java showLineNumbers
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Adapty.activate(
applicationContext,
new AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withObserverMode(true) //default false
.build()
);
}
```
Các tham số:
| Tham số | Mô tả |
| --------------------------- | ------------------------------------------------------------ |
| observerMode | Giá trị boolean kiểm soát [Observer mode](observer-vs-full-mode). Giá trị mặc định là `false`. |
## Sử dụng paywall của Adapty trong Observer Mode \{#using-adapty-paywalls-in-observer-mode\}
Nếu bạn cũng muốn sử dụng các tính năng paywall và A/B test của Adapty, bạn hoàn toàn có thể — nhưng cần thêm một số thiết lập trong Observer mode. Đây là những gì bạn cần làm ngoài các bước trên:
1. Hiển thị paywall như bình thường đối với [remote config paywalls](present-remote-config-paywalls-android). Đối với paywall dùng Paywall Builder, hãy làm theo hướng dẫn cài đặt cụ thể cho [Android](android-present-paywall-builder-paywalls-in-observer-mode).
3. [Liên kết paywall](report-transactions-observer-mode-android) với các giao dịch mua hàng.
---
# File: report-transactions-observer-mode-android
---
---
title: "Báo cáo giao dịch trong Observer Mode trên Android SDK"
description: "Báo cáo giao dịch mua hàng trong Adapty Observer Mode để theo dõi thông tin người dùng và doanh thu trên Android SDK."
---
Trong Observer Mode, SDK không thể tự động theo dõi các giao dịch mua hàng được thực hiện qua hệ thống mua hàng hiện tại của bạn. Bạn cần báo cáo các giao dịch từ app store. Điều quan trọng là phải thiết lập điều này **trước** khi phát hành ứng dụng để tránh sai sót trong analytics.
Sử dụng `reportTransaction` để báo cáo từng giao dịch một cách tường minh để Adapty nhận diện.
:::warning
**Đừng bỏ qua việc báo cáo giao dịch!**
Nếu bạn không gọi `reportTransaction`, Adapty sẽ không nhận diện được giao dịch, nó sẽ không xuất hiện trong analytics và sẽ không được gửi đến các tích hợp.
:::
Nếu bạn sử dụng paywall của Adapty, hãy đưa `variationId` vào khi báo cáo giao dịch. Điều này liên kết giao dịch mua hàng với paywall đã kích hoạt nó, đảm bảo analytics paywall chính xác.
```kotlin showLineNumbers
val transactionInfo = TransactionInfo.fromPurchase(purchase)
Adapty.reportTransaction(transactionInfo, variationId) { result ->
if (result is AdaptyResult.Success) {
// success
}
}
```
Tham số:
| Tham số | Bắt buộc | Mô tả |
| --------------- | -------- | ------------------------------------------------------------ |
| transactionInfo | bắt buộc | TransactionInfo từ giao dịch mua hàng, trong đó purchase là một instance của lớp [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) trong thư viện billing. |
| variationId | tùy chọn | Định danh chuỗi của biến thể. Bạn có thể lấy nó thông qua thuộc tính `variationId` của đối tượng [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). |
```java showLineNumbers
TransactionInfo transactionInfo = TransactionInfo.fromPurchase(purchase);
Adapty.reportTransaction(transactionInfo, variationId, result -> {
if (result instanceof AdaptyResult.Success) {
// success
}
});
```
Tham số:
| Tham số | Bắt buộc | Mô tả |
| --------------- | -------- | ------------------------------------------------------------ |
| transactionInfo | bắt buộc | TransactionInfo từ giao dịch mua hàng, trong đó purchase là một instance của lớp [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) trong thư viện billing. |
| variationId | tùy chọn | Định danh chuỗi của biến thể. Bạn có thể lấy nó thông qua thuộc tính `variationId` của đối tượng [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). |
Trong Observer Mode, SDK không thể tự động theo dõi các giao dịch mua hàng được thực hiện qua hệ thống mua hàng hiện tại của bạn. Bạn cần báo cáo các giao dịch từ app store hoặc khôi phục chúng. Điều quan trọng là phải thiết lập điều này **trước** khi phát hành ứng dụng để tránh sai sót trong analytics.
Sử dụng `restorePurchases` để báo cáo giao dịch cho Adapty.
:::warning
**Đừng bỏ qua việc khôi phục giao dịch mua hàng!**
Nếu bạn không gọi `restorePurchases`, Adapty sẽ không nhận diện được giao dịch, nó sẽ không xuất hiện trong analytics và sẽ không được gửi đến các tích hợp.
:::
Nếu bạn sử dụng paywall của Adapty, hãy liên kết giao dịch với paywall đã dẫn đến giao dịch mua hàng bằng phương thức `setVariationId`. Điều này đảm bảo giao dịch mua hàng được quy đúng về paywall kích hoạt để analytics chính xác. Bước này chỉ cần thiết nếu bạn đang sử dụng paywall của Adapty.
```kotlin showLineNumbers
Adapty.restorePurchases { result ->
if (result is AdaptyResult.Success) {
// success
}
}
Adapty.setVariationId(transactionId, variationId) { error ->
if (error == null) {
// success
}
}
```
Tham số:
| Tham số | Bắt buộc | Mô tả |
| ------------- | -------- | ------------------------------------------------------------ |
| transactionId | bắt buộc | Định danh chuỗi (`purchase.getOrderId`) của giao dịch mua hàng, trong đó purchase là một instance của lớp [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) trong thư viện billing. |
| variationId | bắt buộc | Định danh chuỗi của biến thể. Bạn có thể lấy nó thông qua thuộc tính `variationId` của đối tượng [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). |
```java showLineNumbers
Adapty.restorePurchases(result -> {
if (result instanceof AdaptyResult.Success) {
// success
}
});
Adapty.setVariationId(transactionId, variationId, error -> {
if (error == null) {
// success
}
});
```
Tham số:
| Tham số | Bắt buộc | Mô tả |
| ------------- | -------- | ------------------------------------------------------------ |
| transactionId | bắt buộc | Định danh chuỗi (`purchase.getOrderId`) của giao dịch mua hàng, trong đó purchase là một instance của lớp [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) trong thư viện billing. |
| variationId | bắt buộc | Định danh chuỗi của biến thể. Bạn có thể lấy nó thông qua thuộc tính `variationId` của đối tượng [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). |
**Báo cáo giao dịch**
Sử dụng `restorePurchases` để báo cáo giao dịch cho Adapty trong Observer Mode, như được giải thích trên trang [Khôi phục giao dịch mua hàng trong Mobile Code](android-restore-purchase).
:::warning
**Đừng bỏ qua việc báo cáo giao dịch!**
Nếu bạn không gọi `restorePurchases`, Adapty sẽ không nhận diện được giao dịch, nó sẽ không xuất hiện trong analytics và sẽ không được gửi đến các tích hợp.
:::
**Liên kết paywall với giao dịch**
SDK không thể xác định nguồn gốc của các giao dịch mua hàng vì bạn là người xử lý chúng. Do đó, nếu bạn có ý định sử dụng paywall và/hoặc A/B test trong Observer Mode, bạn cần liên kết giao dịch đến từ app store với paywall tương ứng trong code ứng dụng của bạn. Điều này quan trọng để thiết lập đúng trước khi phát hành ứng dụng, nếu không sẽ dẫn đến sai sót trong analytics.
```kotlin
Adapty.setVariationId(transactionId, variationId) { error ->
if (error == null) {
// success
}
}
```
Tham số yêu cầu:
| Tham số | Bắt buộc | Mô tả |
| ------------- | -------- | ------------------------------------------------------------ |
| transactionId | bắt buộc | Định danh chuỗi (purchase.getOrderId của giao dịch mua hàng, trong đó purchase là một instance của lớp [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) trong thư viện billing. |
| variationId | bắt buộc | Định danh chuỗi của biến thể. Bạn có thể lấy nó thông qua thuộc tính `variationId` của đối tượng [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). |
```java
Adapty.setVariationId(transactionId, variationId, error -> {
if (error == null) {
// success
}
});
```
| Tham số | Bắt buộc | Mô tả |
| ------------------------------------------------- | -------- | ------------------------------------------------------------ |
| transactionId | bắt buộc | Định danh chuỗi (purchase.getOrderId của giao dịch mua hàng, trong đó purchase là một instance của lớp [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) trong thư viện billing. |
| variationId | bắt buộc | Định danh chuỗi của biến thể. Bạn có thể lấy nó thông qua thuộc tính `variationId` của đối tượng [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). |
---
# File: android-present-paywall-builder-paywalls-in-observer-mode
---
---
title: "Hiển thị paywall được tạo bằng Paywall Builder ở chế độ Observer trên Android SDK"
description: "Tìm hiểu cách hiển thị paywall ở chế độ observer bằng Paywall Builder của Adapty."
---
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 để hiển thị cho người dùng. Paywall đó đã bao gồm cả nội dung cần hiển thị lẫn cách hiển thị.
:::warning
Phần này chỉ áp dụng cho [chế độ Observer](observer-vs-full-mode). Nếu bạn không làm việc ở chế độ Observer, hãy tham khảo chủ đề [Android - Hiển thị paywall bằng Paywall Builder](android-present-paywalls).
:::
Trước khi bắt đầu hiển thị paywall (Nhấp để mở rộng)
1. Thiết lập tích hợp ban đầu của Adapty [với Google Play](initial-android) và [với App Store](initial_ios).
2. Cài đặt và cấu hình Adapty SDK. Đảm bảo đặt tham số `observerMode` thành `true`. Tham khảo hướng dẫn theo framework [cho Android](sdk-installation-android).
3. [Tạo sản phẩm](create-product) trong Adapty Dashboard.
4. [Cấu hình paywall, gán sản phẩm cho chúng](create-paywall) và tùy chỉnh bằng Paywall Builder trong Adapty Dashboard.
5. [Tạo placement và gán paywall vào chúng](create-placement) trong Adapty Dashboard.
6. [Lấy paywall Paywall Builder và cấu hình của chúng](android-get-pb-paywalls) trong code ứng dụng của bạn.
1. Triển khai `AdaptyUiObserverModeHandler`.
Sự kiện `onPurchaseInitiated` sẽ thông báo cho bạn khi người dùng bắt đầu thực hiện mua hàng. Bạn có thể kích hoạt flow mua hàng tùy chỉnh của mình trong callback này:
```kotlin showLineNumbers
val observerModeHandler =
AdaptyUiObserverModeHandler { product, paywall, paywallView, onStartPurchase, onFinishPurchase ->
onStartPurchase()
yourBillingClient.makePurchase(
product,
onSuccess = { purchase ->
onFinishPurchase()
//handle success
},
onError = {
onFinishPurchase()
//handle error
},
onCancel = {
onFinishPurchase()
//handle cancel
}
)
}
```
```java showLineNumbers
AdaptyUiObserverModeHandler observerModeHandler = (product, paywall, paywallView, onStartPurchase, onFinishPurchase) -> {
onStartPurchase.invoke();
yourBillingClient.makePurchase(
product,
purchase -> {
onFinishPurchase.invoke();
//handle success
},
error -> {
onFinishPurchase.invoke();
//handle error
},
() -> { //cancellation
onFinishPurchase.invoke();
//handle cancel
}
);
};
```
Để xử lý khôi phục giao dịch ở chế độ Observer, hãy override `getRestoreHandler()`. Mặc định nó trả về `null`, tức là sử dụng flow `Adapty.restorePurchases()` tích hợp sẵn của Adapty. Để cung cấp triển khai khôi phục tùy chỉnh:
```kotlin showLineNumbers
val observerModeHandler = object : AdaptyUiObserverModeHandler {
// onPurchaseInitiated implementation (see above)
override fun getRestoreHandler() =
AdaptyUiObserverModeHandler.RestoreHandler { onStartRestore, onFinishRestore ->
onStartRestore()
yourBillingClient.restorePurchases(
onSuccess = { restoredPurchases ->
onFinishRestore()
//handle successful restore
},
onError = {
onFinishRestore()
//handle error
}
)
}
}
```
```java showLineNumbers
AdaptyUiObserverModeHandler observerModeHandler = new AdaptyUiObserverModeHandler() {
// onPurchaseInitiated implementation (see above)
@Override
public RestoreHandler getRestoreHandler() {
return (onStartRestore, onFinishRestore) -> {
onStartRestore.invoke();
yourBillingClient.restorePurchases(
restoredPurchases -> {
onFinishRestore.invoke();
//handle successful restore
},
error -> {
onFinishRestore.invoke();
//handle error
}
);
};
}
};
```
Hãy nhớ gọi các callback sau để thông báo cho AdaptyUI về quá trình mua hàng hoặc khôi phục. Điều này cần thiết để paywall hoạt động đúng, chẳng hạn như hiển thị loader:
| Callback | Mô tả |
| :----------------- |:---------------------------------------------------------------------------------------|
| onStartPurchase() | Callback cần được gọi để thông báo cho AdaptyUI rằng quá trình mua hàng đã bắt đầu. |
| onFinishPurchase() | Callback cần được gọi để thông báo cho AdaptyUI rằng quá trình mua hàng đã kết thúc. |
| onStartRestore() | Tùy chọn. Callback có thể được gọi để thông báo cho AdaptyUI rằng quá trình khôi phục đã bắt đầu. |
| onFinishRestore() | Tùy chọn. Callback có thể được gọi để thông báo cho AdaptyUI rằng quá trình khôi phục đã kết thúc. |
2. Để hiển thị paywall trực quan trên màn hình thiết bị, bạn cần cấu hình nó trước.
Để làm điều đó, gọi phương thức `AdaptyUI.getPaywallView()` hoặc tạo `AdaptyPaywallView` trực tiếp:
```kotlin showLineNumbers
val paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
products,
eventListener,
personalizedOfferResolver,
tagResolver,
timerResolver,
observerModeHandler,
)
```
```kotlin showLineNumbers
val paywallView =
AdaptyPaywallView(activity) // or retrieve it from xml
...
with(paywallView) {
showPaywall(
viewConfiguration,
products,
eventListener,
personalizedOfferResolver,
tagResolver,
timerResolver,
observerModeHandler,
)
}
```
```java showLineNumbers
AdaptyPaywallView paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
products,
eventListener,
personalizedOfferResolver,
tagResolver,
timerResolver,
observerModeHandler
);
```
```java showLineNumbers
AdaptyPaywallView paywallView =
new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml
...
paywallView.showPaywall(viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler);
```
```xml showLineNumbers
```
Sau khi view được tạo thành công, bạn có thể thêm nó vào view hierarchy và hiển thị.
Để làm điều đó, sử dụng hàm composable sau:
```kotlin showLineNumbers
AdaptyPaywallScreen(
viewConfiguration,
products,
eventListener,
personalizedOfferResolver,
tagResolver,
timerResolver,
)
```
Các tham số yêu cầu:
| Tham số | Bắt buộc | Mô tả |
|---------|--------|-----------|
| **Products** | tùy chọn | Cung cấp một mảng `AdaptyPaywallProduct` để tối ưu thời gian hiển thị sản phẩm trên màn hình. Nếu truyền `null`, AdaptyUI sẽ tự động lấy các sản phẩm cần thiết. |
| **ViewConfiguration** | bắt buộc | Cung cấp đối tượng `AdaptyViewConfiguration` chứa thông tin trực quan của paywall. Sử dụng phương thức `Adapty.getViewConfiguration(paywall)` để tải nó. Tham khảo chủ đề [Lấy cấu hình trực quan của paywall](#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) để biết thêm chi tiết. |
| **EventListener** | tùy chọn | Cung cấp `AdaptyUiEventListener` để theo dõi các sự kiện paywall. Khuyến nghị mở rộng AdaptyUiDefaultEventListener để dễ sử dụng. Tham khảo chủ đề [Xử lý sự kiện paywall](android-handling-events) để biết thêm chi tiết. |
| **PersonalizedOfferResolver** | tùy chọn | Để chỉ định giá cá nhân hóa ([đọc thêm](https://developer.android.com/google/play/billing/integrate#personalized-price)), hãy triển khai `AdaptyUiPersonalizedOfferResolver` và truyền logic của bạn để ánh xạ `AdaptyPaywallProduct` thành true nếu giá sản phẩm được cá nhân hóa, ngược lại là false. |
| **TagResolver** | tùy chọn | Sử dụng `AdaptyUiTagResolver` để phân giải các custom tag trong văn bản paywall. Resolver này nhận tham số tag và phân giải thành chuỗi tương ứng. Tham khảo chủ đề Custom tags in Paywall Builder để biết thêm chi tiết. |
| **ObserverModeHandler** | bắt buộc cho chế độ Observer | `AdaptyUiObserverModeHandler` mà bạn đã triển khai ở bước trước. |
| **variationId** | bắt buộc | Chuỗi định danh của biến thể. Bạn có thể lấy nó bằng thuộc tính `variationId` của đối tượng [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). |
| **transaction** | bắt buộc | Với iOS, StoreKit1: đối tượng [`SKPaymentTransaction`](https://developer.apple.com/documentation/storekit/skpaymenttransaction).
Với iOS, StoreKit 2: đối tượng [Transaction](https://developer.apple.com/documentation/storekit/transaction).
Với Android: Chuỗi định danh (`purchase.getOrderId()`) của giao dịch mua, trong đó purchase là một instance của lớp [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) trong thư viện billing.
|
Trước khi bắt đầu hiển thị paywall (Nhấp để mở rộng)
1. Thiết lập tích hợp ban đầu của Adapty [với Google Play](initial-android) và [với App Store](initial_ios).
2. Cài đặt và cấu hình Adapty SDK. Đảm bảo đặt tham số `observerMode` thành `true`. Tham khảo hướng dẫn theo framework [cho Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk) và [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk).
3. [Tạo sản phẩm](create-product) trong Adapty Dashboard.
4. [Cấu hình paywall, gán sản phẩm cho chúng](create-paywall) và tùy chỉnh bằng Paywall Builder trong Adapty Dashboard.
5. [Tạo placement và gán paywall vào chúng](create-placement) trong Adapty Dashboard.
6. [Lấy paywall Paywall Builder và cấu hình của chúng](android-get-pb-paywalls) trong code ứng dụng của bạn.
1. Triển khai `AdaptyUiObserverModeHandler`. Callback của `AdaptyUiObserverModeHandler` (`onPurchaseInitiated`) thông báo cho bạn khi người dùng bắt đầu thực hiện mua hàng. Bạn có thể kích hoạt flow mua hàng tùy chỉnh của mình trong callback này như sau:
```kotlin showLineNumbers
val observerModeHandler =
AdaptyUiObserverModeHandler { product, paywall, paywallView, onStartPurchase, onFinishPurchase ->
onStartPurchase()
yourBillingClient.makePurchase(
product,
onSuccess = { purchase ->
onFinishPurchase()
//handle success
},
onError = {
onFinishPurchase()
//handle error
},
onCancel = {
onFinishPurchase()
//handle cancel
}
)
}
```
```java showLineNumbers
AdaptyUiObserverModeHandler observerModeHandler = (product, paywall, paywallView, onStartPurchase, onFinishPurchase) -> {
onStartPurchase.invoke();
yourBillingClient.makePurchase(
product,
purchase -> {
onFinishPurchase.invoke();
//handle success
},
error -> {
onFinishPurchase.invoke();
//handle error
},
() -> { //cancellation
onFinishPurchase.invoke();
//handle cancel
}
);
};
```
Ngoài ra, hãy nhớ gọi các callback này cho AdaptyUI. Điều này cần thiết để paywall hoạt động đúng, chẳng hạn như hiển thị loader:
| Callback trong Kotlin | Callback trong Java | Mô tả |
| :----------------- | :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------- |
| onStartPurchase() | onStartPurchase.invoke() | Callback cần được gọi để thông báo cho AdaptyUI rằng quá trình mua hàng đã bắt đầu. |
| onFinishPurchase() | onFinishPurchase.invoke() | Callback cần được gọi để thông báo cho AdaptyUI rằng quá trình mua hàng đã hoàn thành (thành công, thất bại hoặc bị hủy). |
2. Để hiển thị paywall trực quan, bạn cần khởi tạo nó trước. Để làm điều đó, gọi phương thức `AdaptyUI.getPaywallView()` hoặc tạo `AdaptyPaywallView` trực tiếp:
```kotlin showLineNumbers
val paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
products,
AdaptyPaywallInsets.of(topInset, bottomInset),
eventListener,
personalizedOfferResolver,
tagResolver,
observerModeHandler,
)
//======= OR =======
val paywallView =
AdaptyPaywallView(activity) // or retrieve it from xml
...
with(paywallView) {
setEventListener(eventListener)
setObserverModeHandler(observerModeHandler)
showPaywall(
viewConfiguration,
products,
AdaptyPaywallInsets.of(topInset, bottomInset),
personalizedOfferResolver,
tagResolver,
)
}
```
```java showLineNumbers
AdaptyPaywallView paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
products,
AdaptyPaywallInsets.of(topInset, bottomInset),
eventListener,
personalizedOfferResolver,
tagResolver,
observerModeHandler
);
//======= OR =======
AdaptyPaywallView paywallView =
new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml
...
paywallView.setEventListener(eventListener);
paywallView.setObserverModeHandler(observerModeHandler);
paywallView.showPaywall(viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), personalizedOfferResolver);
```
```xml showLineNumbers
```
Sau khi view được tạo thành công, bạn có thể thêm nó vào view hierarchy và hiển thị.
Các tham số yêu cầu:
| Tham số | Bắt buộc | Mô tả |
|---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Products** | tùy chọn | Cung cấp một mảng `AdaptyPaywallProduct` để tối ưu thời gian hiển thị sản phẩm trên màn hình. Nếu truyền `null`, AdaptyUI sẽ tự động lấy các sản phẩm cần thiết. |
| **ViewConfiguration** | bắt buộc | Cung cấp đối tượng `AdaptyViewConfiguration` chứa thông tin trực quan của paywall. Sử dụng phương thức `Adapty.getViewConfiguration(paywall)` để tải nó. Tham khảo chủ đề [Lấy cấu hình trực quan của paywall](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) để biết thêm chi tiết. |
| **Insets** | bắt buộc | Định nghĩa đối tượng `AdaptyPaywallInsets` chứa thông tin về vùng bị system bar che khuất, tạo margin dọc cho nội dung. Nếu cả status bar lẫn navigation bar không che `AdaptyPaywallView`, truyền `AdaptyPaywallInsets.NONE`. Đối với chế độ toàn màn hình khi system bar che một phần giao diện, lấy insets theo hướng dẫn bên dưới bảng. |
| **EventListener** | tùy chọn | Cung cấp `AdaptyUiEventListener` để theo dõi các sự kiện paywall. Khuyến nghị mở rộng AdaptyUiDefaultEventListener để dễ sử dụng. Tham khảo chủ đề [Xử lý sự kiện paywall](android-handling-events) để biết thêm chi tiết. |
| **PersonalizedOfferResolver** | tùy chọn | Để chỉ định giá cá nhân hóa ([đọc thêm](https://developer.android.com/google/play/billing/integrate#personalized-price)), hãy triển khai `AdaptyUiPersonalizedOfferResolver` và truyền logic của bạn để ánh xạ `AdaptyPaywallProduct` thành true nếu giá sản phẩm được cá nhân hóa, ngược lại là false. |
| **TagResolver** | tùy chọn | Sử dụng `AdaptyUiTagResolver` để phân giải các custom tag trong văn bản paywall. Resolver này nhận tham số tag và phân giải thành chuỗi tương ứng. Tham khảo chủ đề Custom tags in Paywall Builder để biết thêm chi tiết. |
| **ObserverModeHandler** | bắt buộc cho chế độ Observer | `AdaptyUiObserverModeHandler` mà bạn đã triển khai ở bước trước. |
| **variationId** | bắt buộc | Chuỗi định danh của biến thể. Bạn có thể lấy nó bằng thuộc tính `variationId` của đối tượng [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). |
| **transaction** | bắt buộc | Với iOS, StoreKit1: đối tượng [`SKPaymentTransaction`](https://developer.apple.com/documentation/storekit/skpaymenttransaction).
Với iOS, StoreKit 2: đối tượng [Transaction](https://developer.apple.com/documentation/storekit/transaction).
Với Android: Chuỗi định danh (`purchase.getOrderId()`) của giao dịch mua, trong đó purchase là một instance của lớp [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) trong thư viện billing.
|
Đối với chế độ toàn màn hình khi system bar che một phần giao diện, lấy insets theo cách sau:
```kotlin showLineNumbers
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
//create extension function
fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) {
ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
ViewCompat.setOnApplyWindowInsetsListener(this, null)
action(systemBarInsets)
insets
}
}
//and then use it with the view
paywallView.onReceiveSystemBarsInsets { insets ->
val paywallInsets = AdaptyPaywallInsets.of(insets.top, insets.bottom)
paywallView.setEventListener(eventListener)
paywallView.setObserverModeHandler(observerModeHandler)
paywallView.showPaywall(viewConfig, products, paywallInsets, personalizedOfferResolver, tagResolver)
}
```
```java showLineNumbers
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
...
ViewCompat.setOnApplyWindowInsetsListener(paywallView, (view, insets) -> {
Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars());
ViewCompat.setOnApplyWindowInsetsListener(paywallView, null);
AdaptyPaywallInsets paywallInsets =
AdaptyPaywallInsets.of(systemBarInsets.top, systemBarInsets.bottom);
paywallView.setEventListener(eventListener);
paywallView.setObserverModeHandler(observerModeHandler);
paywallView.showPaywall(viewConfiguration, products, paywallInsets, personalizedOfferResolver, tagResolver);
return insets;
});
```
Kết quả trả về:
| Đối tượng | Mô tả |
| :------------------ | :------------------------------------------------- |
| `AdaptyPaywallView` | Đối tượng đại diện cho màn hình paywall được yêu cầu. |
:::warning
Đừng quên [Liên kết paywall với giao dịch mua hàng](report-transactions-observer-mode-android). Nếu không, Adapty sẽ không xác định được paywall nguồn của giao dịch mua.
:::
---
# File: android-troubleshoot-purchases
---
---
title: "Xử lý sự cố mua hàng trong Android SDK"
description: "Xử lý sự cố mua hàng trong Android SDK"
---
Hướng dẫn này giúp bạn giải quyết các vấn đề thường gặp khi triển khai mua hàng thủ công trong Android SDK.
## makePurchase được gọi thành công nhưng hồ sơ người dùng không được cập nhật \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\}
**Vấn đề**: Phương thức `makePurchase` hoàn thành thành công, nhưng hồ sơ người dùng và trạng thái gói đăng ký không được cập nhật trong Adapty.
**Nguyên nhân**: Thường là do thiết lập Google Play Store chưa đầy đủ hoặc có vấn đề về cấu hình.
**Giải pháp**: Đảm bảo bạn đã hoàn thành tất cả [các bước thiết lập Google Play](initial-android).
## makePurchase bị gọi hai lần \{#makepurchase-is-invoked-twice\}
**Vấn đề**: Phương thức `makePurchase` đang được gọi nhiều lần cho cùng một giao dịch mua.
**Nguyên nhân**: Điều này thường xảy ra khi flow mua hàng được kích hoạt nhiều lần do vấn đề quản lý trạng thái UI hoặc người dùng thao tác quá nhanh.
**Giải pháp**: Đảm bảo bạn đã hoàn thành tất cả [các bước thiết lập Google Play](initial-android).
## AdaptyError.cantMakePayments trong chế độ observer \{#adaptyelrorcantmakepayments-in-observer-mode\}
**Vấn đề**: Bạn nhận được `AdaptyError.cantMakePayments` khi sử dụng `makePurchase` trong chế độ observer.
**Nguyên nhân**: Trong chế độ observer, bạn nên tự xử lý các giao dịch mua ở phía mình, không dùng phương thức `makePurchase` của Adapty.
**Giải pháp**: Nếu bạn dùng `makePurchase` để thực hiện mua hàng, hãy tắt chế độ observer. Bạn cần chọn một trong hai: dùng `makePurchase` hoặc tự xử lý mua hàng ở phía mình trong chế độ observer. Xem [Triển khai chế độ Observer](implement-observer-mode-android) để biết thêm chi tiết.
## Lỗi Adapty: (code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\}
**Vấn đề**: Bạn nhận được lỗi billing không khả dụng từ Google Play Store.
**Nguyên nhân**: Lỗi này không liên quan đến Adapty. Đây là lỗi của Google Play Billing Library, cho biết tính năng thanh toán không khả dụng trên thiết bị.
**Giải pháp**: Lỗi này không liên quan đến Adapty. Bạn có thể tìm hiểu thêm trong tài liệu Play Store: [Handle BillingResult response codes](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers.
## Không tìm thấy makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\}
**Vấn đề**: Bạn gặp sự cố với `makePurchasesCompletionHandlers` không được tìm thấy.
**Nguyên nhân**: Thường liên quan đến các vấn đề khi kiểm thử trong môi trường sandbox.
**Giải pháp**: Tạo người dùng sandbox mới và thử lại. Cách này thường giải quyết được các vấn đề về purchase completion handler trong môi trường sandbox.
## Các vấn đề khác \{#other-issues\}
**Vấn đề**: Bạn gặp phải các vấn đề liên quan đến mua hàng khác chưa được đề cập ở trên.
**Giải pháp**: Nếu cần, hãy migrate SDK lên phiên bản mới nhất theo [hướng dẫn migration](android-sdk-migration-guides). Nhiều vấn đề đã được khắc phục trong các phiên bản SDK mới hơn.
---
# File: android-identifying-users
---
---
title: "Xác định người dùng trong Android SDK"
description: "Xác định người dùng trong Adapty để cải thiện trải nghiệm gói đăng ký được cá nhân hóa (Android)."
---
Adapty tạo một ID hồ sơ nội bộ cho mỗi người dùng. Tuy nhiên, nếu bạn có hệ thống xác thực riêng, bạn nên đặt Customer User ID của mình. Bạn có thể tìm người dùng theo Customer User ID của họ trong phần [Profiles](profiles-crm) và sử dụng nó trong [server-side API](getting-started-with-server-side-api), ID này sẽ được gửi đến tất cả các tích hợp.
### Đặt customer user ID khi khởi tạo \{#setting-customer-user-id-on-configuration\}
Nếu bạn có user ID trong quá trình khởi tạo, chỉ cần truyền nó làm tham số `customerUserId` vào phương thức `.activate()`:
```kotlin showLineNumbers
Adapty.activate(applicationContext, "PUBLIC_SDK_KEY", customerUserId = "YOUR_USER_ID")
```
:::tip
Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác.
:::
### Đặt customer user ID sau khi khởi tạo \{#setting-customer-user-id-after-configuration\}
Nếu bạn chưa có user ID khi khởi tạo SDK, bạn có thể đặt nó bất cứ lúc nào sau đó bằng phương thức `.identify()`. Trường hợp phổ biến nhất để sử dụng phương thức này là sau khi đăng ký hoặc đăng nhập, khi người dùng chuyển từ người dùng ẩn danh sang người dùng đã xác thực.
```kotlin showLineNumbers
Adapty.identify("YOUR_USER_ID") { error ->
if (error == null) {
// successful identify
}
}
```
```java showLineNumbers
Adapty.identify("YOUR_USER_ID", error -> {
if (error == null) {
// successful identify
}
});
```
Các tham số yêu cầu:
- **Customer User ID** (bắt buộc): chuỗi định danh người dùng.
:::warning
Gửi lại dữ liệu người dùng quan trọng
Trong một số trường hợp, chẳng hạn khi người dùng đăng nhập lại vào tài khoản của họ, máy chủ Adapty đã có thông tin về người dùng đó. Trong những trường hợp này, Adapty SDK sẽ tự động chuyển sang làm việc với người dùng mới. Nếu bạn đã truyền dữ liệu cho người dùng ẩn danh, chẳng hạn như các thuộc tính tùy chỉnh hoặc attribution từ các mạng bên thứ ba, bạn cần gửi lại dữ liệu đó cho người dùng đã được xác định.
Ngoài ra, cần lưu ý rằng bạn nên yêu cầu lại tất cả các paywall và sản phẩm sau khi xác định người dùng, vì dữ liệu của người dùng mới có thể khác.
:::
### Đăng xuất và đăng nhập \{#logging-out-and-logging-in\}
Bạn có thể đăng xuất người dùng bất cứ lúc nào bằng cách gọi phương thức `.logout()`:
```kotlin showLineNumbers
Adapty.logout { error ->
if (error == null) {
// successful logout
}
}
```
```java showLineNumbers
Adapty.logout(error -> {
if (error == null) {
// successful logout
}
});
```
Sau đó, bạn có thể đăng nhập người dùng bằng phương thức `.identify()`.
### Phát hiện người dùng trên nhiều thiết bị \{#detect-users-across-devices\}
---
no_index: true
---
Khi SDK được kích hoạt, nó tự động đọc các quyền hiện có của người dùng từ StoreKit (iOS) hoặc Google Play Billing (Android) và đồng bộ chúng với backend của Adapty. Một gói đăng ký đang hoạt động sẽ xuất hiện trên hồ sơ người dùng Adapty mà không cần ứng dụng gọi `restorePurchases`.
Điều **không** xảy ra tự động là nhận diện rằng một hồ sơ người dùng trên thiết bị mới thuộc về cùng một người dùng như hồ sơ trên thiết bị ban đầu. Adapty khớp các hồ sơ người dùng theo Customer User ID, vì vậy tính liên tục danh tính phụ thuộc vào những gì bạn sử dụng làm CUID.
**Những gì Adapty có thể phát hiện trên nhiều thiết bị**
| Cài đặt của bạn | Adapty phát hiện được gì | Bạn phải làm gì |
| --- | --- | --- |
| Customer User ID = `device_id` (không đăng nhập ứng dụng) | Thiết bị mới nhận được CUID khác và do đó có hồ sơ người dùng khác. Gói đăng ký đồng bộ với hồ sơ người dùng mới thông qua sự kiện **Access level updated**, nhưng `subscription_started` không kích hoạt — hồ sơ người dùng mới được coi là người kế thừa của giao dịch mua ban đầu. Các phân tích dựa trên `subscription_started` sẽ đếm thiếu những người dùng quay lại. | Sử dụng ID tài khoản ổn định làm Customer User ID để người dùng quay lại khớp với hồ sơ người dùng hiện có trên các thiết bị. |
| Customer User ID = ID tài khoản ổn định (đăng nhập trên mọi thiết bị) | SDK tự động đồng bộ gói đăng ký khi `activate()`, và `identify()` khớp hồ sơ người dùng hiện có theo CUID. | Không cần cài đặt thêm — cả danh tính lẫn gói đăng ký đều được xử lý tự động. |
| Người kế thừa Apple Family Sharing | Thành viên gia đình nhận gói đăng ký thông qua sự kiện **Access level updated** — `subscription_started` không kích hoạt. | Lắng nghe sự kiện **Access level updated**. Xem [Apple Family Sharing](apple-family-sharing) để biết ma trận sự kiện đầy đủ. |
| Cùng tài khoản Apple/Google, người dùng khác nhau trong ứng dụng | Hồ sơ người dùng đầu tiên ghi lại giao dịch mua trở thành hồ sơ cha. Các hồ sơ người dùng tiếp theo thấy gói đăng ký thông qua chuỗi kế thừa, với một sự kiện **Access level updated**. | Yêu cầu đăng nhập, sau đó chọn [chế độ chia sẻ](sharing-paid-access-between-user-accounts) phù hợp với mô hình của bạn. |
**Khôi phục giao dịch mua trên thiết bị mới**
Hiển thị nút "Khôi phục giao dịch mua" do người dùng khởi tạo trên paywall của bạn. Apple App Review (hướng dẫn 3.1.1) yêu cầu có nút này, và nó đóng vai trò dự phòng khi quá trình đồng bộ tự động bỏ sót một trường hợp đặc biệt. Nút này nên gọi `restorePurchases` trên SDK của bạn.
Không cần gọi `restorePurchases` theo chương trình khi khởi chạy lần đầu trong điều kiện sử dụng bình thường — SDK đã thực hiện tương đương khi `activate()`. Chỉ dùng các lệnh gọi theo chương trình để buộc kiểm tra biên lai mới, ví dụ khi debug trường hợp mất quyền truy cập sau khi `activate()` đã hoàn thành.
---
# File: android-setting-user-attributes
---
---
title: "Thiết lập thuộc tính người dùng trong Android SDK"
description: "Tìm hiểu cách thiết lập thuộc tính người dùng trong Adapty để phân khúc đối tượng hiệu quả hơn."
---
Bạn có thể thiết lập các thuộc tính tùy chọn như email, số điện thoại, v.v. cho người dùng trong ứng dụng của mình. Sau đó, bạn có thể sử dụng các thuộc tính này để tạo [phân khúc](segments) người dùng hoặc chỉ đơn giản là xem chúng trong CRM.
### Thiết lập thuộc tính người dùng \{#setting-user-attributes\}
Để thiết lập thuộc tính người dùng, hãy gọi phương thức `.updateProfile()`:
```kotlin showLineNumbers
val builder = AdaptyProfileParameters.Builder()
.withEmail("email@email.com")
.withPhoneNumber("+18888888888")
.withFirstName("John")
.withLastName("Appleseed")
.withGender(AdaptyProfile.Gender.OTHER)
.withBirthday(AdaptyProfile.Date(1970, 1, 3))
Adapty.updateProfile(builder.build()) { error ->
if (error != null) {
// handle the error
}
}
```
```java showLineNumbers
AdaptyProfileParameters.Builder builder = new AdaptyProfileParameters.Builder()
.withEmail("email@email.com")
.withPhoneNumber("+18888888888")
.withFirstName("John")
.withLastName("Appleseed")
.withGender(AdaptyProfile.Gender.OTHER)
.withBirthday(new AdaptyProfile.Date(1970, 1, 3));
Adapty.updateProfile(builder.build(), error -> {
if (error != null) {
// handle the error
}
});
```
Lưu ý rằng các thuộc tính bạn đã thiết lập trước đó bằng phương thức `updateProfile` sẽ không bị xóa.
:::tip
Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác.
:::
### Danh sách các key được phép \{#the-allowed-keys-list\}
Các key `` được phép của `AdaptyProfileParameters.Builder` và các giá trị `` tương ứng được liệt kê bên dưới:
| Key | Value |
|---|-----|
| email
phoneNumber
firstName
lastName
| String |
| gender | Enum, các giá trị được phép: `female`, `male`, `other` |
| birthday | Date |
### Thuộc tính người dùng tùy chỉnh \{#custom-user-attributes\}
Bạn có thể thiết lập các thuộc tính tùy chỉnh của riêng mình. Những thuộc tính này thường liên quan đến cách người dùng sử dụng ứng dụng. Ví dụ, đối với ứng dụng thể dục, có thể là số buổi tập mỗi tuần; đối với ứng dụng học ngôn ngữ, có thể là trình độ của người dùng, v.v. Bạn có thể dùng chúng trong các phân khúc để tạo paywall và ưu đãi có mục tiêu, đồng thời sử dụng trong analytics để tìm ra các chỉ số sản phẩm nào ảnh hưởng nhiều nhất đến doanh thu.
```kotlin showLineNumbers
builder.withCustomAttribute("key1", "value1")
```
```java showLineNumbers
builder.withCustomAttribute("key1", "value1");
```
Để xóa key hiện có, hãy dùng phương thức `.withRemoved(customAttributeForKey:)`:
```kotlin showLineNumbers
builder.withRemovedCustomAttribute("key2")
```
```java showLineNumbers
builder.withRemovedCustomAttribute("key2");
```
Đôi khi bạn cần biết những thuộc tính tùy chỉnh nào đã được thiết lập trước đó. Để làm điều này, hãy sử dụng trường `customAttributes` của đối tượng `AdaptyProfile`.
:::warning
Lưu ý rằng giá trị của `customAttributes` có thể không còn cập nhật, vì thuộc tính người dùng có thể được gửi từ nhiều thiết bị khác nhau bất kỳ lúc nào, nên các thuộc tính trên server có thể đã thay đổi kể từ lần đồng bộ cuối cùng.
:::
### Giới hạn \{#limits\}
- Tối đa 30 thuộc tính tùy chỉnh mỗi người dùng
- Tên key tối đa 30 ký tự. Tên key có thể bao gồm các ký tự chữ và số cùng với các ký tự sau: `_` `-` `.`
- Giá trị có thể là chuỗi hoặc số thực (float) với tối đa 50 ký tự.
---
# File: android-listen-subscription-changes
---
---
title: "Kiểm tra trạng thái gói đăng ký trong Android SDK"
description: "Theo dõi và quản lý trạng thái gói đăng ký của người dùng trong Adapty để cải thiện việc giữ chân khách hàng trong ứng dụng Android của bạn."
---
Với Adapty, việc theo dõi trạng thái gói đăng ký trở nên đơn giản hơn bao giờ hết. Bạn không cần phải nhập thủ công các ID sản phẩm vào code. Thay vào đó, bạn có thể dễ dàng xác nhận trạng thái gói đăng ký của người dùng bằng cách kiểm tra [mức độ truy cập](access-level) đang hoạt động.
Trước khi bắt đầu kiểm tra trạng thái gói đăng ký, hãy thiết lập [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn).
## Mức độ truy cập và đối tượng AdaptyProfile \{#access-level-and-the-adaptyprofile-object\}
Mức độ truy cập là các thuộc tính của đối tượng [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/). Chúng tôi khuyến nghị bạn lấy hồ sơ người dùng khi ứng dụng khởi động, chẳng hạn như khi [xác định người dùng](android-identifying-users#setting-customer-user-id-on-configuration), rồi cập nhật lại mỗi khi có thay đổi. Nhờ vậy, bạn có thể sử dụng đối tượng profile mà không cần phải gọi lại liên tục.
Để nhận thông báo khi profile được cập nhật, hãy lắng nghe các thay đổi của profile như mô tả trong phần [Lắng nghe cập nhật profile, bao gồm mức độ truy cập](android-listen-subscription-changes) bên dưới.
:::tip
Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác.
:::
## Lấy mức độ truy cập từ server \{#retrieving-the-access-level-from-the-server\}
Để lấy mức độ truy cập từ server, sử dụng phương thức `.getProfile()`:
```kotlin showLineNumbers
Adapty.getProfile { result ->
when (result) {
is AdaptyResult.Success -> {
val profile = result.value
// check the access
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getProfile(result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyProfile profile = ((AdaptyResult.Success) result).getValue();
// check the access
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
Tham số phản hồi:
| Tham số | Mô tả |
| --------- | ------------------------------------------------------------ |
| Profile | Một đối tượng [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/). Thông thường, bạn chỉ cần kiểm tra trạng thái mức độ truy cập của profile để xác định xem người dùng có quyền truy cập premium vào ứng dụng hay không.
Phương thức `.getProfile` cung cấp kết quả mới nhất vì nó luôn cố gắng truy vấn API. Nếu vì lý do nào đó (ví dụ: không có kết nối internet), Adapty SDK không thể lấy thông tin từ server, dữ liệu từ cache sẽ được trả về. Cần lưu ý rằng Adapty SDK cập nhật cache `AdaptyProfile` định kỳ để đảm bảo thông tin luôn được cập nhật nhất có thể.
|
Phương thức `.getProfile()` cung cấp cho bạn hồ sơ người dùng, từ đó bạn có thể lấy trạng thái mức độ truy cập. Một ứng dụng có thể có nhiều mức độ truy cập. Ví dụ, nếu bạn có ứng dụng báo và bán gói đăng ký cho các chủ đề khác nhau một cách độc lập, bạn có thể tạo các mức độ truy cập "sports" và "science". Tuy nhiên, trong hầu hết các trường hợp, bạn chỉ cần một mức độ truy cập duy nhất — khi đó, bạn có thể dùng mức độ truy cập mặc định là "premium".
Dưới đây là ví dụ kiểm tra mức độ truy cập "premium" mặc định:
```kotlin showLineNumbers
Adapty.getProfile { result ->
when (result) {
is AdaptyResult.Success -> {
val profile = result.value
if (profile.accessLevels["premium"]?.isActive == true) {
// grant access to premium features
}
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getProfile(result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyProfile profile = ((AdaptyResult.Success) result).getValue();
AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("premium");
if (premium != null && premium.isActive()) {
// grant access to premium features
}
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
### Lắng nghe cập nhật trạng thái gói đăng ký \{#listening-for-subscription-status-updates\}
Mỗi khi gói đăng ký của người dùng thay đổi, Adapty sẽ kích hoạt một sự kiện.
Để nhận tin nhắn từ Adapty, bạn cần thực hiện một số cấu hình bổ sung:
```kotlin showLineNumbers
Adapty.setOnProfileUpdatedListener { profile ->
// handle any changes to subscription state
}
```
```java showLineNumbers t
Adapty.setOnProfileUpdatedListener(profile -> {
// handle any changes to subscription state
});
```
Adapty cũng kích hoạt một sự kiện khi ứng dụng khởi động. Trong trường hợp này, trạng thái gói đăng ký được lưu trong cache sẽ được truyền qua.
### Cache trạng thái gói đăng ký \{#subscription-status-cache\}
Cache được tích hợp trong Adapty SDK lưu trữ trạng thái gói đăng ký của hồ sơ người dùng. Điều này có nghĩa là ngay cả khi server không khả dụng, dữ liệu trong cache vẫn có thể được truy cập để cung cấp thông tin về trạng thái gói đăng ký của hồ sơ người dùng.
Tuy nhiên, cần lưu ý rằng bạn không thể yêu cầu dữ liệu trực tiếp từ cache. SDK định kỳ truy vấn server mỗi phút một lần để kiểm tra các cập nhật hoặc thay đổi liên quan đến hồ sơ người dùng. Nếu có bất kỳ thay đổi nào — chẳng hạn như giao dịch mới hoặc các cập nhật khác — chúng sẽ được đồng bộ vào dữ liệu cache để giữ cho cache luôn đồng bộ với server.
---
# File: kids-mode-android
---
---
title: "Chế Độ Trẻ Em trong Android SDK"
description: "Dễ dàng bật Chế Độ Trẻ Em để tuân thủ chính sách của Google. Không thu thập GAID hay dữ liệu quảng cáo trong Android SDK."
---
Nếu ứng dụng Android của bạn dành cho trẻ em, bạn phải tuân theo chính sách của [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Nếu bạn đang dùng Adapty SDK, chỉ cần vài bước đơn giản là có thể cấu hình để đáp ứng các chính sách này và vượt qua quá trình xét duyệt của cửa hàng.
## Cần làm gì? \{#whats-required\}
Bạn cần cấu hình Adapty SDK để tắt việc thu thập:
- [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248)
- [Địa chỉ IP](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf)
Ngoài ra, chúng tôi khuyến nghị sử dụng customer user ID một cách thận trọng. User ID có định dạng `` chắc chắn sẽ bị coi là thu thập dữ liệu cá nhân, tương tự như việc dùng email. Đối với Chế Độ Trẻ Em, cách tốt nhất là sử dụng các định danh ngẫu nhiên hoặc ẩn danh (ví dụ: ID đã được hash hoặc UUID do thiết bị tạo ra) để đảm bảo tuân thủ.
## Bật Chế Độ Trẻ Em \{#enabling-kids-mode\}
### Cập nhật trong Adapty Dashboard \{#updates-in-the-adapty-dashboard\}
Trong Adapty Dashboard, bạn cần tắt tính năng thu thập địa chỉ IP. Để làm điều này, hãy vào [App settings](https://app.adapty.io/settings/general) và nhấn **Disable IP address collection** trong phần **Collect users' IP address**.
### Cập nhật trong code ứng dụng di động \{#updates-in-your-mobile-app-code\}
Để tuân thủ các chính sách, bạn cần tắt việc thu thập Android Advertising ID (AAID/GAID) và địa chỉ IP khi khởi tạo Adapty SDK:
**Kotlin:**
```kotlin showLineNumbers
override fun onCreate() {
super.onCreate()
Adapty.activate(
applicationContext,
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
// highlight-start
.withAdIdCollectionDisabled(true) // set to `true`
.withIpAddressCollectionDisabled(true) // set to `true`
// highlight-end
.build()
)
}
```
**Java:**
```java showLineNumbers
@Override
public void onCreate() {
super.onCreate();
Adapty.activate(
applicationContext,
new AdaptyConfig.Builder("PUBLIC_SDK_KEY")
// highlight-start
.withAdIdCollectionDisabled(true) // set to `true`
.withIpAddressCollectionDisabled(true) // set to `true`
// highlight-end
.build()
);
}
```
### Cập nhật trong Android manifest \{#updates-in-your-android-manifest\}
:::note
Nếu ứng dụng của bạn chỉ nhắm đến trẻ em và biên dịch với Android 13 (API 33) trở lên, Google Play yêu cầu bạn không được yêu cầu quyền `AD_ID`. Một SDK khác trong ứng dụng của bạn (analytics, attribution, hoặc quảng cáo) có thể thêm quyền này thông qua manifest merging. Việc đặt `withAdIdCollectionDisabled(true)` sẽ ngăn Adapty thu thập ID, nhưng không xóa được quyền do SDK khác khai báo.
:::
Để xóa quyền đó, hãy thêm đoạn sau vào bên trong phần tử `` của `app/src/main/AndroidManifest.xml`. Phần tử `` phải khai báo `xmlns:tools="http://schemas.android.com/tools"`.
```xml showLineNumbers title="AndroidManifest.xml"
```
---
# File: android-get-onboardings
---
---
title: "Lấy onboarding trong Android SDK"
description: "Tìm hiểu cách lấy onboarding trong Adapty cho Android."
---
Sau khi [bạn đã thiết kế phần giao diện cho onboarding](design-onboarding) bằng trình thiết kế trong Adapty Dashboard, bạn có thể hiển thị nó trong ứng dụng Android của mình. Bước đầu tiên trong quá trình này là lấy onboarding được liên kết với placement và cấu hình hiển thị của nó như mô tả bên dưới.
Trước khi bắt đầu, hãy đảm bảo rằng:
1. Bạn đã cài đặt [Adapty Android SDK](sdk-installation-android) phiên bản 3.8.0 trở lên.
2. Bạn đã [tạo một onboarding](create-onboarding).
3. Bạn đã thêm onboarding vào một [placement](placements).
## Lấy onboarding \{#fetch-onboarding\}
Khi bạn tạo một [onboarding](onboardings) bằng trình thiết kế không cần code của chúng tôi, nó được lưu trữ dưới dạng một container chứa cấu hình mà ứng dụng của bạn cần lấy và hiển thị. Container này quản lý toàn bộ trải nghiệm — nội dung nào xuất hiện, cách trình bày, và cách xử lý các tương tác của người dùng (như câu trả lời quiz hoặc dữ liệu nhập form). Container cũng tự động theo dõi các sự kiện analytics, vì vậy bạn không cần triển khai theo dõi view riêng biệt.
Để có hiệu suất tốt nhất, hãy lấy cấu hình onboarding sớm để hình ảnh có đủ thời gian tải xuống trước khi hiển thị cho người dùng.
Để lấy một onboarding, sử dụng phương thức `getOnboarding`:
```kotlin showLineNumbers
Adapty.getOnboarding("YOUR_PLACEMENT_ID") { result ->
when (result) {
is AdaptyResult.Success -> {
val onboarding = result.value
// the requested onboarding
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
Tham số:
| Tham số | Bắt buộc | Mô tả |
|---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **placementId** | bắt buộc | Mã định danh của [Placement](placements) mong muốn. Đây là giá trị bạn đã chỉ định khi tạo placement trong Adapty Dashboard. |
| **locale** | tùy chọn
mặc định: `en`
| Mã định danh của bản địa hóa onboarding. Tham số này được kỳ vọng là mã ngôn ngữ bao gồm một hoặc hai subtag được phân cách bằng dấu trừ (**-**). Subtag đầu tiên là ngôn ngữ, subtag thứ hai là vùng.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha (Brazil).
Xem [Localizations and locale codes](localizations-and-locale-codes) để biết thêm thông tin về mã locale và cách chúng tôi khuyến nghị sử dụng chúng.
|
| **fetchPolicy** | mặc định: `.reloadRevalidatingCacheData` | Theo mặc định, SDK sẽ cố tải dữ liệu từ server và trả về dữ liệu đã lưu cache trong trường hợp thất bại. Chúng tôi khuyến nghị tùy chọn này vì nó đảm bảo người dùng của bạn luôn nhận được dữ liệu mới nhất.
Tuy nhiên, nếu bạn cho rằng người dùng của mình có kết nối internet không ổn định, hãy xem xét sử dụng `.returnCacheDataElseLoad` để trả về dữ liệu cache nếu có. Trong trường hợp này, người dùng có thể không nhận được dữ liệu mới nhất tuyệt đối, nhưng họ sẽ có tốc độ tải nhanh hơn, bất kể kết nối internet của họ như thế nào. Cache được cập nhật thường xuyên, vì vậy việc sử dụng nó trong phiên để tránh các yêu cầu mạng là an toàn.
Lưu ý rằng cache vẫn còn nguyên sau khi khởi động lại ứng dụng và chỉ bị xóa khi ứng dụng được cài đặt lại hoặc thông qua việc dọn dẹp thủ công.
Adapty SDK lưu trữ các onboarding cục bộ theo hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và các onboarding dự phòng. Chúng tôi cũng sử dụng CDN để lấy onboarding nhanh hơn và một server dự phòng độc lập trong trường hợp CDN không thể truy cập. Hệ thống này được thiết kế để đảm bảo bạn luôn nhận được phiên bản mới nhất của onboarding trong khi vẫn đảm bảo độ tin cậy ngay cả trong trường hợp kết nối internet kém.
|
| **loadTimeout** | mặc định: 5 giây | Giá trị này giới hạn thời gian chờ cho phương thức này. Nếu hết thời gian chờ, dữ liệu cache hoặc fallback cục bộ sẽ được trả về.
Lưu ý rằng trong một số trường hợp hiếm gặp, phương thức này có thể hết thời gian chờ muộn hơn một chút so với thời gian được chỉ định trong `loadTimeout`, vì thao tác có thể bao gồm nhiều yêu cầu khác nhau bên dưới.
Với Android: Bạn có thể tạo `TimeInterval` bằng các extension function (như `5.seconds`, trong đó `.seconds` từ `import com.adapty.utils.seconds`), hoặc `TimeInterval.seconds(5)`. Để không giới hạn, sử dụng `TimeInterval.INFINITE`.
|
Tham số phản hồi:
| Tham số | Mô tả |
|:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------|
| Onboarding | Một đối tượng [`AdaptyOnboarding`](https://android.adapty.io/adapty/com.adapty.models/-adapty-onboarding/) với: mã định danh và cấu hình onboarding, Remote Config, và một số thuộc tính khác. |
## Tăng tốc lấy onboarding với onboarding đối tượng mặc định \{#speed-up-onboarding-fetching-with-default-audience-onboarding\}
Thông thường, onboarding được lấy gần như ngay lập tức, vì vậy bạn không cần lo lắng về việc tăng tốc quá trình này. Tuy nhiên, trong những trường hợp bạn có nhiều đối tượng và onboarding, và người dùng của bạn có kết nối internet yếu, việc lấy onboarding có thể mất nhiều thời gian hơn mong muốn. Trong những tình huống đó, bạn có thể muốn hiển thị một onboarding mặc định để đảm bảo trải nghiệm người dùng mượt mà hơn là không hiển thị onboarding nào cả.
Để giải quyết vấn đề này, bạn có thể sử dụng phương thức `getOnboardingForDefaultAudience`, phương thức này lấy onboarding của placement đã chỉ định cho đối tượng **All Users**. Tuy nhiên, điều quan trọng cần hiểu là cách tiếp cận được khuyến nghị là lấy onboarding bằng phương thức `getOnboarding`, như được mô tả chi tiết trong phần [Lấy Onboarding](#fetch-onboarding) ở trên.
:::warning
Hãy cân nhắc sử dụng `getOnboarding` thay vì `getOnboardingForDefaultAudience`, vì phương thức sau có những hạn chế quan trọng:
- **Vấn đề tương thích**: Có thể gây ra sự cố khi hỗ trợ nhiều phiên bản ứng dụng, đòi hỏi thiết kế tương thích ngược hoặc chấp nhận rằng các phiên bản cũ hơn có thể hiển thị không đúng.
- **Không có cá nhân hóa**: Chỉ hiển thị nội dung cho đối tượng "All Users", loại bỏ việc nhắm mục tiêu dựa trên quốc gia, attribution, hoặc thuộc tính tùy chỉnh.
Nếu tốc độ lấy nhanh hơn vượt trội so với những hạn chế này cho trường hợp sử dụng của bạn, hãy dùng `getOnboardingForDefaultAudience` như bên dưới. Nếu không, hãy sử dụng `getOnboarding` như được mô tả [ở trên](#fetch-onboarding).
:::
```kotlin
Adapty.getOnboardingForDefaultAudience("YOUR_PLACEMENT_ID") { result ->
when (result) {
is AdaptyResult.Success -> {
val onboarding = result.value
// Handle successful onboarding retrieval
}
is AdaptyResult.Error -> {
val error = result.error
// Handle error case
}
}
}
```
Tham số:
| Tham số | Bắt buộc | Mô tả |
|---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **placementId** | bắt buộc | Mã định danh của [Placement](placements) mong muốn. Đây là giá trị bạn đã chỉ định khi tạo placement trong Adapty Dashboard. |
| **locale** | tùy chọn
mặc định: `en`
| Mã định danh của bản địa hóa onboarding. Tham số này được kỳ vọng là mã ngôn ngữ bao gồm một hoặc hai subtag được phân cách bằng dấu trừ (**-**). Subtag đầu tiên là ngôn ngữ, subtag thứ hai là vùng.
Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha (Brazil).
Xem [Localizations and locale codes](localizations-and-locale-codes) để biết thêm thông tin về mã locale và cách chúng tôi khuyến nghị sử dụng chúng.
|
| **fetchPolicy** | mặc định: `.reloadRevalidatingCacheData` | Theo mặc định, SDK sẽ cố tải dữ liệu từ server và trả về dữ liệu đã lưu cache trong trường hợp thất bại. Chúng tôi khuyến nghị tùy chọn này vì nó đảm bảo người dùng của bạn luôn nhận được dữ liệu mới nhất.
Tuy nhiên, nếu bạn cho rằng người dùng của mình có kết nối internet không ổn định, hãy xem xét sử dụng `.returnCacheDataElseLoad` để trả về dữ liệu cache nếu có. Trong trường hợp này, người dùng có thể không nhận được dữ liệu mới nhất tuyệt đối, nhưng họ sẽ có tốc độ tải nhanh hơn, bất kể kết nối internet của họ như thế nào. Cache được cập nhật thường xuyên, vì vậy việc sử dụng nó trong phiên để tránh các yêu cầu mạng là an toàn.
Lưu ý rằng cache vẫn còn nguyên sau khi khởi động lại ứng dụng và chỉ bị xóa khi ứng dụng được cài đặt lại hoặc thông qua việc dọn dẹp thủ công.
Adapty SDK lưu trữ các onboarding cục bộ theo hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và các onboarding dự phòng. Chúng tôi cũng sử dụng CDN để lấy onboarding nhanh hơn và một server dự phòng độc lập trong trường hợp CDN không thể truy cập. Hệ thống này được thiết kế để đảm bảo bạn luôn nhận được phiên bản mới nhất của onboarding trong khi vẫn đảm bảo độ tin cậy ngay cả trong trường hợp kết nối internet kém.
|
---
# File: android-present-onboardings
---
---
title: "Hiển thị onboarding trong Android SDK"
description: "Tìm hiểu cách hiển thị onboarding trên Android để tăng mức độ tương tác với người dùng."
---
Trước khi bắt đầu, hãy đảm bảo rằng:
1. Bạn đã cài đặt [Adapty Android SDK](sdk-installation-android) phiên bản 3.8.0 trở lên.
2. Bạn đã [tạo onboarding](create-onboarding).
3. Bạn đã thêm onboarding vào một [placement](placements).
Nếu bạn đã tùy chỉnh onboarding bằng Onboarding Builder, bạn không cần lo lắng về việc render nó trong code ứng dụng để hiển thị cho người dùng. Onboarding đó đã bao gồm cả nội dung hiển thị lẫn cách thức hiển thị.
Để hiển thị onboarding trực quan trên màn hình thiết bị, trước tiên bạn phải cấu hình nó. Để làm điều này, gọi phương thức `AdaptyUI.getOnboardingView()` hoặc tạo `OnboardingView` trực tiếp:
```kotlin
val onboardingView = AdaptyUI.getOnboardingView(
activity = this,
viewConfig = onboardingConfig,
eventListener = eventListener
)
```
```kotlin
val onboardingView = AdaptyOnboardingView(activity)
onboardingView.show(
viewConfig = onboardingConfig,
delegate = eventListener
)
```
```java
AdaptyOnboardingView onboardingView = AdaptyUI.getOnboardingView(
activity,
onboardingConfig,
eventListener
);
```
```java
AdaptyOnboardingView onboardingView = new AdaptyOnboardingView(activity);
onboardingView.show(onboardingConfig, eventListener);
```
```xml
```
Sau khi view được tạo thành công, bạn có thể thêm nó vào view hierarchy và hiển thị trên màn hình thiết bị.
Tham số yêu cầu:
| Tham số | Bắt buộc | Mô tả |
| :-------- | :------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **viewConfig** | bắt buộc | Cấu hình onboarding lấy từ `AdaptyUI.getOnboardingConfiguration()` |
| **eventListener** | bắt buộc | Một implementation của `AdaptyOnboardingEventListener` để xử lý các sự kiện onboarding. Xem [Xử lý sự kiện onboarding](android-handle-onboarding-events) để biết thêm chi tiết. |
## Thay đổi màu chỉ báo tải \{#change-loading-indicator-color\}
Bạn có thể ghi đè màu mặc định của chỉ báo tải theo cách sau:
```xml
```
## Thêm hiệu ứng chuyển màn hình mượt mà giữa splash screen và onboarding \{#add-smooth-transitions-between-the-splash-screen-and-onboarding\}
Theo mặc định, giữa splash screen và onboarding, bạn sẽ thấy màn hình loading cho đến khi onboarding được tải xong. Tuy nhiên, nếu bạn muốn tạo hiệu ứng chuyển màn hình mượt mà hơn, bạn có thể tùy chỉnh để kéo dài splash screen hoặc hiển thị nội dung khác.
Để làm điều này, hãy tạo file `adapty_onboarding_placeholder_view.xml` trong thư mục `res/layout` và định nghĩa placeholder (nội dung sẽ được hiển thị trong khi onboarding đang tải) ở đó.
Nếu bạn định nghĩa một placeholder, onboarding sẽ được tải ở nền và tự động hiển thị khi sẵn sàng.
## Tắt safe area padding \{#disable-safe-area-paddings\}
Theo mặc định, onboarding view tự động áp dụng safe area padding để tránh các thành phần UI hệ thống như status bar và navigation bar. Tuy nhiên, nếu bạn muốn tắt hành vi này và kiểm soát hoàn toàn bố cục, bạn có thể làm bằng cách đặt tham số `safeAreaPaddings` thành `false`.
```kotlin
val onboardingView = AdaptyUI.getOnboardingView(
activity = this,
viewConfig = onboardingConfig,
eventListener = eventListener,
safeAreaPaddings = false
)
```
```kotlin
val onboardingView = AdaptyOnboardingView(activity)
onboardingView.show(
viewConfig = onboardingConfig,
delegate = eventListener,
safeAreaPaddings = false
)
```
```java
AdaptyOnboardingView onboardingView = AdaptyUI.getOnboardingView(
activity,
onboardingConfig,
eventListener,
false
);
```
```java
AdaptyOnboardingView onboardingView = new AdaptyOnboardingView(activity);
onboardingView.show(onboardingConfig, eventListener, false);
```
Ngoài ra, bạn có thể kiểm soát hành vi này toàn cục bằng cách thêm một boolean resource vào ứng dụng:
```xml
false
```
Khi `safeAreaPaddings` được đặt thành `false`, onboarding sẽ mở rộng ra toàn màn hình mà không có padding tự động, giúp bạn kiểm soát hoàn toàn bố cục và cho phép nội dung onboarding sử dụng toàn bộ không gian màn hình.
## Tùy chỉnh cách mở liên kết trong onboarding \{#customize-how-links-open-in-onboardings\}
:::important
Tùy chỉnh cách mở liên kết trong onboarding được hỗ trợ từ Adapty SDK v3.15.1 trở đi.
:::
Theo mặc định, các liên kết trong onboarding mở trong trình duyệt in-app. Điều này mang lại trải nghiệm liền mạch cho người dùng bằng cách hiển thị trang web ngay trong ứng dụng, cho phép họ xem mà không cần chuyển sang ứng dụng khác.
Nếu bạn muốn mở liên kết trong trình duyệt ngoài thay thế, bạn có thể tùy chỉnh hành vi này bằng cách đặt tham số `externalUrlsPresentation` thành `AdaptyWebPresentation.ExternalBrowser`:
```kotlin
val onboardingConfig = AdaptyUI.getOnboardingConfiguration(
onboarding = onboarding,
externalUrlsPresentation = AdaptyWebPresentation.ExternalBrowser // default – InAppBrowser
)
```
```java
AdaptyOnboardingConfiguration onboardingConfig = AdaptyUI.getOnboardingConfiguration(
onboarding,
AdaptyWebPresentation.ExternalBrowser // default – InAppBrowser
);
```
---
# File: android-handle-onboarding-events
---
---
title: "Xử lý sự kiện onboarding trong Android SDK"
description: "Xử lý các sự kiện liên quan đến onboarding trong Android bằng Adapty."
---
Trước khi bắt đầu, hãy đảm bảo rằng:
1. Bạn đã cài đặt [Adapty Android SDK](sdk-installation-android) phiên bản 3.8.0 trở lên.
2. Bạn đã [tạo một onboarding](create-onboarding).
3. Bạn đã thêm onboarding vào một [placement](placements).
Các onboarding được cấu hình bằng builder sẽ tạo ra các sự kiện mà ứng dụng của bạn có thể phản hồi. Tìm hiểu cách xử lý các sự kiện này bên dưới.
Để kiểm soát hoặc theo dõi các tiến trình xảy ra trên màn hình onboarding trong ứng dụng Android của bạn, hãy implement interface `AdaptyOnboardingEventListener`.
## Hành động tùy chỉnh \{#custom-actions\}
Trong builder, bạn có thể thêm hành động **tùy chỉnh** cho một nút và gán cho nó một ID. Sau đó, bạn có thể dùng ID này trong code và xử lý nó như một hành động tùy chỉnh.
Ví dụ: nếu người dùng nhấn vào một nút tùy chỉnh như **Login** hoặc **Allow notifications**, phương thức delegate `onCustomAction` sẽ được kích hoạt với action ID từ builder. Bạn có thể tự tạo ID, chẳng hạn như "allowNotifications".
```kotlin showLineNumbers
class YourActivity : AppCompatActivity() {
private val eventListener = object : AdaptyOnboardingEventListener {
override fun onCustomAction(action: AdaptyOnboardingCustomAction, context: Context) {
when (action.actionId) {
"allowNotifications" -> {
// Request notification permissions
}
}
}
override fun onError(error: AdaptyOnboardingError, context: Context) {
// Handle errors
}
// ... other required delegate methods
}
}
```
Ví dụ sự kiện (Nhấn để mở rộng)
```json
{
"actionId": "allowNotifications",
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "profile_screen",
"screenIndex": 0,
"screensTotal": 3
}
}
```
## Đóng onboarding \{#closing-onboarding\}
Onboarding được coi là đã đóng khi người dùng nhấn vào nút được gán hành động **Close**. Bạn cần quản lý những gì xảy ra khi người dùng đóng onboarding. Ví dụ:
:::important
Bạn cần quản lý những gì xảy ra khi người dùng đóng onboarding. Chẳng hạn, bạn cần dừng hiển thị chính onboarding đó.
:::
Ví dụ:
```kotlin
override fun onCloseAction(action: AdaptyOnboardingCloseAction, context: Context) {
// Dismiss the onboarding screen
(context as? Activity)?.onBackPressed()
}
```
Ví dụ sự kiện (Nhấn để mở rộng)
```json
{
"action_id": "close_button",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "final_screen",
"screen_index": 3,
"total_screens": 4
}
}
```
## Mở một paywall \{#opening-a-paywall\}
:::tip
Xử lý sự kiện này để mở paywall nếu bạn muốn mở nó bên trong onboarding. Nếu bạn muốn mở paywall sau khi onboarding đã đóng, có một cách đơn giản hơn — xử lý [`AdaptyOnboardingCloseAction`](#closing-onboarding) và mở paywall mà không cần dựa vào dữ liệu sự kiện.
:::
Cách liền mạch nhất để làm việc với paywall trong onboarding là đặt action ID bằng với placement ID của paywall. Như vậy, sau sự kiện `AdaptyOnboardingOpenPaywallAction`, bạn có thể dùng placement ID để lấy và mở paywall ngay lập tức:
```kotlin
override fun onOpenPaywallAction(action: AdaptyOnboardingOpenPaywallAction, context: Context) {
// Get the paywall using the placement ID from the action
Adapty.getPaywall(placementId = action.actionId) { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
// Get the paywall configuration
AdaptyUI.getViewConfiguration(paywall) { result ->
when(result) {
is AdaptyResult.Success -> {
val paywallConfig = result.value
// Create and present the paywall
val paywallView = AdaptyUI.getPaywallView(
activity = this,
viewConfig = paywallConfig,
products,
eventListener = paywallEventListener
)
// Add the paywall view to your layout
binding.container.addView(paywallView)
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
}
```
Ví dụ sự kiện (Nhấn để mở rộng)
```json
{
"action_id": "premium_offer_1",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "pricing_screen",
"screen_index": 2,
"total_screens": 4
}
}
```
## Hoàn thành tải onboarding \{#finishing-loading-onboarding\}
Khi onboarding hoàn tất quá trình tải, phương thức này sẽ được gọi:
```kotlin
override fun onFinishLoading(action: AdaptyOnboardingLoadedAction, context: Context) {
// Handle loading completion
}
```
Ví dụ sự kiện (Nhấn để mở rộng)
```json
{
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "welcome_screen",
"screen_index": 0,
"total_screens": 4
}
}
```
## Sự kiện điều hướng \{#navigation-events\}
Phương thức `onAnalyticsEvent` được gọi khi các sự kiện analytics khác nhau xảy ra trong flow onboarding.
Đối tượng `event` có thể là một trong các loại sau:
|Loại | Mô tả |
|------------|-------------|
| `OnboardingStarted` | Khi onboarding đã được tải |
| `ScreenPresented` | Khi bất kỳ màn hình nào được hiển thị |
| `ScreenCompleted` | Khi một màn hình hoàn thành. Bao gồm `elementId` tùy chọn (định danh của phần tử đã hoàn thành) và `reply` tùy chọn (phản hồi từ người dùng). Được kích hoạt khi người dùng thực hiện bất kỳ hành động nào để thoát khỏi màn hình. |
| `SecondScreenPresented` | Khi màn hình thứ hai được hiển thị |
| `UserEmailCollected` | Được kích hoạt khi email của người dùng được thu thập qua trường nhập liệu |
| `OnboardingCompleted` | Được kích hoạt khi người dùng đến màn hình có ID `final`. Nếu bạn cần sự kiện này, hãy gán ID `final` cho màn hình cuối cùng. |
| `Unknown` | Cho bất kỳ loại sự kiện nào không được nhận dạng. Bao gồm `name` (tên của sự kiện không xác định) và `meta` (metadata bổ sung) |
Mỗi sự kiện bao gồm thông tin `meta` chứa:
| Trường | Mô tả |
|------------|-------------|
| `onboardingId` | Định danh duy nhất của flow onboarding |
| `screenClientId` | Định danh của màn hình hiện tại |
| `screenIndex` | Vị trí của màn hình hiện tại trong flow |
| `totalScreens` | Tổng số màn hình trong flow |
Đây là ví dụ về cách bạn có thể sử dụng các sự kiện analytics để theo dõi:
```kotlin
override fun onAnalyticsEvent(event: AdaptyOnboardingAnalyticsEvent, context: Context) {
when (event) {
is AdaptyOnboardingAnalyticsEvent.OnboardingStarted -> {
// Track onboarding start
trackEvent("onboarding_started", event.meta)
}
is AdaptyOnboardingAnalyticsEvent.ScreenPresented -> {
// Track screen presentation
trackEvent("screen_presented", event.meta)
}
is AdaptyOnboardingAnalyticsEvent.ScreenCompleted -> {
// Track screen completion with user response
trackEvent("screen_completed", event.meta, event.elementId, event.reply)
}
is AdaptyOnboardingAnalyticsEvent.OnboardingCompleted -> {
// Track successful onboarding completion
trackEvent("onboarding_completed", event.meta)
}
is AdaptyOnboardingAnalyticsEvent.Unknown -> {
// Handle unknown events
trackEvent(event.name, event.meta)
}
// Handle other cases as needed
}
}
```
Ví dụ các sự kiện (Nhấn để mở rộng)
```javascript
// OnboardingStarted
{
"name": "onboarding_started",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "welcome_screen",
"screen_index": 0,
"total_screens": 4
}
}
// ScreenPresented
{
"name": "screen_presented",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "interests_screen",
"screen_index": 2,
"total_screens": 4
}
}
// ScreenCompleted
{
"name": "screen_completed",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "profile_screen",
"screen_index": 1,
"total_screens": 4
},
"params": {
"element_id": "profile_form",
"reply": "success"
}
}
// SecondScreenPresented
{
"name": "second_screen_presented",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "profile_screen",
"screen_index": 1,
"total_screens": 4
}
}
// UserEmailCollected
{
"name": "user_email_collected",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "profile_screen",
"screen_index": 1,
"total_screens": 4
}
}
// OnboardingCompleted
{
"name": "onboarding_completed",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "final_screen",
"screen_index": 3,
"total_screens": 4
}
}
```
---
# File: android-onboarding-input
---
---
title: "Xử lý dữ liệu từ onboarding trong Android SDK"
description: "Lưu và sử dụng dữ liệu từ onboarding trong ứng dụng Android của bạn với Adapty SDK."
---
Khi người dùng trả lời câu hỏi quiz hoặc nhập dữ liệu vào ô nhập liệu, phương thức `onStateUpdatedAction` sẽ được gọi. Bạn có thể lưu hoặc xử lý loại trường đó trong code của mình.
Ví dụ:
```kotlin
override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) {
// Store user preferences or responses
when (val params = action.params) {
is AdaptyOnboardingStateUpdatedParams.Select -> {
// Handle single selection
}
is AdaptyOnboardingStateUpdatedParams.MultiSelect -> {
// Handle multiple selections
}
is AdaptyOnboardingStateUpdatedParams.Input -> {
// Handle text input
}
is AdaptyOnboardingStateUpdatedParams.DatePicker -> {
// Handle date selection
}
}
}
```
Xem định dạng action [tại đây](https://android.adapty.io/adapty-ui/com.adapty.ui.onboardings.actions/-adapty-onboarding-state-updated-action/).
Ví dụ về dữ liệu đã lưu (định dạng có thể khác trong triển khai của bạn)
```javascript
// Example of a saved select action
{
"elementId": "preference_selector",
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "preferences_screen",
"screenIndex": 1,
"screensTotal": 3
},
"params": {
"type": "select",
"value": {
"id": "option_1",
"value": "premium",
"label": "Premium Plan"
}
}
}
// Example of a saved multi-select action
{
"elementId": "interests_selector",
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "interests_screen",
"screenIndex": 2,
"screensTotal": 3
},
"params": {
"type": "multiSelect",
"value": [
{
"id": "interest_1",
"value": "sports",
"label": "Sports"
},
{
"id": "interest_2",
"value": "music",
"label": "Music"
}
]
}
}
// Example of a saved input action
{
"elementId": "name_input",
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "profile_screen",
"screenIndex": 0,
"screensTotal": 3
},
"params": {
"type": "input",
"value": {
"type": "text",
"value": "John Doe"
}
}
}
// Example of a saved date picker action
{
"elementId": "birthday_picker",
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "profile_screen",
"screenIndex": 0,
"screensTotal": 3
},
"params": {
"type": "datePicker",
"value": {
"day": 15,
"month": 6,
"year": 1990
}
}
}
```
## Trường hợp sử dụng \{#use-cases\}
### Bổ sung hồ sơ người dùng bằng dữ liệu \{#enrich-user-profiles-with-data\}
Nếu bạn muốn liên kết ngay dữ liệu đầu vào với hồ sơ người dùng và tránh hỏi họ hai lần cùng một thông tin, bạn cần [cập nhật hồ sơ người dùng](android-setting-user-attributes) với dữ liệu đầu vào khi xử lý action.
Ví dụ: bạn yêu cầu người dùng nhập tên vào trường văn bản có ID `name`, và bạn muốn đặt giá trị của trường này làm tên của người dùng. Ngoài ra, bạn yêu cầu họ nhập email vào trường `email`. Trong code ứng dụng, nó có thể trông như sau:
```kotlin showLineNumbers
override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) {
// Store user preferences or responses
when (val params = action.params) {
is AdaptyOnboardingStateUpdatedParams.Input -> {
// Handle text input
val builder = AdaptyProfileParameters.Builder()
// Map elementId to appropriate profile field
when (action.elementId) {
"name" -> {
when (val inputParams = params.params) {
is AdaptyOnboardingInputParams.Text -> {
builder.withFirstName(inputParams.value)
}
}
}
"email" -> {
when (val inputParams = params.params) {
is AdaptyOnboardingInputParams.Email -> {
builder.withEmail(inputParams.value)
}
}
}
}
Adapty.updateProfile(builder.build()) { error ->
if (error != null) {
// handle the error
}
}
}
}
}
```
### Tùy chỉnh paywall dựa trên câu trả lời \{#customize-paywalls-based-on-answers\}
Sử dụng quiz trong onboarding, bạn cũng có thể tùy chỉnh paywall hiển thị cho người dùng sau khi họ hoàn thành onboarding.
Ví dụ: bạn có thể hỏi người dùng về kinh nghiệm tập thể thao của họ và hiển thị các CTA và sản phẩm khác nhau cho từng nhóm người dùng.
1. [Thêm quiz](onboarding-quizzes) trong trình xây dựng onboarding và gán các ID có ý nghĩa cho các tùy chọn của nó.
2. Xử lý các phản hồi quiz dựa trên ID của chúng và [đặt thuộc tính tùy chỉnh](android-setting-user-attributes) cho người dùng.
```kotlin showLineNumbers
override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) {
// Handle quiz responses and set custom attributes
when (val params = action.params) {
is AdaptyOnboardingStateUpdatedParams.Select -> {
// Handle quiz selection
val builder = AdaptyProfileParameters.Builder()
// Map quiz responses to custom attributes
when (action.elementId) {
"experience" -> {
// Set custom attribute 'experience' with the selected value (beginner, amateur, pro)
builder.withCustomAttribute("experience", params.params.value)
}
}
Adapty.updateProfile(builder.build()) { error ->
if (error != null) {
// handle the error
}
}
}
}
}
```
3. [Tạo phân khúc](segments) cho từng giá trị thuộc tính tùy chỉnh.
4. Tạo một [placement](placements) và thêm [đối tượng](audience) cho từng phân khúc bạn đã tạo.
5. [Hiển thị paywall](android-paywalls) cho placement trong code ứng dụng của bạn. Nếu onboarding của bạn có nút mở paywall, hãy triển khai code paywall như một [phản hồi cho action của nút đó](android-handle-onboarding-events#opening-a-paywall).
---
# File: android-sdk-call-order
---
---
title: "Thứ tự gọi trong Android SDK"
description: "Tránh mất quyền truy cập premium, thiếu attribution, và lỗi ADAPTY_NOT_INITIALIZED gián đoạn bằng cách gọi các phương thức Adapty SDK theo đúng thứ tự."
---
`Adapty.activate()` phải hoàn thành trước khi bạn gọi bất kỳ phương thức nào khác của Adapty SDK. Cho đến khi hoàn tất, SDK chưa có trạng thái. Bất kỳ lệnh gọi nào được thực hiện trước hoặc song song với `activate()` đều thất bại với lỗi [`ADAPTY_NOT_INITIALIZED`](android-sdk-error-handling).
Nếu ứng dụng của bạn xác thực người dùng và bạn thu thập customer user ID sau khi khởi chạy, hãy gọi `Adapty.identify()` vào thời điểm đó. Không gọi các phương thức liên quan đến hành động người dùng cho đến khi callback hoàn thành của `identify` được kích hoạt. Các lệnh gọi chạy đua với nó sẽ trả về lỗi trong callback, hoặc sẽ tác động lên hồ sơ người dùng ẩn danh được tạo khi kích hoạt. Khi điều này xảy ra, attribution, MMP ID như `appsflyer_id`, và quyền sở hữu lượt cài đặt không phải lúc nào cũng được chuyển sang hồ sơ người dùng đã xác định. Nếu ứng dụng của bạn không xác thực người dùng, bỏ qua `identify` và tiếp tục làm việc với hồ sơ người dùng ẩn danh.
Các SDK MMP và analytics (AppsFlyer, Adjust, Branch, PostHog) tuân theo quy tắc tương tự. Khởi tạo chúng trước và chờ callback UID của chúng trước khi gọi `Adapty.activate`. Nếu không, MMP ID sẽ được gán vào một hồ sơ người dùng ẩn danh tồn tại trong thời gian ngắn và không phải lúc nào cũng được chuyển sang hồ sơ người dùng đã xác định. Để biết chi tiết về AppsFlyer, xem [AppsFlyer](appsflyer).
## Thứ tự đúng \{#the-correct-order\}
Con đường thực hiện phụ thuộc vào hai yếu tố: khi nào bạn biết customer user ID, và liệu bạn có sử dụng SDK MMP hoặc analytics hay không.
- **Bước 2 và 5**: Bắt buộc với mọi ứng dụng. Kích hoạt SDK, sau đó gọi các phương thức SDK.
- **Bước 1 và 3**: Chỉ cần thiết nếu bạn tích hợp SDK MMP hoặc analytics (AppsFlyer, Adjust, Branch, PostHog).
- **Bước 4**: Chỉ cần thiết nếu ứng dụng của bạn xác thực người dùng và thu thập customer user ID sau khi khởi chạy.
Nếu bạn đã có customer user ID khi khởi chạy ứng dụng, hãy truyền nó vào `AdaptyConfig.Builder` trước khi gọi `activate()` (bước 2a). Con đường này không bao giờ tạo hồ sơ người dùng ẩn danh, vì vậy bước 4 là không cần thiết.
| Bước | Lệnh gọi | Thời điểm | Ghi chú |
|------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------|
| 1 | Khởi tạo SDK MMP hoặc analytics của bạn (AppsFlyer, Adjust, PostHog, Branch) | Khởi chạy ứng dụng, đầu tiên | Chờ callback UID của MMP, ví dụ `getAppsFlyerUID`. |
| 2a | `Adapty.activate(context, AdaptyConfig.Builder("KEY").withCustomerUserId(...).build())` | Khởi chạy ứng dụng, sau bước 1, nếu bạn đã có customer user ID | Khuyến nghị. Không bao giờ tạo hồ sơ người dùng ẩn danh. |
| 2b | `Adapty.activate(context, AdaptyConfig.Builder("KEY").build())` không có `customerUserId` | Khởi chạy ứng dụng, sau bước 1, nếu bạn chưa có customer user ID (hoặc không bao giờ thu thập) | Adapty tạo một hồ sơ người dùng ẩn danh. |
| 3 | `Adapty.setIntegrationIdentifier("appsflyer_id", uid)` cho từng MMP | Sau bước 2, trước bất kỳ lệnh gọi hành động người dùng nào | Cần thiết để MMP ID được gán vào đúng hồ sơ người dùng. |
| 4 | `Adapty.identify("YOUR_USER_ID") { error -> ... }` | Sau bước 3 (hoặc bước 2 nếu không có MMP), trước bước 5 — chỉ trên con đường 2b có xác thực | Sử dụng completion callback. Các lệnh gọi đồng thời trong quá trình `identify` có thể tác động lên hồ sơ người dùng ẩn danh. |
| 5 | `getPaywall`, `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | Sau bước 4 nếu bạn gọi `identify`; nếu không thì sau bước 3 (hoặc bước 2 nếu không có MMP) | Các lệnh gọi này cần một hồ sơ người dùng ổn định. |
:::important
Bỏ qua các bước này sẽ dẫn đến mất quyền truy cập premium cho người dùng quay lại, thiếu `appsflyer_id` trên hồ sơ người dùng, và paywall được trả về cho sai đối tượng.
:::
## Cài đặt Web2app và web-funnel \{#web2app-and-web-funnel-installs\}
Nếu người dùng mua trên web checkout (Stripe, Paddle) và sau đó cài đặt ứng dụng native, lần `activate()` đầu tiên trên thiết bị sẽ tạo một hồ sơ người dùng ẩn danh mới. Hồ sơ người dùng này không được liên kết với hồ sơ người dùng web. Nếu bạn có thể xác định customer user ID trước khi khởi chạy ứng dụng (từ luồng xác thực hoặc install referrer), hãy truyền nó vào `AdaptyConfig.Builder` trực tiếp. Nếu không, giao dịch mua trên web sẽ không hiển thị trên thiết bị cho đến khi bạn gọi `identify("YOUR_USER_ID")` và sau đó là `restorePurchases`.
Để biết metadata cần gửi kèm với mỗi web checkout, xem:
- [Stripe](stripe)
- [Paddle](paddle)
---
# File: android-optimize-paywall-fetching
---
---
title: "Tối ưu hóa việc tải paywall trong Android SDK"
description: "Tải paywall Adapty đáng tin cậy: thời điểm, bộ nhớ đệm và các mẫu dự phòng cho Android."
---
Một lần tải paywall đáng tin cậy trên Android cần đảm bảo ba điều: hiển thị nhanh, trả về paywall đúng với đối tượng mục tiêu, và xử lý dự phòng linh hoạt khi mạng chậm. Các quy tắc dưới đây đề cập đến thời điểm, bộ nhớ đệm và các mẫu dự phòng để đạt được điều đó.
:::tip
Các quy tắc này giả định rằng `Adapty.activate()` và `Adapty.identify()` đã hoàn thành. Xem [Thứ tự gọi trong Android SDK](android-sdk-call-order).
:::
## Quy tắc và lưu ý \{#rules-and-pitfalls\}
| Nên làm | Không nên làm | Lý do |
|---|---|---|
| Tải placement mà bạn sắp hiển thị. | Tải trước tất cả các placement cùng lúc khi khởi động. | Tải trước hàng loạt sẽ chặn luồng chính và gây màn hình đen trong suốt thời gian đó. |
| Gọi `getPaywall` sau khi attribution đã có cơ hội xử lý xong — ví dụ, 1–2 giây sau `activate` hoặc sau khi `setOnProfileUpdatedListener` kích hoạt. | Gọi `getPaywall` trong `Application.onCreate()`. | Attribution chưa được xử lý xong. Paywall sẽ được phân giải theo đối tượng mặc định và bỏ qua phân khúc cũng như ASA personalization mà không có cảnh báo. |
| Đặt `loadTimeout` và cấu hình [paywall dự phòng](fallback-paywalls) cho mỗi placement. | Chờ `getPaywall` vô thời hạn. | Nếu không có timeout, người dùng ở kết nối kém sẽ thấy màn hình trắng cho đến khi mạng phục hồi — hoặc thoát ứng dụng. |
Xem [Tải paywalls và sản phẩm](fetch-paywalls-and-products-android) để tham khảo tham số `fetchPolicy` và `loadTimeout`, và [Placements](placements) để chọn đúng placement.
## Tối ưu cho kết nối kém \{#tune-for-poor-connectivity\}
Đối với các thị trường có kết nối kém thường xuyên (khu vực nông thôn, phương tiện giao thông công cộng, vùng bị ảnh hưởng bởi routing):
- Đặt `fetchPolicy` thành `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad` cho mọi lần tải, trừ lần đầu tiên.
- Cấu hình [paywall dự phòng](fallback-paywalls) cho mỗi placement trong Adapty Dashboard.
- Đặt `loadTimeout` từ 3–5 giây và chấp nhận paywall dự phòng khi timeout xảy ra.
- Không để việc hiển thị paywall phụ thuộc vào `getProfile`. Gọi `getPaywall` độc lập để hồ sơ người dùng chậm không làm trì hoãn giao diện.
---
# File: android-test
---
---
title: "Test & release in Android SDK"
description: "Tìm hiểu cách kiểm tra trạng thái gói đăng ký trong ứng dụng Android của bạn với Adapty."
---
Nếu bạn đã tích hợp Adapty SDK vào ứng dụng Android của mình, bạn sẽ muốn kiểm tra xem mọi thứ đã được thiết lập đúng cách và các giao dịch mua hoạt động như mong đợi. Quá trình này bao gồm kiểm tra cả tích hợp SDK lẫn luồng mua hàng thực tế với môi trường sandbox của Google Play.
## Kiểm thử ứng dụng của bạn \{#test-your-app\}
Để kiểm thử toàn diện các in-app purchase, bao gồm kiểm thử sandbox và xác thực closed track, hãy xem [hướng dẫn kiểm thử](testing-on-android) của chúng tôi.
## Chuẩn bị để phát hành \{#prepare-for-release\}
Trước khi gửi ứng dụng lên cửa hàng, hãy làm theo [Danh sách kiểm tra phát hành](release-checklist) để xác nhận:
- Kết nối cửa hàng và thông báo máy chủ đã được cấu hình
- Các giao dịch mua hoàn tất và được báo cáo cho Adapty
- Quyền truy cập được mở khóa và khôi phục đúng cách
- Các yêu cầu về quyền riêng tư và đánh giá đã được đáp ứng
---
# File: android-sdk-error-handling
---
---
title: "Xử lý lỗi trong Android SDK"
description: "Xử lý lỗi Android SDK hiệu quả với hướng dẫn khắc phục sự cố của Adapty."
---
Mọi lỗi được trả về bởi SDK đều có dạng `AdaptyError`.
:::tip
**Bật verbose logs trước khi debug.** Hầu hết các `AdaptyError` đều bọc một lỗi bên dưới từ Play Billing, mạng, hoặc backend. Khi bật verbose logs (`Adapty.logLevel = AdaptyLogLevel.VERBOSE` — xem [Logging](sdk-installation-android#logging)), lỗi được bọc đó sẽ được in ra console, thường cho biết nguyên nhân thực sự.
:::
:::important
Nếu các giải pháp này không khắc phục được vấn đề của bạn, hãy xem [Các vấn đề khác](#other-issues) để biết các bước cần thực hiện trước khi liên hệ hỗ trợ, giúp chúng tôi hỗ trợ bạn hiệu quả hơn.
:::
| Lỗi | Giải pháp |
|----------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| UNKNOWN | Lỗi này cho biết đã xảy ra một lỗi không xác định hoặc không mong đợi. |
| [ITEM_UNAVAILABLE](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_UNAVAILABLE()) | Lỗi này thường xảy ra trong giai đoạn kiểm thử. Nguyên nhân có thể là các sản phẩm chưa có trên môi trường production, hoặc người dùng không thuộc nhóm Testers trong Google Play. |
| ADAPTY_NOT_INITIALIZED | Adapty SDK chưa được kích hoạt.
Thường gặp khi màn hình splash hoặc một UI hook sớm gọi các phương thức Adapty trước khi `Adapty.activate` hoàn tất. Triệu chứng này không ổn định và có thể không tái hiện trên máy ảo vì thời gian thực thi trên thiết bị thật khác nhau. Hãy chờ `Adapty.activate` hoàn tất trước khi gọi bất kỳ SDK nào khác. Xem [Thứ tự gọi trong Android SDK](android-sdk-call-order) để biết trình tự đầy đủ. Bạn cũng cần [cấu hình Adapty SDK](sdk-installation-android#activate-adapty-module-of-adapty-sdk) đúng cách bằng phương thức `Adapty.activate`. |
| PROFILE_WAS_CHANGED | Hồ sơ người dùng đã thay đổi trong quá trình thực hiện thao tác.
Điều này xảy ra khi một phương thức được gọi trong khi `Adapty.identify` vẫn đang chạy — lệnh gọi đang xử lý đó rơi vào một hồ sơ sắp bị thay thế, và SDK từ chối nó. Hãy chờ `Adapty.identify` hoàn tất trước khi gọi các SDK khác. Xem [Thứ tự gọi trong Android SDK](android-sdk-call-order). |
| PRODUCT_NOT_FOUND | Lỗi này cho biết sản phẩm được yêu cầu mua không có sẵn trong cửa hàng. |
| INVALID_JSON | JSON của paywall dự phòng cục bộ không hợp lệ.
Hãy sửa paywall tiếng Anh mặc định trước, sau đó thay thế các paywall cục bộ không hợp lệ. Tham khảo chủ đề [Tùy chỉnh paywall bằng Remote Config](customize-paywall-with-remote-config) để biết cách sửa paywall, và [Định nghĩa paywall dự phòng cục bộ](fallback-paywalls) để biết cách thay thế các paywall cục bộ.
|
| CURRENT_SUBSCRIPTION_TO_UPDATE
\_NOT_FOUND_IN_HISTORY
| Không tìm thấy gói đăng ký gốc cần thay thế trong danh sách gói đăng ký đang hoạt động. |
| [BILLING_SERVICE_TIMEOUT](https://developer.android.com/google/play/billing/errors#service_timeout_error_code_-3) | Lỗi này cho biết yêu cầu đã vượt quá thời gian chờ tối đa trước khi Google Play có thể phản hồi. Nguyên nhân có thể là do thao tác được yêu cầu bởi lệnh gọi Play Billing Library bị trễ. |
| [FEATURE_NOT_SUPPORTED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#FEATURE_NOT_SUPPORTED()) | Tính năng được yêu cầu không được Play Store hỗ trợ trên thiết bị hiện tại. |
| [BILLING_SERVICE_DISCONNECTED](https://developer.android.com/google/play/billing/errors#service_disconnected_error_code_-1) | Lỗi này cho biết kết nối của ứng dụng khách đến dịch vụ Google Play Store thông qua `BillingClient` đã bị ngắt. |
| [BILLING_SERVICE_UNAVAILABLE](https://developer.android.com/google/play/billing/errors#service_unavailable_error_code_2) | Lỗi này cho biết dịch vụ Google Play Billing hiện không khả dụng. Trong hầu hết các trường hợp, điều này có nghĩa là có sự cố kết nối mạng ở đâu đó giữa thiết bị khách và các dịch vụ Google Play Billing. |
| [BILLING_UNAVAILABLE](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Lỗi này cho biết đã xảy ra sự cố thanh toán trong quá trình mua hàng. Các nguyên nhân có thể bao gồm:
1. Ứng dụng Play Store trên thiết bị của người dùng bị thiếu hoặc đã lỗi thời.
2. Người dùng ở quốc gia không được hỗ trợ.
3. Người dùng thuộc tài khoản doanh nghiệp mà quản trị viên đã tắt tính năng mua hàng.
4. Google Play không thể tính phí phương thức thanh toán của người dùng (ví dụ: thẻ tín dụng đã hết hạn).
5. Người dùng chưa đăng nhập vào ứng dụng Play Store.
|
| [DEVELOPER_ERROR](https://developer.android.com/google/play/billing/errors#developer_error) | Lỗi này cho biết bạn đang sử dụng API không đúng cách. |
| [BILLING_ERROR](https://developer.android.com/google/play/billing/errors#error_error_code_6) | Lỗi này cho biết có sự cố nội bộ với chính Google Play. |
| [ITEM_ALREADY_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_ALREADY_OWNED()) | Sản phẩm đã được mua trước đó. |
| [ITEM_NOT_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_NOT_OWNED()) | Lỗi này cho biết thao tác được yêu cầu trên mặt hàng thất bại vì người dùng chưa sở hữu nó. |
| [BILLING_NETWORK_ERROR](https://developer.android.com/google/play/billing/errors#network_error_error_code_12) | Lỗi này cho biết có sự cố với kết nối mạng giữa thiết bị và hệ thống của Play. |
| NO_PRODUCT_IDS_FOUND | Lỗi này cho biết không có sản phẩm nào trong paywall khả dụng trong cửa hàng.
Nếu bạn gặp lỗi này, hãy làm theo các bước dưới đây để khắc phục:
- Kiểm tra xem tất cả các sản phẩm đã được thêm vào Adapty Dashboard chưa.
- Đảm bảo rằng **Package name** của ứng dụng khớp với tên trong Google Play Console.
- Xác minh rằng các mã định danh sản phẩm từ cửa hàng ứng dụng khớp với những gì bạn đã thêm vào Dashboard. Lưu ý rằng các mã định danh không nên chứa Bundle ID, trừ khi nó đã được bao gồm trong cửa hàng.
- Xác nhận rằng trạng thái thanh toán của ứng dụng là **Active** trong cài đặt thuế Google của bạn. Đảm bảo thông tin thuế của bạn được cập nhật và chứng chỉ còn hiệu lực.
- Kiểm tra xem tài khoản ngân hàng đã được liên kết với ứng dụng chưa để ứng dụng đủ điều kiện kiếm tiền.
- Kiểm tra xem các sản phẩm có khả dụng ở khu vực của bạn không.
- Đảm bảo ứng dụng của bạn đang ở một trong các track kiểm thử. Track **Internal testing** là lựa chọn dễ nhất vì không yêu cầu xét duyệt và giữ ứng dụng ẩn với khách hàng.
|
| NO_PURCHASES_TO_RESTORE | Lỗi này cho biết Google Play không tìm thấy giao dịch mua để khôi phục. |
| AUTHENTICATION_ERROR | Bạn cần [cấu hình Adapty SDK](sdk-installation-android#activate-adapty-module-of-adapty-sdk) đúng cách bằng phương thức `Adapty.activate`. |
| BAD_REQUEST | Yêu cầu không hợp lệ.
Đảm bảo bạn đã hoàn thành tất cả các bước cần thiết để [tích hợp với Google Play](google-play-store-connection-configuration). |
| SERVER_ERROR | Lỗi máy chủ. |
| REQUEST_FAILED | Lỗi này cho biết có sự cố mạng không thể xác định cụ thể. |
| DECODING_FAILED | Chúng tôi không thể giải mã phản hồi.
Hãy xem lại code của bạn và đảm bảo rằng các tham số bạn gửi đi là hợp lệ. Ví dụ, lỗi này có thể cho biết bạn đang sử dụng API key không hợp lệ. |
| ANALYTICS_DISABLED | Chúng tôi không thể xử lý các sự kiện analytics vì bạn đã [tắt tính năng này](analytics-integration#disabling-external-analytics-for-a-specific-customer). |
| WRONG_PARAMETER | Lỗi này cho biết một số tham số của bạn không đúng: để trống khi không được phép để trống, hoặc sai kiểu dữ liệu, v.v. |
## Các vấn đề khác \{#other-issues\}
Nếu bạn vẫn chưa tìm được giải pháp, các bước tiếp theo có thể là:
- **Nâng cấp SDK lên phiên bản mới nhất**: Chúng tôi luôn khuyến nghị nâng cấp lên các phiên bản SDK mới nhất vì chúng ổn định hơn và bao gồm các bản sửa lỗi đã biết.
- **Liên hệ đội ngũ hỗ trợ hoặc nhờ trợ giúp từ các nhà phát triển khác** trong [diễn đàn hỗ trợ](https://adapty.featurebase.app/).
- **Liên hệ đội ngũ hỗ trợ qua [support@adapty.io](mailto:support@adapty.io) hoặc qua chat**: Nếu bạn chưa sẵn sàng nâng cấp SDK hoặc việc nâng cấp không giúp được, hãy liên hệ đội ngũ hỗ trợ của chúng tôi. Lưu ý rằng vấn đề của bạn sẽ được giải quyết nhanh hơn nếu bạn [bật verbose logging](sdk-installation-android#logging) và chia sẻ logs với đội ngũ. Bạn cũng có thể đính kèm các đoạn code liên quan.
---
# File: migration-to-android-312
---
---
title: "Migrate Adapty Android SDK sang v3.12"
description: "Migrate lên Adapty Android SDK v3.12 để có hiệu suất tốt hơn và các tính năng kiếm tiền mới."
---
Trong Adapty SDK 3.12.0, chúng tôi đã xóa phương thức `logShowOnboarding` khỏi SDK.
Nếu bạn đang sử dụng phương thức này, nó sẽ không còn khả dụng khi bạn nâng cấp SDK lên phiên bản 3.12 trở lên.
Thay vào đó, bạn có thể [tạo onboarding trong trình tạo onboarding no-code của Adapty](onboardings). Dữ liệu analytics cho các onboarding này được theo dõi tự động, và bạn có nhiều tùy chọn tùy chỉnh.
---
# File: migration-to-android-310
---
---
title: "Hướng dẫn migration lên Android Adapty SDK 3.10.0"
description: ""
---
Adapty SDK 3.10.0 là một bản phát hành lớn mang lại một số cải tiến, tuy nhiên có thể yêu cầu bạn thực hiện một số bước migration:
1. `AdaptyUiPersonalizedOfferResolver` đã bị loại bỏ. Nếu bạn đang sử dụng nó, hãy truyền vào callback `onAwaitingPurchaseParams`.
2. Cập nhật chữ ký phương thức `onAwaitingSubscriptionUpdateParams` cho các paywall dùng Paywall Builder.
## Cập nhật callback tham số mua hàng \{#update-purchase-parameters-callback\}
Phương thức `onAwaitingSubscriptionUpdateParams` đã được đổi tên thành `onAwaitingPurchaseParams` và hiện sử dụng `AdaptyPurchaseParameters` thay vì `AdaptySubscriptionUpdateParameters`. Điều này cho phép bạn chỉ định các tham số thay thế gói đăng ký (crossgrade) và xác định liệu giá có được cá nhân hóa hay không ([đọc thêm](https://developer.android.com/google/play/billing/integrate#personalized-price)), cùng với các tham số mua hàng khác.
```diff showLineNumbers
- override fun onAwaitingSubscriptionUpdateParams(
- product: AdaptyPaywallProduct,
- context: Context,
- onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback,
- ) {
- onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...))
- }
+ override fun onAwaitingPurchaseParams(
+ product: AdaptyPaywallProduct,
+ context: Context,
+ onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback,
+ ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked {
+ onPurchaseParamsReceived(
+ AdaptyPurchaseParameters.Builder()
+ .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...))
+ .withOfferPersonalized(true)
+ .build()
+ )
+ return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked
+ }
```
Nếu không cần thêm tham số nào, bạn có thể dùng đơn giản như sau:
```kotlin showLineNumbers
+ override fun onAwaitingPurchaseParams(
product: AdaptyPaywallProduct,
context: Context,
onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback,
): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked {
onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty)
return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
---
# File: migration-to-android-sdk-34
---
---
title: "Migrate Adapty Android SDK to v3.4"
description: "Migrate sang Adapty Android SDK v3.4 để cải thiện hiệu suất và các tính năng kiếm tiền mới."
---
Adapty SDK 3.4.0 là một bản phát hành lớn, giới thiệu các cải tiến yêu cầu bạn thực hiện các bước migration.
## Cập nhật file paywall dự phòng \{#update-fallback-paywall-files\}
Cập nhật các file paywall dự phòng để đảm bảo tương thích với phiên bản SDK mới:
1. [Tải xuống các file paywall dự phòng đã cập nhật](fallback-paywalls) từ Adapty Dashboard.
2. [Thay thế các paywall dự phòng hiện có trong ứng dụng di động của bạn](android-use-fallback-paywalls) bằng các file mới.
## Cập nhật cài đặt Observer Mode \{#update-implementation-of-observer-mode\}
Nếu bạn đang sử dụng Observer Mode, hãy đảm bảo cập nhật cách cài đặt của nó.
Trong các phiên bản trước, bạn phải khôi phục giao dịch mua để Adapty có thể nhận diện các giao dịch được thực hiện qua cơ sở hạ tầng riêng của bạn, vì Adapty không có quyền truy cập trực tiếp vào chúng trong Observer Mode. Nếu bạn sử dụng paywall, bạn cũng cần liên kết thủ công từng giao dịch với paywall đã khởi tạo nó.
Trong phiên bản mới, bạn phải báo cáo rõ ràng từng giao dịch để Adapty nhận diện. Nếu bạn sử dụng paywall, bạn cũng cần truyền variation ID để liên kết giao dịch với paywall đã được sử dụng.
:::warning
**Đừng bỏ qua bước báo cáo giao dịch!**
Nếu bạn không gọi `reportTransaction`, Adapty sẽ không nhận diện được giao dịch, giao dịch sẽ không hiển thị trong analytics và sẽ không được gửi đến các tích hợp.
:::
```diff showLineNumbers
- Adapty.restorePurchases { result ->
- if (result is AdaptyResult.Success) {
- // success
- }
- }
-
- Adapty.setVariationId(transactionId, variationId) { error ->
- if (error == null) {
- // success
- }
- }
+ val transactionInfo = TransactionInfo.fromPurchase(purchase)
+
+ Adapty.reportTransaction(transactionInfo, variationId) { result ->
+ if (result is AdaptyResult.Success) {
+ // success
+ }
+ }
```
```diff showLineNumbers
- Adapty.restorePurchases(result -> {
- if (result instanceof AdaptyResult.Success) {
- // success
- }
- });
-
- Adapty.setVariationId(transactionId, variationId, error -> {
- if (error == null) {
- // success
- }
- });
+ TransactionInfo transactionInfo = TransactionInfo.fromPurchase(purchase);
+
+ Adapty.reportTransaction(transactionInfo, variationId, result -> {
+ if (result instanceof AdaptyResult.Success) {
+ // success
+ }
+ });
```
---
# File: migration-to-android330
---
---
title: "Migrate Adapty Android SDK sang v3.3"
description: "Migrate sang Adapty Android SDK v3.3 để cải thiện hiệu suất và có thêm các tính năng monetization mới."
---
Adapty SDK 3.3.0 là một bản phát hành lớn mang đến một số cải tiến, tuy nhiên có thể yêu cầu bạn thực hiện một số bước migration.
1. Cập nhật cách xử lý thanh toán trong các paywall không được tạo bằng Paywall Builder. Dừng xử lý các mã lỗi `USER_CANCELED` và `PENDING_PURCHASE`. Giao dịch bị hủy không còn được coi là lỗi nữa và sẽ xuất hiện trong kết quả mua hàng thành công (non-error).
2. Thay thế các sự kiện `onPurchaseCanceled` và `onPurchaseSuccess` bằng sự kiện `onPurchaseFinished` mới cho các paywall được tạo bằng Paywall Builder. Thay đổi này vì cùng lý do: giao dịch bị hủy không còn bị coi là lỗi và sẽ được đưa vào kết quả mua hàng thành công.
3. Thay đổi chữ ký phương thức `onAwaitingSubscriptionUpdateParams` cho các paywall của Paywall Builder.
4. Cập nhật phương thức dùng để cung cấp paywall dự phòng nếu bạn truyền URI file trực tiếp.
5. Cập nhật cấu hình tích hợp cho Adjust, AirBridge, Amplitude, AppMetrica, Appsflyer, Branch, Facebook Ads, Firebase và Google Analytics, Mixpanel, OneSignal, Pushwoosh.
## Cập nhật thực hiện mua hàng \{#update-making-purchase\}
Trước đây, giao dịch bị hủy và đang chờ xử lý được coi là lỗi và trả về mã `USER_CANCELED` và `PENDING_PURCHASE` tương ứng.
Giờ đây, một lớp `AdaptyPurchaseResult` mới được sử dụng để biểu thị các trạng thái: đã hủy, thành công và đang chờ xử lý. Cập nhật code xử lý mua hàng như sau:
~~~diff
Adapty.makePurchase(activity, product) { result ->
when (result) {
is AdaptyResult.Success -> {
- val info = result.value
- val profile = info?.profile
-
- if (profile?.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true) {
- // Grant access to the paid features
- }
+ when (val purchaseResult = result.value) {
+ is AdaptyPurchaseResult.Success -> {
+ val profile = purchaseResult.profile
+ if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) {
+ // Grant access to the paid features
+ }
+ }
+
+ is AdaptyPurchaseResult.UserCanceled -> {
+ // Handle the case where the user canceled the purchase
+ }
+
+ is AdaptyPurchaseResult.Pending -> {
+ // Handle deferred purchases (e.g., the user will pay offline with cash
+ }
+ }
}
is AdaptyResult.Error -> {
val error = result.error
// Handle the error
}
}
}
~~~
Để xem ví dụ code đầy đủ, hãy xem trang [Thực hiện mua hàng trong ứng dụng mobile](android-making-purchases#make-purchase).
## Chỉnh sửa sự kiện mua hàng trong Paywall Builder \{#modify-paywall-builder-purchase-events\}
1. Thêm sự kiện `onPurchaseFinished`:
```diff showLineNumbers
+ public override fun onPurchaseFinished(
+ purchaseResult: AdaptyPurchaseResult,
+ product: AdaptyPaywallProduct,
+ context: Context,
+ ) {
+ when (purchaseResult) {
+ is AdaptyPurchaseResult.Success -> {
+ // Grant access to the paid features
+ }
+ is AdaptyPurchaseResult.UserCanceled -> {
+ // Handle the case where the user canceled the purchase
+ }
+ is AdaptyPurchaseResult.Pending -> {
+ // Handle deferred purchases (e.g., the user will pay offline with cash)
+ }
+ }
+ }
```
Để xem ví dụ code đầy đủ, hãy xem phần [Mua hàng thành công, bị hủy hoặc đang chờ xử lý](android-handling-events#successful-canceled-or-pending-purchase) và mô tả sự kiện.
2. Xóa phần xử lý sự kiện `onPurchaseCancelled`:
```diff showLineNumbers
- public override fun onPurchaseCanceled(
- product: AdaptyPaywallProduct,
- context: Context,
- ) {}
```
3. Xóa `onPurchaseSuccess`:
```diff showLineNumbers
- public override fun onPurchaseSuccess(
- profile: AdaptyProfile?,
- product: AdaptyPaywallProduct,
- context: Context,
- ) {
- // Your logic on successful purchase
- }
```
## Thay đổi chữ ký của phương thức onAwaitingSubscriptionUpdateParams \{#change-the-signature-of--onawaitingsubscriptionupdateparams-method\}
Bây giờ, nếu một gói đăng ký mới được mua trong khi gói đăng ký khác vẫn còn hoạt động, hãy gọi `onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters...))` nếu gói đăng ký mới sẽ thay thế gói đang hoạt động, hoặc `onSubscriptionUpdateParamsReceived(null)` nếu gói đang hoạt động vẫn tiếp tục và gói mới sẽ được thêm riêng biệt:
```diff showLineNumbers
- public override fun onAwaitingSubscriptionUpdateParams(
- product: AdaptyPaywallProduct,
- context: Context,
- ): AdaptySubscriptionUpdateParameters? {
- return AdaptySubscriptionUpdateParameters(...)
- }
+ public override fun onAwaitingSubscriptionUpdateParams(
+ product: AdaptyPaywallProduct,
+ context: Context,
+ onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback,
+ ) {
+ onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...))
+ }
```
Xem phần tài liệu [Nâng cấp gói đăng ký](android-handling-events#upgrade-subscription) để xem ví dụ code hoàn chỉnh.
## Cập nhật cung cấp paywall dự phòng \{#update-providing-fallback-paywalls\}
Nếu bạn truyền URI file để cung cấp paywall dự phòng, hãy cập nhật cách thực hiện như sau:
```diff showLineNumbers
val fileUri: Uri = // Get the URI for the file with fallback paywalls
- Adapty.setFallbackPaywalls(fileUri, callback)
+ Adapty.setFallbackPaywalls(FileLocation.fromFileUri(fileUri), callback)
```
```diff showLineNumbers
Uri fileUri = // Get the URI for the file with fallback paywalls
- Adapty.setFallbackPaywalls(fileUri, callback);
+ Adapty.setFallbackPaywalls(FileLocation.fromFileUri(fileUri), callback);
```
## Cập nhật cấu hình SDK tích hợp bên thứ ba \{#update-third-party-integration-sdk-configuration\}
Để đảm bảo các tích hợp hoạt động đúng với Adapty Android SDK 3.3.0 trở lên, hãy cập nhật cấu hình SDK cho các tích hợp sau theo hướng dẫn trong các phần bên dưới.
### Adjust \{#adjust\}
Cập nhật code ứng dụng mobile của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp Adjust](adjust#connect-your-app-to-adjust).
```diff showLineNumbers
- Adjust.getAttribution { attribution ->
- if (attribution == null) return@getAttribution
-
- Adjust.getAdid { adid ->
- if (adid == null) return@getAdid
-
- Adapty.updateAttribution(attribution, AdaptyAttributionSource.ADJUST, adid) { error ->
- // Handle the error
- }
- }
- }
+ Adjust.getAdid { adid ->
+ if (adid == null) return@getAdid
+
+ Adapty.setIntegrationIdentifier("adjust_device_id", adid) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
+ }
+
+ Adjust.getAttribution { attribution ->
+ if (attribution == null) return@getAttribution
+
+ Adapty.updateAttribution(attribution, "adjust") { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
+ }
```
```diff showLineNumbers
val config = AdjustConfig(context, adjustAppToken, environment)
config.setOnAttributionChangedListener { attribution ->
attribution?.let { attribution ->
- Adapty.updateAttribution(attribution, AdaptyAttributionSource.ADJUST) { error ->
+ Adapty.updateAttribution(attribution, "adjust") { error ->
if (error != null) {
// Handle the error
}
}
}
}
Adjust.onCreate(config)
```
### AirBridge \{#airbridge\}
Cập nhật code ứng dụng mobile của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp AirBridge](airbridge#connect-your-app-to-airbridge).
```diff showLineNumbers
Airbridge.getDeviceInfo().getUUID(object: AirbridgeCallback.SimpleCallback() {
override fun onSuccess(result: String) {
- val params = AdaptyProfileParameters.Builder()
- .withAirbridgeDeviceId(result)
- .build()
- Adapty.updateProfile(params) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ Adapty.setIntegrationIdentifier("airbridge_device_id", result) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
}
override fun onFailure(throwable: Throwable) {
}
})
```
### Amplitude \{#amplitude\}
Cập nhật code ứng dụng mobile của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp Amplitude](amplitude#sdk-configuration).
```diff showLineNumbers
// For Amplitude maintenance SDK (obsolete)
val amplitude = Amplitude.getInstance()
val amplitudeDeviceId = amplitude.getDeviceId()
val amplitudeUserId = amplitude.getUserId()
//for actual Amplitude Kotlin SDK
val amplitude = Amplitude(
Configuration(
apiKey = AMPLITUDE_API_KEY,
context = applicationContext
)
)
val amplitudeDeviceId = amplitude.store.deviceId
val amplitudeUserId = amplitude.store.userId
//
- val params = AdaptyProfileParameters.Builder()
- .withAmplitudeDeviceId(amplitudeDeviceId)
- .withAmplitudeUserId(amplitudeUserId)
- .build()
- Adapty.updateProfile(params) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ Adapty.setIntegrationIdentifier("amplitude_user_id", amplitudeUserId) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
+ Adapty.setIntegrationIdentifier("amplitude_device_id", amplitudeDeviceId) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
```
### AppMetrica \{#appmetrica\}
Cập nhật code ứng dụng mobile của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp AppMetrica](appmetrica#sdk-configuration).
```diff showLineNumbers
val startupParamsCallback = object: StartupParamsCallback {
override fun onReceive(result: StartupParamsCallback.Result?) {
val deviceId = result?.deviceId ?: return
- val params = AdaptyProfileParameters.Builder()
- .withAppmetricaDeviceId(deviceId)
- .withAppmetricaProfileId("YOUR_ADAPTY_CUSTOMER_USER_ID")
- .build()
- Adapty.updateProfile(params) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ Adapty.setIntegrationIdentifier("appmetrica_device_id", deviceId) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
+
+ Adapty.setIntegrationIdentifier("appmetrica_profile_id", "YOUR_ADAPTY_CUSTOMER_USER_ID") { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
}
override fun onRequestError(
reason: StartupParamsCallback.Reason,
result: StartupParamsCallback.Result?
) {
// Handle the error
}
}
AppMetrica.requestStartupParams(context, startupParamsCallback, listOf(StartupParamsCallback.APPMETRICA_DEVICE_ID))
```
### AppsFlyer \{#appsflyer\}
Cập nhật code ứng dụng mobile của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp AppsFlyer](appsflyer#connect-your-app-to-appsflyer).
```diff showLineNumbers
val conversionListener: AppsFlyerConversionListener = object : AppsFlyerConversionListener {
override fun onConversionDataSuccess(conversionData: Map) {
- Adapty.updateAttribution(
- conversionData,
- AdaptyAttributionSource.APPSFLYER,
- AppsFlyerLib.getInstance().getAppsFlyerUID(context)
- ) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ val uid = AppsFlyerLib.getInstance().getAppsFlyerUID(context)
+ Adapty.setIntegrationIdentifier("appsflyer_id", uid) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
+ Adapty.updateAttribution(conversionData, "appsflyer") { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
}
}
```
### Branch \{#branch\}
Cập nhật code ứng dụng mobile của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp Branch](branch#connect-your-app-to-branch).
```diff showLineNumbers
// Login and update attribution
Branch.getAutoInstance(this)
.setIdentity("YOUR_USER_ID") { referringParams, error ->
referringParams?.let { data ->
- Adapty.updateAttribution(data, AdaptyAttributionSource.BRANCH) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ Adapty.updateAttribution(data, "branch") { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
}
}
// Logout
Branch.getAutoInstance(context).logout()
```
### Facebook Ads \{#facebook-ads\}
Cập nhật code ứng dụng mobile của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp Facebook Ads](facebook-ads#connect-your-app-to-facebook-ads).
```diff showLineNumbers
- val builder = AdaptyProfileParameters.Builder()
- .withFacebookAnonymousId(AppEventsLogger.getAnonymousAppDeviceGUID(context))
-
- Adapty.updateProfile(builder.build()) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ Adapty.setIntegrationIdentifier(
+ "facebook_anonymous_id",
+ AppEventsLogger.getAnonymousAppDeviceGUID(context)
+ ) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
```
### Firebase và Google Analytics \{#firebase-and-google-analytics\}
Cập nhật code ứng dụng mobile của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp Firebase và Google Analytics](firebase-and-google-analytics).
```diff showLineNumbers
// After Adapty.activate()
FirebaseAnalytics.getInstance(context).appInstanceId.addOnSuccessListener { appInstanceId ->
- Adapty.updateProfile(
- AdaptyProfileParameters.Builder()
- .withFirebaseAppInstanceId(appInstanceId)
- .build()
- ) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
}
```
```diff showLineNumbers
// After Adapty.activate()
- FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> {
- AdaptyProfileParameters params = new AdaptyProfileParameters.Builder()
- .withFirebaseAppInstanceId(appInstanceId)
- .build();
-
- Adapty.updateProfile(params, error -> {
- if (error != null) {
- // Handle the error
- }
- });
- });
+ FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> {
+ Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId, error -> {
+ if (error != null) {
+ // Handle the error
+ }
+ });
+ });
```
### Mixpanel \{#mixpanel\}
Cập nhật code ứng dụng mobile của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp Mixpanel](mixpanel#sdk-configuration).
```diff showLineNumbers
- val params = AdaptyProfileParameters.Builder()
- .withMixpanelUserId(mixpanelAPI.distinctId)
- .build()
-
- Adapty.updateProfile(params) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ Adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelAPI.distinctId) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
```
### OneSignal \{#onesignal\}
Cập nhật code ứng dụng mobile của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp OneSignal](onesignal#sdk-configuration).
```diff showLineNumbers
// SubscriptionID
val oneSignalSubscriptionObserver = object: IPushSubscriptionObserver {
override fun onPushSubscriptionChange(state: PushSubscriptionChangedState) {
- val params = AdaptyProfileParameters.Builder()
- .withOneSignalSubscriptionId(state.current.id)
- .build()
-
- Adapty.updateProfile(params) { error ->
+ Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.current.id) { error ->
if (error != null) {
// Handle the error
}
}
}
}
```
```diff showLineNumbers
// SubscriptionID
IPushSubscriptionObserver oneSignalSubscriptionObserver = state -> {
- AdaptyProfileParameters params = new AdaptyProfileParameters.Builder()
- .withOneSignalSubscriptionId(state.getCurrent().getId())
- .build();
- Adapty.updateProfile(params, error -> {
+ Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.getCurrent().getId(), error -> {
if (error != null) {
// Handle the error
}
});
};
```
```diff showLineNumbers
// PlayerID
val osSubscriptionObserver = OSSubscriptionObserver { stateChanges ->
stateChanges?.to?.userId?.let { playerId ->
- val params = AdaptyProfileParameters.Builder()
- .withOneSignalPlayerId(playerId)
- .build()
-
- Adapty.updateProfile(params) { error ->
+ Adapty.setIntegrationIdentifier("one_signal_player_id", playerId) { error ->
if (error != null) {
// Handle the error
}
- }
}
}
```
```diff showLineNumbers
// PlayerID
OSSubscriptionObserver osSubscriptionObserver = stateChanges -> {
OSSubscriptionState to = stateChanges != null ? stateChanges.getTo() : null;
String playerId = to != null ? to.getUserId() : null;
if (playerId != null) {
- AdaptyProfileParameters params1 = new AdaptyProfileParameters.Builder()
- .withOneSignalPlayerId(playerId)
- .build();
-
- Adapty.updateProfile(params1, error -> {
+ Adapty.setIntegrationIdentifier("one_signal_player_id", playerId, error -> {
if (error != null) {
// Handle the error
}
- });
}
};
```
### Pushwoosh \{#pushwoosh\}
Cập nhật code ứng dụng mobile của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp Pushwoosh](pushwoosh#sdk-configuration).
```diff showLineNumbers
- val params = AdaptyProfileParameters.Builder()
- .withPushwooshHwid(Pushwoosh.getInstance().hwid)
- .build()
- Adapty.updateProfile(params) { error ->
+ Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().hwid) { error ->
if (error != null) {
// Handle the error
}
}
```
```diff showLineNumbers
- AdaptyProfileParameters params = new AdaptyProfileParameters.Builder()
- .withPushwooshHwid(Pushwoosh.getInstance().getHwid())
- .build();
-
- Adapty.updateProfile(params, error -> {
+ Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().getHwid(), error -> {
if (error != null) {
// Handle the error
}
});
```
---
# File: migration-to-android-sdk-v3
---
---
title: "Migrate Adapty Android SDK to v3.0"
description: "Migrate to Adapty Android SDK v3.0 for better performance and new monetization features."
---
Adapty SDK v3.0 hỗ trợ [Adapty Paywall Builder](adapty-paywall-builder) mới — công cụ no-code thân thiện để tạo paywall. Với sự linh hoạt tối đa và khả năng thiết kế phong phú, các paywall của bạn sẽ trở nên hiệu quả và mang lại doanh thu tốt hơn.
Adapty SDK được phân phối dưới dạng BoM (Bill of Materials), đảm bảo phiên bản Adapty SDK và AdaptyUI SDK trong ứng dụng của bạn luôn nhất quán với nhau.
Để migrate lên v3.0, hãy cập nhật code như sau:
```diff showLineNumbers
dependencies {
...
- implementation 'io.adapty:android-sdk:2.11.5'
- implementation 'io.adapty:android-ui:2.11.3'
+ implementation platform('io.adapty:adapty-bom:3.0.4')
+ implementation 'io.adapty:android-sdk'
+ implementation 'io.adapty:android-ui'
}
```
```diff showLineNumbers
dependencies {
...
- implementation("io.adapty:android-sdk:2.11.5")
- implementation("io.adapty:android-ui:2.11.3")
+ implementation(platform("io.adapty:adapty-bom:3.0.4"))
+ implementation("io.adapty:android-sdk")
+ implementation("io.adapty:android-ui")
}
```
```diff showLineNumbers
//libs.versions.toml
[versions]
..
- adapty = "2.11.5"
- adaptyUi = "2.11.3"
+ adaptyBom = "3.0.4"
[libraries]
..
- adapty = { group = "io.adapty", name = "android-sdk", version.ref = "adapty" }
- adapty-ui = { group = "io.adapty", name = "android-ui", version.ref = "adaptyUi" }
+ adapty-bom = { module = "io.adapty:adapty-bom", version.ref = "adaptyBom" }
+ adapty = { module = "io.adapty:android-sdk" }
+ adapty-ui = { module = "io.adapty:android-ui" }
//module-level build.gradle.kts
dependencies {
...
+ implementation(libs.adapty.bom)
implementation(libs.adapty)
implementation(libs.adapty.ui)
}
```
---
# End of Documentation
_Generated on: 2026-06-24T14:36:38.639Z_
_Successfully processed: 40/40 files_