Background
Get set up with the Helium SDK for Flutter. Reach out over your Helium slack channel or email founders@tryhelium.com for any questions.
Installation
Run flutter pub add helium_flutter
or manually add the helium_flutter package to your pubspec.yaml:
dependencies :
helium_flutter : ^3.0.5
Then run:
The minimum version of Flutter supported by this SDK is 3.24.0 .
Recommended - Make sure that Swift Package Manager support is enabled:
flutter upgrade
flutter config --enable-swift-package-manager
See this Flutter documentation for more details about Swift Package Manager.
You can still use Cocoapods for your dependencies if preferred. If you need to disable Swift Package Manager dependencies after having enabled it, refer to that same Flutter documentation .
iOS Settings
Helium requires iOS 15+. If your app already has a minimum of iOS 15 or higher, you’re all set. This can be specified in your ios/Podfile
with:
If you still see errors related to minimum iOS version, consider updating to 15.0 or higher directly in the Xcode project .
Initialize Helium
In your app’s initialization code (typically in main.dart
or your root widget), add the following to configure Helium and prepare your paywalls:
import 'package:helium_flutter/helium_flutter.dart' ;
Future < void > main () async {
WidgetsFlutterBinding . ensureInitialized ();
final heliumFlutter = HeliumFlutter ();
await heliumFlutter. initialize (
apiKey : "<your-helium-api-key>" ,
);
runApp ( const MyApp ());
}
(Optional) Pass in a listener for paywall events.
(Optional) Delegate for handling your own purchase logic. If not provided, Helium will handle purchase logic for you.
(Optional) A widget to display if paywall fails to display. See Fallbacks section.
(Optional) If set, a custom user id to use instead of Helium’s. We’ll use this id when forwarding to third party analytics services, so this can be used for attribution (e.g. an amplitude user id, or a custom user id from your own analytics service)
(Optional) Pass in custom user traits to be used for targeting, personalization, and dynamic paywall content.
(Optional) RevenueCat ONLY. Supply RevenueCat appUserID here (and initialize RevenueCat before Helium initialize).
You can also set this value outside of initialize: HeliumFlutter().setRevenueCatAppUserId(await Purchases.appUserID)
(Optional) Path to fallback bundle. See Fallbacks section.
paywallLoadingConfig
HeliumPaywallLoadingConfig?
(Optional) Set loading budget in seconds and whether to even show a loading state. This can be done per trigger.final paywallLoadingConfig = HeliumPaywallLoadingConfig (
loadingBudget : 5 , // after 5 seconds, show fallback instead
perTriggerLoadingConfig : {
"onboarding" : TriggerLoadingConfig (useLoadingState : false ),
"from_menu" : TriggerLoadingConfig (useLoadingState : true , loadingBudget : 2 ),
}
);
Custom User ID and Custom User Traits
You can set a custom user ID and custom user traits with the initialize
method or by calling: await heliumFlutter. overrideUserId (
newUserId : "your-custom-user-id" ,
traits : {
"exampleTrait" : "value" ,
"userType" : "premium"
}
);
In most cases there is no need to check download status. Helium will display a loading indication if a paywall is presented before download has completed.
After initialization, you can check the status of the paywalls download: String downloadStatus = await heliumFlutter. getDownloadStatus () ?? 'Unknown' ;
The download status will be one of the following:
notDownloadedYet
: The download has not been initiated.
inProgress
: The download has not been initiated.
downloadSuccess
: The download was successful.
downloadFailure
: The download failed.
You can also check if paywalls have been downloaded with await heliumFlutter.paywallsLoaded()
Helium Events
To handle various paywall-related events , implement HeliumCallbacks
and pass it to initialize:
abstract class HeliumCallbacks {
Future < void > onPaywallEvent ( HeliumPaywallEvent heliumPaywallEvent);
}
For example:
class LogCallbacks implements HeliumCallbacks {
@override
Future < void > onPaywallEvent ( HeliumPaywallEvent heliumPaywallEvent) async {
log ( 'onPaywallEvent: ${ heliumPaywallEvent . type } - trigger: ${ heliumPaywallEvent . triggerName } ' );
}
}
Handling Purchases
By default, Helium will handle purchase logic for you. If you want a custom implementation, you’ll want to implement HeliumPurchaseDelegate
and pass that into initialize
.
abstract class HeliumPurchaseDelegate {
Future < HeliumPurchaseResult > makePurchase ( String productId);
Future < bool > restorePurchases ();
}
Basic Implementation
Here’s a basic implementation of HeliumPurchaseDelegate
:
import 'package:helium_flutter/helium_flutter.dart' ;
import 'dart:developer' ;
class PaymentCallbacks implements HeliumCallbacks {
@override
Future < HeliumPurchaseResult > makePurchase ( String productId) async {
log ( 'makePurchase: $ productId ' );
// Implement your purchase logic here
return HeliumPurchaseResult (status : HeliumTransactionStatus .purchased);
}
@override
Future < bool > restorePurchases () async {
log ( 'restorePurchases called' );
// Implement your restore logic here
return true ;
}
}
RevenueCat Implementation Example
import 'package:helium_flutter/helium_flutter.dart' ;
import 'package:purchases_flutter/purchases_flutter.dart' ;
import 'dart:developer' ;
class RevenueCatCallbacks implements HeliumCallbacks {
@override
Future < HeliumPurchaseResult > makePurchase ( String productId) async {
try {
log ( 'RevenueCat making purchase: $ productId ' );
final offerings = await Purchases . getOfferings ();
Package ? packageToPurchase;
// Search all offerings for package
for ( var offering in offerings.all.values) {
for ( var package in offering.availablePackages) {
if (package.storeProduct.identifier == productId) {
packageToPurchase = package;
break ;
}
}
if (packageToPurchase != null ) break ;
}
if (packageToPurchase == null ) {
return HeliumPurchaseResult (
status : HeliumTransactionStatus .failed,
error : 'Product not found in any offering: $ productId ' ,
);
}
final customerInfo = await Purchases . purchasePackage (packageToPurchase);
// Check if the purchase was successful by looking at entitlements
if (customerInfo.entitlements.active.isNotEmpty) {
return HeliumPurchaseResult (status : HeliumTransactionStatus .purchased);
} else {
return HeliumPurchaseResult (status : HeliumTransactionStatus .failed);
}
} catch (e) {
log ( 'RevenueCat purchase error: $ e ' );
if (e is PurchasesErrorCode ) {
if (e == PurchasesErrorCode .purchaseCancelledError) {
return HeliumPurchaseResult (status : HeliumTransactionStatus .cancelled);
} else if (e == PurchasesErrorCode .paymentPendingError) {
return HeliumPurchaseResult (status : HeliumTransactionStatus .pending);
}
}
return HeliumPurchaseResult (
status : HeliumTransactionStatus .failed,
error : 'RevenueCat purchase error: ${( e as PlatformException ?)?. message ?? "Unknown error" } '
);
}
}
@override
Future < bool > restorePurchases () async {
try {
log ( 'RevenueCat restoring purchases' );
final restoredInfo = await Purchases . restorePurchases ();
return restoredInfo.entitlements.active.isNotEmpty;
} catch (e) {
log ( 'RevenueCat restore error: $ e ' );
return false ;
}
}
}
Presenting Paywalls
There are two ways to show a Helium paywall in your Flutter app:
Via Direct Method Call (Recommended)
You can present a paywall programmatically using the presentUpsell
method:
ElevatedButton (
onPressed : () {
final heliumFlutter = HeliumFlutter ();
heliumFlutter. presentUpsell (context : context, trigger : 'insert-trigger-here' );
},
child : Text ( 'Show Premium Features' ),
),
Configure triggers in the dashboard. You can also pass in custom paywall traits and event handlers directly to presentUpsell
:
HeliumFlutter (). presentUpsell (
trigger : 'my_paywall' ,
context : context,
eventHandlers : PaywallEventHandlers (
onOpen : (event) {
log ( ' ${ event . type } - trigger: ${ event . triggerName } ' );
},
onClose : (event) {
log ( ' ${ event . type } - trigger: ${ event . triggerName } ' );
},
onDismissed : (event) {
log ( ' ${ event . type } - trigger: ${ event . triggerName } ' );
},
onPurchaseSucceeded : (event) {
log ( ' ${ event . type } - trigger: ${ event . triggerName } ' );
},
),
customPaywallTraits : {
"has_seen_intro_video" : true ,
}
);
You can also use the HeliumFlutter.getUpsellWidget
method to embed a paywall directly in your widget tree:
class ExamplePageWithEmbeddedPaywall extends StatelessWidget {
const ExamplePageWithEmbeddedPaywall ({ super .key});
@override
Widget build ( BuildContext context) {
return Scaffold (
body : HeliumFlutter (). getUpsellWidget (trigger : 'insert-trigger-here' ),
);
}
}
You will have to handle your own dismissal in this case. You can do so by listening for the paywallDismissed
event in onPaywallEvent
of your HeliumCallbacks
implementation.
Hiding Paywalls
To programmatically hide a paywall that was shown with the presentUpsell
method:
bool hideResult = await heliumFlutter. hideUpsell () ?? false ;
Checking Subscription Status & Entitlements
Use these methods to check current user subscription status.
/// Checks if the user has any active subscription (including non-renewable)
Future < bool > hasAnyActiveSubscription ();
/// Checks if the user has any entitlement
Future < bool > hasAnyEntitlement ();
Fallbacks
The Flutter SDK currently has two fallback options - 1) Fallback bundle and 2) a fallback view. Fallback situations should be quite rare, but to be safe, it is recommended you implement a fallback bundle or custom fallback view.