Kích hoạt mua hàng với Flow Builder trong Flutter SDK
Để kích hoạt in-app purchase, bạn cần hiểu ba khái niệm chính:
- Sản phẩm – những gì người dùng có thể mua (gói đăng ký, consumable, quyền truy cập trọn đời)
- Flow – chuỗi màn hình hiển thị sản phẩm cho người dùng, được xây dựng bằng Flow Builder không cần code. SDK lấy chúng qua
getFlow. Nếu bạn muốn tự xây dựng giao diện bằng code, hãy dùng paywall thay thế — xem Triển khai paywall thủ công. - Placement – vị trí và thời điểm hiển thị flow trong ứng dụng (ví dụ:
main,onboarding,settings). Bạn gắn flow vào placement trên dashboard, sau đó yêu cầu chúng theo placement ID trong code. Cách này giúp bạn dễ dàng chạy A/B test và hiển thị các flow khác nhau cho từng nhóm người dùng. Adapty cung cấp cho bạn ba cách để kích hoạt tính năng mua hàng trong ứng dụng. Hãy chọn một trong số đó tùy theo yêu cầu của ứng dụng: | Triển khai | Độ phức tạp | Khi nào nên dùng | |------------------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Adapty Flow Builder | ✅ Dễ | Bạn tạo một flow hoàn chỉnh, sẵn sàng để mua hàng trong trình tạo không cần code. Adapty tự động hiển thị và xử lý toàn bộ luồng mua hàng phức tạp, xác thực biên lai và quản lý gói đăng ký. | | Paywall tự tạo | 🟡 Trung bình | Bạn tự xây dựng giao diện paywall trong code ứng dụng, nhưng vẫn lấy đối tượng flow từ Adapty để duy trì sự linh hoạt trong danh sách sản phẩm. Xem hướng dẫn. | | Chế độ Observer | 🔴 Khó | Bạn đã có hạ tầng xử lý mua hàng riêng và muốn tiếp tục dùng nó. Lưu ý rằng chế độ observer có một số hạn chế trong Adapty. Xem bài viết. |
Các bước dưới đây hướng dẫn cách triển khai một flow được tạo trong Adapty Flow Builder.
Nếu bạn muốn tự xây dựng giao diện paywall, hãy xem Triển khai paywall thủ công.
Để hiển thị một flow được tạo trong Adapty Flow Builder, trong code của ứng dụng, bạn chỉ cần:
- Lấy flow: Lấy flow đó từ Adapty.
- Hiển thị flow và Adapty sẽ xử lý các giao dịch mua cho bạn: Hiển thị view trong ứng dụng của bạn.
- Xử lý các hành động nút bấm: Liên kết các tương tác của người dùng với phản hồi tương ứng trong ứng dụng. Ví dụ: mở liên kết hoặc đóng flow khi người dùng nhấn nút.
Trước khi bắt đầu
Trước khi bắt đầu, hãy hoàn thành các bước sau:
- Kết nối ứng dụng của bạn với App Store và/hoặc Google Play trong Adapty Dashboard.
- Tạo sản phẩm trong Adapty.
- Tạo flow và thêm sản phẩm vào đó.
- Tạo placement và thêm flow vào đó.
- Cài đặt và kích hoạt Adapty SDK trong mã ứng dụng của bạn. Hướng dẫn này sử dụng API của Adapty Flutter SDK v4.
Cách nhanh nhất để hoàn thành các bước này là làm theo hướng dẫn quickstart hoặc tạo paywall và placement bằng Developer CLI.
1. Lấy flow
Các flow của bạn được liên kết với các placement được cấu hình trong dashboard. Placement cho phép bạn chạy các flow khác nhau cho các đối tượng khác nhau hoặc để chạy A/B test.
Để lấy một flow đã tạo trong Adapty Flow Builder, bạn cần:
-
Lấy đối tượng
flowtheo ID placement bằng phương thứcgetFlowvà kiểm tra xem nó có được tạo trong builder hay không thông qua thuộc tínhhasViewConfiguration. -
Tạo flow view bằng phương thức
createFlowView. View chứa các phần tử UI và style cần thiết để hiển thị flow.
Để lấy cấu hình view, bạn phải bật toggle Show on device trong builder. Nếu không, bạn sẽ nhận được cấu hình view rỗng và flow sẽ không được hiển thị.
try {
// the requested flow
final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID');
final view = await AdaptyUI().createFlowView(
flow: flow,
);
} on AdaptyError catch (adaptyError) {
// handle the error
} catch (e) {
// handle the error
}
2. Hiển thị flow
Bây giờ, khi bạn đã có flow view, chỉ cần thêm vài dòng code để hiển thị nó.
Để hiển thị flow, sử dụng phương thức view.present() trên view được tạo bởi phương thức createFlowView. Mỗi view chỉ có thể được hiển thị một lần: sau khi bạn đóng nó, view sẽ được giải phóng khỏi bộ nhớ. Nếu bạn cần hiển thị lại flow, hãy gọi createFlowView thêm một lần nữa để tạo một view instance mới.
try {
await view.present();
} on AdaptyError catch (e) {
// handle the error
} catch (e) {
// handle the error
}
Để biết thêm chi tiết về cách hiển thị một flow, hãy xem hướng dẫn của chúng tôi.
3. Xử lý các hành động nút bấm
Khi người dùng nhấn các nút trong flow, Flutter SDK tự động xử lý các thao tác mua hàng, khôi phục, đóng màn hình và mở URL. Tuy nhiên, một số nút có ID tùy chỉnh hoặc được định sẵn và cần bạn xử lý trong code.
Để kiểm soát hoặc theo dõi các tiến trình trên màn hình flow, hãy implement các phương thức của AdaptyUIFlowsEventsObserver và thiết lập observer trước khi hiển thị bất kỳ màn hình nào. Khi người dùng thực hiện một hành động, flowViewDidPerformAction sẽ được gọi, và ứng dụng của bạn cần phản hồi tương ứng với action ID đó.
Có ba phương thức observer bắt buộc: flowViewDidFinishPurchase, flowViewDidFinishRestore, và flowViewDidReceiveError — class của bạn sẽ không thể biên dịch nếu thiếu chúng.
Triển khai observer dưới dạng một đối tượng độc lập, tồn tại lâu dài thay vì gắn vào widget. Vì toàn bộ ứng dụng chỉ dùng chung một slot observer duy nhất, nếu gắn vào State sẽ gây rò rỉ màn hình (SDK giữ một strong reference đến nó) và sẽ bị thay thế âm thầm khi màn hình tiếp theo đăng ký. Sử dụng extends cũng kế thừa hành vi mặc định của SDK, vì vậy ngoài ba phương thức bắt buộc, bạn chỉ cần override những callback mà bạn quan tâm.
// A dedicated, long-lived handler for flow events.
// It does NOT live inside a Widget/State, so it never leaks and is never
// silently replaced when screens are pushed or popped.
class FlowEventsHandler extends AdaptyUIFlowsEventsObserver {
// A single, app-wide instance — same idiom as Adapty() and AdaptyUI().
static final FlowEventsHandler _instance = FlowEventsHandler._();
factory FlowEventsHandler() => _instance;
FlowEventsHandler._();
// This method is called when user performs an action on the flow UI.
// Overriding it replaces the default behavior (dismiss on close, open URLs),
// so keep those cases if you want to preserve it.
@override
void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) {
switch (action) {
case const CloseAction():
case const AndroidSystemBackAction(): // close the flow on the Android back button
view.dismiss();
break;
case OpenUrlAction(:final url, :final openIn):
AdaptyUI().openUrl(url, openIn: openIn);
break;
default:
break;
}
}
// Required: decide what happens after a purchase finishes
@override
void flowViewDidFinishPurchase(AdaptyUIFlowView view,
AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) {
if (purchaseResult is! AdaptyPurchaseResultUserCancelled) {
view.dismiss();
}
}
// Required: dismiss the flow once a restore succeeds
@override
void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) {
view.dismiss();
}
// Required: handle rendering and other view errors
@override
void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) {
print('Flow error: $error');
view.dismiss();
}
}
Đăng ký handler một lần khi khởi động ứng dụng, trước khi hiển thị bất kỳ flow nào:
AdaptyUI().setFlowsEventsObserver(FlowEventsHandler());
Bước tiếp theo
Bạn có câu hỏi hoặc gặp sự cố? Hãy xem diễn đàn hỗ trợ 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. Hãy kiểm tra mua hàng trong sandbox App Store hoặc Google Play Store để đảm bảo bạn có thể hoàn thành một lần mua thử từ paywall.
Tiếp theo, bạn cần kiểm tra mức độ truy cập của người dùng để đảm bảo bạn hiển thị paywall hoặc cấp quyền truy cập vào các tính năng trả phí cho đúng người dùng.
Ví dụ đầy đủ
Dưới đây là cách tích hợp tất cả các bước trên vào ứng dụng của bạn.
void main() {
// Register a single, long-lived observer once, before any flow is shown.
// It is intentionally a plain object (NOT a Widget/State): its lifetime is the
// whole app, so it never leaks and is never silently replaced when screens are
// pushed or popped.
AdaptyUI().setFlowsEventsObserver(FlowEventsHandler());
runApp(MaterialApp(home: FlowScreen()));
}
/// A dedicated handler for AdaptyUI flow events.
///
/// It `extends` [AdaptyUIFlowsEventsObserver] (rather than being implemented
/// by a `State`), which gives you two things for free:
/// * the SDK's sensible defaults for optional callbacks, so besides the three
/// required methods you only override what you actually care about;
/// * a lifecycle that is independent of the widget tree — there is no strong
/// reference back into a `Widget`, so nothing leaks and there is nothing to
/// unregister.
///
/// Every callback receives the [AdaptyUIFlowView] it relates to, so handling
/// flow actions never requires a `BuildContext` or widget state.
class FlowEventsHandler extends AdaptyUIFlowsEventsObserver {
// A single, app-wide instance — same idiom as Adapty() and AdaptyUI().
static final FlowEventsHandler _instance = FlowEventsHandler._();
factory FlowEventsHandler() => _instance;
FlowEventsHandler._();
// Called when the user performs an action on the flow UI.
@override
void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) {
switch (action) {
case const CloseAction():
case const AndroidSystemBackAction(): // close the flow on the Android back button
view.dismiss();
break;
case OpenUrlAction(:final url, :final openIn):
// Open the URL natively, honoring the dashboard browser setting.
AdaptyUI().openUrl(url, openIn: openIn);
break;
default:
break;
}
}
// Required: decide what happens after a purchase finishes.
@override
void flowViewDidFinishPurchase(AdaptyUIFlowView view,
AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) {
if (purchaseResult is! AdaptyPurchaseResultUserCancelled) {
view.dismiss();
}
}
// Required: dismiss the flow once a restore succeeds.
@override
void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) {
view.dismiss();
}
// Required: handle rendering and other view errors.
@override
void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) {
print('Flow error: $error');
view.dismiss();
}
}
class FlowScreen extends StatefulWidget {
const FlowScreen({super.key});
@override
State<FlowScreen> createState() => _FlowScreenState();
}
class _FlowScreenState extends State<FlowScreen> {
@override
void initState() {
super.initState();
_showFlowIfNeeded();
}
Future<void> _showFlowIfNeeded() async {
try {
final flow = await Adapty().getFlow(
placementId: 'YOUR_PLACEMENT_ID',
);
if (!flow.hasViewConfiguration) return;
final view = await AdaptyUI().createFlowView(flow: flow);
await view.present();
} catch (_) {
// Handle any errors (network, SDK issues, etc.)
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Adapty Flow Example')),
body: Center(
// Add a button to re-trigger the flow for testing purposes.
child: ElevatedButton(
onPressed: _showFlowIfNeeded,
child: const Text('Show Flow'),
),
),
);
}
}