Xử lý dữ liệu từ flows trong iOS 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 một toggle trong một flow, SDK sẽ truyền giá trị đó đến ứng dụng của bạn thông qua analytics callback.

Các ứng dụng thường sử dụng dữ liệu đó để:

  • Đăng ký người dùng trên backend riêng 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 đóng lại.
  • Lưu câu trả lời và tùy chọn: Theo dõi những gì người dùng đã chọn để ứng dụng của bạn có thể xử lý 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 quiz dưới dạng custom attributes, sau đó nhắm mục tiêu 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 bên trong đó.
  • Đẩy dữ liệu sang các nền tảng analytics bên thứ ba: Chuyển tiếp câu trả lời đến Amplitude, Mixpanel, hoặc bất kỳ nền tảng product analytics nào bạn đang dùng.

Các input và nhóm có thể chọn sẽ tự động báo cáo giá trị của chúng. Để phân biệt các input trong code của bạn, hãy đặt cho mỗi input một Element ID có ý nghĩa và mỗi nhóm có thể chọn 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 truyền qua cùng callback với các sự kiện analytics khác của một flow, dưới tên sự kiện flow_user_input. Đăng ký callback này cùng với các handler sự kiện flow khác của bạn.

Closure và delegate method nhận cùng hai tham số, vì vậy code đọc giá trị là như nhau trong cả hai trường hợp.

Callback didReceiveAnalyticEvent cung cấp tất cả các sự kiện analytics từ một flow, bao gồm cả lượt xem màn hình.

  • Tham số name chứa tên sự kiện. Để lọc các sự kiện nhập liệu của người dùng, hãy so sánh name với flow_user_input.
  • Tham số element_type cho biết danh mục của phần tử.
  • Giá trị nhập liệu được lưu ở các tham số khác nhau tùy theo 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 báo cáo các tùy chọn đang active trong item_idsitem_titles
func handleFlowInput(name: String, params: [String: any Sendable]) {
    guard name == "flow_user_input",
          let elementId = params["element_id"] as? String,
          let elementType = params["element_type"] as? String
    else { return }

    // The screen the input sits on. Pair it with elementId to tell apart
    // two fields that share an Element ID on different screens.
    let screenId = params["instanceId"] as? String

    switch elementType {
    case "text_input", "email_input", "number_input", "phone_input":
        let text = params["value"] as? String
    case "date_picker", "time_picker", "date_time_picker":
        // Integer Unix time in milliseconds, not the seconds Date expects.
        let date = (params["value"] as? Int).map { Date(timeIntervalSince1970: Double($0) / 1000) }
    case "single_choice":
        let optionId = (params["item_ids"] as? [String])?.first
    case "multi_choice":
        let optionIds = params["item_ids"] as? [String]
    case "toggle":
        let isOn = params["value"] as? Bool
    default:
        break
    }
}

Để 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 dữ liệu đầu vào

Important

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ả
nameflow_user_input cho sự kiện nhập liệu, flow_screen_showed cho lượt xem màn hình.
instanceIdID 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_idElement ID của input, hoặc Group ID của nhóm có thể chọn.
element_typeLoại element đã gửi sự kiện. Nó xác định tham số nào dưới đây chứa giá trị input.
valueChỉ 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_idsChỉ 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_titlesChỉ 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ó.
isCustomerEventCờ 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.
isBackendEventCờ 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 builderelement_typeTham số lưu giá trịNội dung
Input Text, Number, Phone numbertext_input, number_input, phone_inputvalueChuỗ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-mailemail_inputvalueChuỗ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 PasswordnonenoneKhông gửi sự kiện.
Input Datedate_pickervalueUnix 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 Timetime_pickervalueUnix 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 & Timedate_pickertime_pickervalueHai 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 Typedate_time_pickervalueUnix 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 choicesingle_choiceitem_ids, item_titlesHai 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-choicemulti_choiceitem_ids, item_titlesHai 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 ToggletogglevalueMộ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

Các ví dụ dưới đây hiển thị 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 bình luận.

Nhập văn bản, email, số và số điện thoại (Nhấn để mở rộng)
func handleFlowInput(name: String, params: [String: any Sendable]) {
    name;                      // "flow_user_input"
    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"   (String)
}
Bộ chọn ngày, giờ và ngày giờ (Nhấn để mở rộng)
func handleFlowInput(name: String, params: [String: any Sendable]) {
    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)
func handleFlowInput(name: String, params: [String: any Sendable]) {
    params["element_id"];      // "experience"
    params["element_type"];    // "single_choice"
    params["item_ids"];        // ["pro"]
    params["item_titles"];     // ["I train professionally"]
}
Nhiều lựa chọn (Nhấn để mở rộng)
func handleFlowInput(name: String, params: [String: any Sendable]) {
    params["element_id"];      // "interests"
    params["element_type"];    // "multi_choice"
    params["item_ids"];        // ["sports", "music"]
    params["item_titles"];     // ["Sports", "Music"]
}
Toggle (Click to expand)
func handleFlowInput(name: String, params: [String: any Sendable]) {
    params["element_id"];      // "reminders"
    params["element_type"];    // "toggle"
    params["value"];           // true   (Bool)
}

Phân phối và giới hạn

Warning

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 từng giá trị đầu vào khi handler nhận được, và gửi toàn bộ tập dữ liệu khi flow đóng lại. Để bắt được thời điểm đó, hãy triển khai flowControllerDidDisappear trên AdaptyFlowControllerDelegate trong UIKit, hoặc truyền một closure didDisappear vào modifier .flow trong SwiftUI. Cả hai đều chạy sau khi view của flow đã rời khỏi màn hình, dù người dùng hoàn thành flow hay đóng nó lại.

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 chúng một lần sau khi flow đóng lại, để một request mang đầy đủ tất cả câu trả lời.

View sẽ 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 bạn cần 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 một flow đang chạy. Nếu đăng ký thất bại, chẳng hạn vì email đã được sử dụng, hãy hiển thị lỗi trong UI của bạn sau khi flow đóng lại.

private var flowAnswers: [String: String] = [:]

func handleFlowInput(name: String, params: [String: any Sendable]) {
    guard name == "flow_user_input",
          let elementId = params["element_id"] as? String,
          let value = params["value"] as? String
    else { return }

    flowAnswers[elementId] = value
}

func flowControllerDidDisappear(_ controller: AdaptyFlowController) {
    guard flowAnswers["email"] != nil else { return }

    // Send flowAnswers to your backend here to create the account.

    flowAnswers.removeAll()
}

Làm phong phú thêm dữ liệu hồ sơ người dùng

Để 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 ô nhập văn bản với Element ID là name và một ô nhập email với Element ID là email:

func handleFlowInput(name: String, params: [String: any Sendable]) {
    guard name == "flow_user_input",
          let elementId = params["element_id"] as? String,
          let value = params["value"] as? String
    else { return }

    let builder = AdaptyProfileParameters.Builder()

    switch elementId {
    case "name":
        builder.with(firstName: value)
    case "email":
        builder.with(email: value)
    default:
        return
    }

    // Delegate methods are synchronous; kick off the async update in a Task.
    Task {
        do {
            try await Adapty.updateProfile(params: builder.build())
        } catch {
            // 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 người dùng thấy gì ở một placement sau đó — một flow khác, hoặc một paywall khác bên trong placement đó.

Ví dụ: hỏi người dùng về kinh nghiệm tập thể thao trong onboarding flow, rồi hiển thị cho mỗi nhóm một flow riêng với các sản phẩm và nội dung khác nhau.

  1. Thêm một quiz vào flow của bạn. Đặt Group ID cho selectable groupexperience, và đặt Element ID có ý nghĩa cho từng lựa chọn.
  2. Xử lý các câu trả lời và thiết lập custom attributes cho người dùng.
func handleFlowInput(name: String, params: [String: any Sendable]) {
    guard name == "flow_user_input",
          params["element_id"] as? String == "experience",
          let optionId = (params["item_ids"] as? [String])?.first
    else { return }

    let builder = AdaptyProfileParameters.Builder()
    // Set the custom attribute 'experience' to the option the user selected
    // (beginner, amateur, or pro).
    try? builder.with(customAttribute: optionId, forKey: "experience")

    Task {
        do {
            try await Adapty.updateProfile(params: builder.build())
        } catch {
            // handle the error
        }
    }
}
  1. Tạo một phân khúc cho mỗi giá trị thuộc tính tùy chỉnh.
  2. Tạo một placement và thêm một đối tượng cho mỗi phân khúc.
  3. Hiển thị flow cho placement đó trong ứng dụng của bạn.