Xử lý dữ liệu từ flow trong Kotlin Multiplatform SDK
Khi người dùng nhập vào trường văn bản, trả lời câu hỏi, hoặc bật/tắt toggle trong một flow, SDK sẽ truyền giá trị đó đến ứng dụng của bạn thông qua callback analytics.
Ứng dụng thường sử dụng dữ liệu đó để:
- Đăng ký người dùng trên backend của bạn: Lấy địa chỉ email và tên người dùng đã nhập trong flow onboarding, rồi tạo tài khoản cho họ khi flow kết thúc.
- Lưu câu trả lời và sở thích: Theo dõi những gì người dùng đã chọn để ứng dụng có thể sử dụng sau — ví dụ, ghi vào hồ sơ người dùng Adapty của họ dưới dạng custom attributes.
- Tùy chỉnh các flow sau: Lưu câu trả lời khảo sát dưới dạng custom attributes, sau đó nhắm mục tiêu vào một placement sau để mỗi phân khúc nhận được một flow khác nhau, hoặc một paywall khác nhau bên trong đó.
- Chuyển dữ liệu tới các nền tảng analytics bên thứ ba: Chuyển tiếp câu trả lời tới Amplitude, Mixpanel, hoặc bất kỳ nền tảng product analytics nào bạn đang dùng.
Các input và selectable group tự động báo cáo giá trị của chúng. Để phân biệt các input trong code, hãy đặt cho mỗi input một Element ID có ý nghĩa và mỗi selectable group một Group ID trong builder.
Trước khi bắt đầu
Bạn cần:
- Adapty SDK v4 trở lên: Các flow callback không tồn tại trong các phiên bản cũ hơn.
- Một flow được xây dựng trong Flow & Paywall Builder: Chỉ có flow mới báo cáo các giá trị đầu vào thông qua callback này.
- Một phiên bản flow đã được xuất bản gần đây: Flow chỉ báo cáo các giá trị đầu vào nếu bạn xuất bản nó sau khi tính năng này có sẵn. Nếu không có gì đến được ứng dụng của bạn, hãy xuất bản một phiên bản mới của flow và thử lại.
Nhận giá trị đầu vào
Giá trị đầu vào được gửi đến cùng callback với mọi sự kiện analytics khác từ một flow, dưới tên sự kiện flow_user_input. Ghi đè flowViewDidReceiveAnalyticEvent trên observer mà bạn đã đăng ký với AdaptyUI.setFlowsEventsObserver:
AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver {
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String,
) {
handleFlowInput(name, paramsJsonString)
}
})
Callback flowViewDidReceiveAnalyticEvent truyền tất cả các sự kiện analytics từ một flow, bao gồm cả lượt xem màn hình. Các tham số sự kiện được truyền vào dưới dạng một chuỗi JSON trong paramsJsonString; hãy giải mã một lần và đọc các trường.
- Tham số
namechứa tên sự kiện. Để lọc các sự kiện nhập liệu của người dùng, so sánhnamevớiflow_user_input. - Tham số
element_typecho biết danh mục của phần tử. - Giá trị của dữ liệu nhập được lưu ở các tham số khác nhau tùy thuộc vào loại phần tử:
- Trường văn bản, picker và toggle lưu dữ liệu nhập của người dùng trong
value - Nhóm có thể chọn (selectable groups) báo cáo các tùy chọn đang hoạt động trong
item_idsvàitem_titles
- Trường văn bản, picker và toggle lưu dữ liệu nhập của người dùng trong
private fun handleFlowInput(name: String, paramsJsonString: String) {
if (name != "flow_user_input") return
val params = Json.parseToJsonElement(paramsJsonString).jsonObject
val elementId = params["element_id"]?.jsonPrimitive?.content ?: return
// The screen the input sits on. Pair it with elementId to tell apart
// two fields that share an Element ID on different screens.
val screenId = params["instanceId"]?.jsonPrimitive?.contentOrNull
when (params["element_type"]?.jsonPrimitive?.content) {
"text_input", "email_input", "number_input", "phone_input" -> {
val text = params["value"]?.jsonPrimitive?.content
}
"date_picker", "time_picker", "date_time_picker" -> {
// Unix time in milliseconds. Read it as a double — Android serializes numbers that way.
val millis = params["value"]?.jsonPrimitive?.double?.toLong()
}
"single_choice" -> {
val optionId = params["item_ids"]?.jsonArray?.firstOrNull()?.jsonPrimitive?.content
}
"multi_choice" -> {
val optionIds = params["item_ids"]?.jsonArray?.map { it.jsonPrimitive.content }
}
"toggle" -> {
val isOn = params["value"]?.jsonPrimitive?.boolean
}
}
}
Để xác nhận callback được kích hoạt, hãy tương tác với input trong bản build thử nghiệm của ứng dụng. Nếu callback handler không nhận được sự kiện, hãy kiểm tra điều kiện tiên quyết. Đảm bảo rằng flow đã được xuất bản sau khi tính năng này có sẵn.
Khi ứng dụng của bạn nhận được đầu vào
Giá trị nhập mặc định hoặc lựa chọn mặc định sẽ không bao giờ được gửi đến ứng dụng của bạn thông qua callback này. Nếu người dùng chấp nhận tùy chọn được đánh dấu Set as default và tiếp tục, sẽ không có sự kiện nào được kích hoạt. Đừng coi việc thiếu sự kiện là “chưa trả lời” — người dùng chỉ đơn giản là giữ nguyên giá trị mặc định.
Các thành phần sau đây kích hoạt sự kiện này:
- Trường văn bản, email, số và điện thoại
- Bộ chọn ngày, giờ và ngày-giờ
- Nhóm lựa chọn đơn và đa lựa chọn, cùng với toggle
Các thành phần sau đây không kích hoạt:
- Trường mật khẩu, lựa chọn sản phẩm và chuyển tab
- Các trường nhập liệu bên trong phần tử Header, vốn được dùng chung trên nhiều màn hình
- Nhóm lựa chọn có Element ID trùng lặp hoặc bị thiếu, hoặc có Group ID được dùng lại trên màn hình khác. Nhóm như vậy sẽ không gửi gì cả, thay vì gửi một phần câu trả lời.
Sự kiện được kích hoạt khi:
- Một trường mất tiêu điểm (focus). Trường đã được xóa trắng sẽ báo cáo một chuỗi rỗng; trường người dùng chưa bao giờ chỉnh sửa sẽ không báo cáo gì. Placeholder không phải là giá trị. Nếu người dùng quay lại trường, chỉnh sửa rồi rời đi, một sự kiện thứ hai sẽ được gửi.
- Người dùng đóng bộ chọn sau khi chọn một giá trị mới. Đóng mà không thay đổi gì sẽ không báo cáo gì.
- Người dùng nhấn vào một tùy chọn hoặc toggle. Sự kiện đa lựa chọn liệt kê mọi tùy chọn đã được chọn, vì vậy việc bỏ chọn tùy chọn cuối cùng sẽ gửi hai mảng rỗng.
Sự kiện không được kích hoạt khi:
- Người dùng đang gõ. Không có luồng sự kiện gõ phím, chỉ có giá trị mà trường đang giữ khi tiêu điểm rời khỏi nó.
- Người dùng gửi hoặc đóng flow. Giá trị đang được chỉnh sửa tại thời điểm đó có thể bị mất; phần giới hạn gửi đề cập đến cách thiết kế màn hình cuối cùng xung quanh vấn đề này.
- Một giá trị được đặt mà không có tương tác của người dùng. Tùy chọn được đánh dấu Set as default được chọn sẵn khi màn hình mở ra, và hành động Set Variable có thể chọn một tùy chọn hoặc điền vào trường nhập liệu từ một tương tác khác. Cả hai đều không gửi sự kiện; trường được điền sẵn chỉ được báo cáo một lần khi người dùng chỉnh sửa nó.
Những gì bạn nhận được
Callback truyền hai sự kiện. Lọc theo name để chỉ hiển thị các sự kiện flow_user_input. Payload JSON trả về có dạng như sau:
{
"name": "flow_user_input",
"instanceId": "scr_registration",
"isBackendEvent": false,
"isCustomerEvent": true,
"element_id": "email",
"element_type": "email_input",
"value": "jane@example.com"
}
| Tham số | Mô tả |
|---|---|
name | flow_user_input cho sự kiện nhập liệu, flow_screen_showed cho lượt xem màn hình. |
instanceId | ID của màn hình chứa input. Element ID là duy nhất trong phạm vi một màn hình, không phải toàn bộ flow. Nếu flow của bạn có input trên nhiều màn hình, hãy kết hợp instanceId với element_id khi lọc sự kiện. |
element_id | Element ID của input, hoặc Group ID của nhóm có thể chọn. |
element_type | Loại element đã gửi sự kiện. Nó xác định tham số nào dưới đây chứa giá trị input. |
value | Chỉ dành cho text field, picker và toggle. Giá trị input: chuỗi ký tự cho text field, số nguyên cho picker, boolean cho toggle. |
item_ids | Chỉ dành cho nhóm single-choice và multi-choice. Element ID của các tùy chọn được chọn, theo thứ tự xuất hiện trong builder. Một mục cho nhóm single-choice; nhiều mục cho nhóm multi-choice. |
item_titles | Chỉ dành cho nhóm single-choice và multi-choice. Tiêu đề của các tùy chọn được liệt kê trong item_ids, theo cùng thứ tự. Không bao giờ rỗng: một tùy chọn không có tiêu đề sẽ báo cáo ID của nó. |
isCustomerEvent | Cờ tiện ích, luôn là true cho sự kiện này. Đánh dấu các sự kiện mà SDK truyền đến callback của bạn. Hữu ích nếu một handler chuyển tiếp mọi sự kiện flow đến analytics của bạn và bạn kiểm tra theo cờ này thay vì theo name. |
isBackendEvent | Cờ tiện ích, luôn là false cho sự kiện này. Đánh dấu các sự kiện mà Adapty cũng ghi lại cho analytics của riêng mình. false xác nhận rằng nội dung người dùng nhập chỉ đến ứng dụng của bạn và không đi đâu khác — Adapty không nhận hay lưu trữ nó. |
Những gì mỗi element báo cáo:
| Trong builder | element_type | Tham số lưu giá trị | Nội dung |
|---|---|---|---|
| Input Text, Number, Phone number | text_input, number_input, phone_input | value | Chuỗi ký tự thô người dùng đã nhập. Số đến dưới dạng chuỗi, không phải kiểu số. |
| Input E-mail | email_input | value | Chuỗi ký tự thô người dùng đã nhập, kể cả khi không vượt qua xác thực định dạng của builder. Hãy tự xác thực ở phía bạn trước khi sử dụng. |
| Input Password | none | none | Không gửi sự kiện. |
| Input Date | date_picker | value | Unix time tính bằng mili giây, dưới dạng số nguyên, tại nửa đêm theo giờ địa phương của ngày được chọn. |
| Input Time | time_picker | value | Unix time tính bằng mili giây, dưới dạng số nguyên, làm tròn xuống theo phút. |
| Input Date & Time | date_picker và time_picker | value | Hai element: một date picker và một time picker. Mỗi element gửi sự kiện riêng. |
| Input được chuyển sang Date & Time trong dropdown Type | date_time_picker | value | Unix time tính bằng mili giây, dưới dạng số nguyên, làm tròn xuống theo phút. |
| Nhóm Single choice | single_choice | item_ids, item_titles | Hai mảng. item_ids: mảng chứa Element ID của tùy chọn được chọn. item_titles: mảng chứa tiêu đề của tùy chọn đó. |
| Nhóm Multi-choice | multi_choice | item_ids, item_titles | Hai mảng. item_ids: Element ID của mọi tùy chọn được chọn, theo thứ tự xuất hiện trong builder. item_titles: tiêu đề của chúng, theo cùng thứ tự. Cả hai mảng đều rỗng khi không có gì được chọn. |
| Nhóm Toggle | toggle | value | Một boolean. |
Để phân nhánh theo câu trả lời, hãy so sánh item_ids, không phải item_titles. Tiêu đề được suy ra: Element Title của tùy chọn nếu bạn đã đặt, nếu không thì văn bản trong ngôn ngữ mặc định của bạn, nếu không thì Element ID của nó. Người dùng xem flow bằng ngôn ngữ khác sẽ thấy văn bản khác.
Ví dụ sự kiện
Những ví dụ này cho thấy các thuộc tính có sẵn trên mỗi sự kiện, với các giá trị minh họa trong phần chú thích.
Text, email, number, and phone input (Click to expand)
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String,
) {
name // "flow_user_input"
paramsJsonString // "{\"name\":\"flow_user_input\",\"instanceId\":\"scr_J260KU5q\",\"isBackendEvent\":false,\"isCustomerEvent\":true,\"element_id\":\"email\",\"element_type\":\"email_input\",\"value\":\"jane@example.com\"}"
// paramsJsonString, once decoded:
params["name"] // "flow_user_input"
params["instanceId"] // "scr_J260KU5q"
params["isCustomerEvent"] // true
params["isBackendEvent"] // false
params["element_id"] // "email"
params["element_type"] // "email_input"
params["value"] // "jane@example.com"
} Bộ chọn ngày, giờ và ngày-giờ (Nhấn để mở rộng)
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String,
) {
// paramsJsonString, once decoded:
params["element_id"] // "birthday"
params["element_type"] // "date_picker"
params["value"] // 645408000000 (Unix milliseconds — 1990-06-15, local midnight)
} Lựa chọn đơn (Nhấn để mở rộng)
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String,
) {
// paramsJsonString, once decoded:
params["element_id"] // "experience"
params["element_type"] // "single_choice"
params["item_ids"] // ["pro"]
params["item_titles"] // ["I train professionally"]
} Multi choice (Click to expand)
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String,
) {
// paramsJsonString, once decoded:
params["element_id"] // "interests"
params["element_type"] // "multi_choice"
params["item_ids"] // ["sports", "music"]
params["item_titles"] // ["Sports", "Music"]
} Toggle (Click to expand)
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String,
) {
// paramsJsonString, once decoded:
params["element_id"] // "reminders"
params["element_type"] // "toggle"
params["value"] // true
} Giới hạn và cách truyền dữ liệu
Flow gửi dữ liệu thô — địa chỉ email, số điện thoại, và bất cứ thứ gì người dùng nhập vào. Hãy coi mọi thứ mà analytics callback trả về đều là dữ liệu cá nhân, và đừng lưu trữ chúng ở bất kỳ nơi nào bạn không muốn lưu địa chỉ email của người dùng.
- Giá trị cuối cùng được ưu tiên: Bạn nhận một sự kiện cho mỗi trường, mang theo giá trị mà người dùng đã chốt chứ không phải từng lần nhấn phím. Nếu họ chỉnh sửa một trường, chỉ phiên bản cuối cùng mới được gửi đến bạn.
- Không đảm bảo submit: Giá trị được gửi đến bạn khi người dùng di chuyển qua flow, và người dùng có thể thoát bất cứ lúc nào. Hãy đợi flow đóng lại trước khi coi một tập hợp câu trả lời là hoàn chỉnh.
- Gửi theo khả năng tốt nhất: Nếu người dùng đóng flow hoặc thu nhỏ ứng dụng trong khi một trường vẫn đang được focus hoặc một picker vẫn đang mở, giá trị đó có thể bị mất.
Để đảm bảo giá trị của trường cuối cùng luôn đáng tin cậy, hãy kết thúc flow bằng một màn hình không có ô nhập liệu và không có picker, đồng thời đóng flow bằng một thao tác rõ ràng của người dùng thay vì tự động đóng khi màn hình đó xuất hiện. Chuyển sang màn hình cuối sẽ lấy focus khỏi trường trước đó, đó chính là thao tác kích hoạt việc gửi giá trị.
Lưu lại từng giá trị input khi observer nhận được, rồi gửi toàn bộ khi flow đóng lại. Để bắt đúng thời điểm đó, hãy override flowViewDidDisappear trên cùng observer. Hàm này chạy khi flow view bị dismiss — dù người dùng hoàn thành flow hay đóng giữa chừng.
Các trường hợp sử dụng
Đăng ký người dùng trên backend của bạn
Thu thập các giá trị khi chúng xuất hiện và gửi tất cả trong một request sau khi flow đóng lại, để một request duy nhất mang đầy đủ thông tin.
View biến mất dù người dùng hoàn thành flow hay bỏ dở giữa chừng. Hãy kiểm tra các trường cần thiết trước khi gọi backend.
Flow không thể hiển thị lỗi từ backend của bạn. Callback không có giá trị trả về, và SDK không có phương thức nào để gửi dữ liệu vào flow đang chạy. Nếu đăng ký thất bại, chẳng hạn do email đã được sử dụng, hãy hiển thị lỗi trong UI của bạn sau khi flow đóng lại.
class MyFlowsEventsObserver : AdaptyUIFlowsEventsObserver {
private val flowAnswers = mutableMapOf<String, String>()
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String,
) {
if (name != "flow_user_input") return
val params = Json.parseToJsonElement(paramsJsonString).jsonObject
val elementId = params["element_id"]?.jsonPrimitive?.content ?: return
val value = params["value"]?.jsonPrimitive?.contentOrNull ?: return
flowAnswers[elementId] = value
}
override fun flowViewDidDisappear(view: AdaptyUIFlowView) {
if (!flowAnswers.containsKey("email")) return
// Send flowAnswers to your backend here to create the account.
flowAnswers.clear()
}
}
Làm phong phú hồ sơ người dùng với dữ liệu
Để liên kết thông tin người dùng nhập với hồ sơ của họ và tránh hỏi lại những thông tin đã có, hãy cập nhật hồ sơ người dùng ngay khi nhận được giá trị.
Ví dụ, nếu flow của bạn có một trường nhập văn bản với Element ID là name và một trường nhập email với Element ID là email:
private fun handleFlowInput(name: String, paramsJsonString: String) {
if (name != "flow_user_input") return
val params = Json.parseToJsonElement(paramsJsonString).jsonObject
val value = params["value"]?.jsonPrimitive?.contentOrNull ?: return
val builder = AdaptyProfileParameters.Builder()
when (params["element_id"]?.jsonPrimitive?.content) {
"name" -> builder.withFirstName(value)
"email" -> builder.withEmail(value)
else -> return
}
mainUiScope.launch {
Adapty.updateProfile(builder.build())
.onError { error ->
// handle the error
}
}
}
Tùy chỉnh flow hiển thị sau đó
Câu trả lời trong quiz cũng có thể quyết định nội dung người dùng thấy ở một placement sau — một flow khác, hoặc một paywall khác bên trong đó.
Ví dụ: hỏi người dùng về kinh nghiệm thể thao của họ trong onboarding flow, rồi hiển thị cho mỗi nhóm một flow riêng với sản phẩm và nội dung khác nhau.
- Thêm quiz vào flow của bạn. Đặt selectable group có Group ID là
experience, và mỗi lựa chọn một Element ID có ý nghĩa. - Xử lý các câu trả lời và đặt thuộc tính tùy chỉnh cho người dùng.
private fun handleFlowInput(name: String, paramsJsonString: String) {
if (name != "flow_user_input") return
val params = Json.parseToJsonElement(paramsJsonString).jsonObject
if (params["element_id"]?.jsonPrimitive?.content != "experience") return
val optionId = params["item_ids"]?.jsonArray?.firstOrNull()
?.jsonPrimitive?.contentOrNull ?: return
val builder = AdaptyProfileParameters.Builder()
// Set the custom attribute 'experience' to the option the user selected
// (beginner, amateur, or pro).
builder.withCustomAttribute("experience", optionId)
mainUiScope.launch {
Adapty.updateProfile(builder.build())
.onError { error ->
// handle the error
}
}
}
- Tạo một phân khúc cho mỗi giá trị thuộc tính tùy chỉnh.
- Tạo một placement và thêm một đối tượng cho mỗi phân khúc.
- Hiển thị flow cho placement đó trong ứng dụng của bạn.