Skip to content
Back to archipelago.banuacoder.com

Feature SDKs

How to add a paywall and subscription state to a Flutter app ​

Free Reference for paywall_sdk: every variable, option and package it generates. Command: archipelago generate paywall_sdk

Generate the Paywall SDK, use the mock purchase flow, check premium status and listen for changes, and swap in a real payment provider.

What you'll learn ​

  • Generating the Paywall SDK with a pricing page and entitlements
  • Understanding the mock purchase flow
  • Checking premium status and listening for changes
  • Replacing the mock with a real payment provider

Prerequisites ​

The paywall does not depend on auth_sdk. The brick generates core_infrastructure_package for you if it is missing. It reads your UI kit name from archipelago.yaml.

Step 1: Generate the Paywall SDK ​

bash
archipelago generate paywall_sdk

The prompts are:

  • appName: your app name, for example MyApp
  • prefix: the prefix for generated classes
  • uiKitPackageName: read from archipelago.yaml
  • isForMonorepo: leave this as true
  • stateManagement: bloc (default), cubit, riverpod or provider

Or use a config file:

json
{
  "appName": "MyApp",
  "isForMonorepo": true,
  "stateManagement": "bloc"
}
bash
archipelago generate paywall_sdk --config paywall_config.json

Step 2: Understand the Generated Structure ​

text
features/paywall/
├── paywall_api/
│   └── lib/src/        # PaywallSDK, events, products, purchase result, status, tier
└── paywall_impl/
    └── lib/src/
        ├── paywall_sdk_impl.dart
        ├── data/       # PaywallDataSource (mock) and the repository
        ├── domain/usecases/
        ├── di/         # Global module, container, imports
        ├── presentation/
        │   ├── paywall_shell_page.dart
        │   ├── paywall_page.dart
        │   └── bloc/   # Or cubit or notifiers, per stateManagement
        └── router/paywall_router.dart

Step 3: Check the Registration ​

The post-generation hook registers PaywallImplGlobalModule in lib/di/injector.dart. You write no registration code.

  • The global module registers PaywallManifest (the /paywall routes) and PaywallSDK as a lazy singleton. The status starts as free.
  • The data source, repository and use cases live in paywallContainer, a child GetIt. paywall_imports.dart forwards the root PaywallSDK.
  • The shell page builds the state holder from its lease. PaywallPage never reads GetIt.

Step 4: Check Entitlements and Open the Paywall ​

dart
final paywall = getIt<PaywallSDK>();

if (paywall.isPremium) {
  // Pro or enterprise access
}

if (paywall.hasEntitlement('export_pdf')) {
  // Show the feature
}

await paywall.launchPaywall(context);

Listen for changes:

dart
final remove = paywall.addStatusListener((event) {
  switch (event) {
    case PaywallPurchaseEvent(): refreshEntitlements();
    case PaywallRestoreEvent(): syncData();
    case PaywallExpiredEvent(): showRenewalPrompt();
    case PaywallStatusChangedEvent(): updateUi();
  }
});

statusStream, currentStatus and currentTier give the same data without a listener.

Step 5: Use the Mock Purchase Flow ​

MockPaywallDataSource returns four fixed products: pro_monthly, pro_yearly, enterprise_monthly and enterprise_yearly. Its purchase() always succeeds with the transaction ID mock_txn_123. No store, receipt check or server sync exists.

dart
final products = await paywall.getProducts();
final result = await paywall.purchase(products.first);

Treat the generated feature as UI and flow scaffolding. It is not a production paywall.

Step 6: Connect a Real Payment Provider ​

Implement PaywallDataSource with your provider and register it in place of the mock in the feature container. Keep the same method shapes:

dart
@LazySingleton(as: PaywallDataSource)
class RevenueCatPaywallDataSource implements PaywallDataSource {
  @override
  Future<List<PaywallProduct>> getProducts() async {
    // Map your provider's offerings to PaywallProduct
  }

  @override
  Future<PurchaseResult> purchase(PaywallProduct product) async {
    // Call the store SDK and map the result
  }
}

Validate receipts and sync entitlements on your server. The mock does neither.

Step 7: Run the Tests ​

bash
cd features/paywall/paywall_impl
flutter test

paywall_di_test.dart checks the manifest, the lazy SDK and the container. The presentation tests cover the chosen state holder.

Common Customizations ​

CustomizationWhere to Change
Products and plansThe data source
Paywall layoutpresentation/paywall_page.dart
Entitlement rulesPaywallSDKImpl
Gate a featureCall hasEntitlement or isPremium in the feature

Next Steps ​