Bật mua hàng với Flow & Paywall Builder trong Flutter SDK
Để bật tính năng mua hàng trong ứng dụng, 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 & paywall – các màn hình hiển thị sản phẩm cho người dùng, được xây dựng bằng Flow & Paywall Builder không cần code. SDK truy xuất chúng qua
getFlow. Nếu bạn muốn tự xây dựng giao diện bằng code, xem Triển khai custom paywall. - Placement – nơi và thời điểm hiển thị flow & paywall trong ứng dụng của bạn (ví dụ:
main,onboarding,settings). Bạn gắn chúng vào 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 nội dung 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 & Paywall Builder | ✅ Dễ | Bạn tạo một flow hoặc paywall hoàn chỉnh, sẵn sàng để thanh toán trong trình tạo no-code. Adapty tự động hiển thị và xử lý toàn bộ logic mua hàng phức tạp, xác thực receipt và quản lý gói đăng ký. |
| Paywall tùy chỉnh | 🟡 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 để linh hoạt trong việc cung cấp sản phẩm. Xem hướng dẫn. |
| Observer mode | 🔴 Khó | Bạn đã có hạ tầng xử lý mua hàng riêng và muốn tiếp tục sử dụng nó. Lưu ý rằng observer mode 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 hoặc paywall được tạo trong Adapty Flow & Paywall Builder.
Nếu bạn muốn tự xây dựng giao diện paywall, hãy xem Triển khai paywall tùy chỉnh.
Để hiển thị một flow hoặc paywall được tạo trong Adapty Flow & Paywall Builder, trong code ứng dụng của bạn, bạn chỉ cần:
- Lấy flow hoặc paywall: Lấy từ Adapty.
- Hiển thị và để Adapty xử lý giao dịch mua cho bạn: Hiển thị view trong ứng dụng của bạn.
- Xử lý các thao tác nút: Liên kết 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 paywall 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 một flow hoặc paywall và thêm sản phẩm vào đó.
- Tạo một placement và thêm flow hoặc paywall vào đó.
- Cài đặt và kích hoạt Adapty SDK trong code ứng dụng của bạn. Hướng dẫn này sử dụng Adapty Flutter SDK v4 APIs.
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 hoặc paywall
Các flow và paywall 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 hoặc paywall khác nhau cho các đối tượng khác nhau hoặc chạy A/B test.
Để lấy flow hoặc paywall được tạo trong Adapty Flow & Paywall 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 bằng thuộc tínhhasViewConfiguration. -
Tạo flow view bằng phương thức
createFlowView. View này chứa các phần tử UI và styling cần thiết để hiển thị.
Để lấy cấu hình view, hãy xuất bản flow hoặc paywall. Một flow hoặc paywall có chỉnh sửa chưa xuất bản sẽ có trạng thái Dirty, và placement của nó vẫn tiếp tục phục vụ phiên bản đã xuất bản lần cuối.
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 hoặc paywall
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 hoặc paywall, hãy xem hướng dẫn của chúng tôi.
3. Xử lý hành động nút bấm
Khi người dùng nhấn các nút trong flow hoặc paywall, Flutter SDK tự động xử lý việc mua hàng, khôi phục, đóng màn hình và mở URL. 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 bạn xử lý hành động 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 triển khai các phương thức 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 hoặc paywall 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 đỡ!
Flow hoặc paywall của bạn đã sẵn sàng để hiển thị trong ứng dụng. Hãy kiểm tra các giao dịch mua hàng trong sandbox của App Store hoặc trên Google Play Store để đảm bảo bạn có thể hoàn tất một giao dịch mua thử từ flow.
Tiếp theo, bạn cần kiểm tra mức độ truy cập của người dùng để đảm bảo 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'),
),
),
);
}
}