helium_flutter
Installation
Add the helium_flutter package to your pubspec.yaml:
dependencies:
helium_flutter: ^0.0.1
Then run:
flutter pub get
Make sure that Swift Package Manager Support is enabled:
flutter upgrade
flutter config --enable-swift-package-manager
See Google's guide on this for more details.
Configuration
Set up your HeliumCallbacks
To integrate Helium paywalls, create a class that implements the HeliumCallbacks
interface. This class is responsible for handling the purchase logic for your paywalls.
abstract class HeliumCallbacks {
// [REQUIRED] - Trigger the purchase of a product with the provided product ID.
// This method should return a HeliumTransactionStatus enum.
Future<HeliumTransactionStatus> makePurchase(String productId);
// [OPTIONAL] - Restore any existing subscriptions.
// This method should return a boolean indicating whether the restore was successful.
Future<bool> restorePurchases(bool status);
// [OPTIONAL] - Custom analytics/error logging for paywall/helium related events.
// By default, events are logged to your analytics service, but you can override
// this method to add additional custom logging/handling.
Future<void> onPaywallEvent(Map<String, dynamic> heliumPaywallEvent);
}
The HeliumTransactionStatus
enum defines the possible states of a paywall transaction:
enum HeliumTransactionStatus {
purchased,
failed,
cancelled,
restored,
pending
}
Example Callbacks Implementation:
Basic Implementation
Here's a basic implementation of the HeliumCallbacks
interface:
import 'package:helium_flutter/helium_flutter.dart';
import 'dart:developer';
class PaymentCallbacks implements HeliumCallbacks {
@override
Future<HeliumTransactionStatus> makePurchase(String productId) async {
log('makePurchase: $productId');
// Implement your purchase logic here
return HeliumTransactionStatus.purchased;
}
@override
Future<bool> restorePurchases(bool status) async {
log('restorePurchases: $status');
// Implement your restore logic here
return status;
}
@override
Future<void> onPaywallEvent(Map<String, dynamic> heliumPaywallEvent) async {
log('onPaywallEvent: $heliumPaywallEvent');
// Handle paywall events here
}
}
RevenueCat Implementation
import 'package:helium_flutter/helium_flutter.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'dart:developer';
class RevenueCatCallbacks implements HeliumCallbacks {
@override
Future<HeliumTransactionStatus> makePurchase(String productId) async {
try {
log('RevenueCat making purchase: $productId');
final offerings = await Purchases.getOfferings();
Package? packageToPurchase;
// Find the package in current offering
if (offerings.current != null) {
for (var package in offerings.current!.availablePackages) {
if (package.storeProduct.identifier == productId) {
packageToPurchase = package;
break;
}
}
}
// If not found in current, search all offerings
if (packageToPurchase == null) {
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) {
log('Product not found in any offering: $productId');
return HeliumTransactionStatus.failed;
}
final purchaseResult = await Purchases.purchasePackage(packageToPurchase);
// Check if the purchase was successful by looking at entitlements
if (purchaseResult.customerInfo.entitlements.active.isNotEmpty) {
return HeliumTransactionStatus.purchased;
} else {
return HeliumTransactionStatus.failed;
}
} catch (e) {
log('RevenueCat purchase error: $e');
if (e is PurchasesErrorCode) {
if (e == PurchasesErrorCode.purchaseCancelledError) {
return HeliumTransactionStatus.cancelled;
} else if (e == PurchasesErrorCode.paymentPendingError) {
return HeliumTransactionStatus.pending;
}
}
return HeliumTransactionStatus.failed;
}
}
@override
Future<bool> restorePurchases(bool status) 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;
}
}
@override
Future<void> onPaywallEvent(Map<String, dynamic> heliumPaywallEvent) async {
log('RevenueCat paywall event: $heliumPaywallEvent');
// Handle specific events as needed
final eventType = heliumPaywallEvent['type'];
if (eventType == 'subscriptionSucceeded') {
// Handle successful subscription
final productId = heliumPaywallEvent['productKey'];
log('Subscription succeeded for product: $productId');
// Add your custom analytics tracking here
}
}
}
Initialize Helium and Download Paywall Configs
In your app's initialization code (typically in main.dart
or your root widget), add the following to download paywall configurations:
import 'package:helium_flutter/helium_flutter.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
// Create your callbacks implementation
PaymentCallbacks paymentCallbacks = PaymentCallbacks();
// Initialize Helium
final heliumFlutter = HeliumFlutter();
heliumFlutter.initialize(
// You'll get this from Helium founders during setup!
apiKey: "<your-helium-api-key>",
// The callbacks implementation you created earlier
callbacks: paymentCallbacks,
// If set, a custom API endpoint (usually provided by Helium)
customAPIEndpoint: "https://api-v2.tryhelium.com/on-launch",
// If set, a custom user ID to use instead of Helium's
customUserId: "your-custom-user-id", // Optional
// Custom user traits for targeting and personalization
customUserTraits: {
"exampleUserTrait": "test_value",
"subscriptionStatus": "active",
"userIntent": "upgrade",
"numericalValue": 3.0,
}, // Optional
);
runApp(const MyApp());
}
Passing Custom User Traits
Custom user traits can be any key-value pairs where the value is a serializable type (String, num, bool, etc.). These traits can be used for targeting, personalization, and dynamic content in your paywalls.
Passing in a Custom User ID
By default, Helium generates a UUID per app session to identify users. You can override this with your own custom user ID (e.g., from a 3rd party analytics service) by passing it in the initialize
method or by explicitly calling overrideUserId
:
// Set a custom user ID
await heliumFlutter.overrideUserId(
newUserId: "your-custom-user-id",
traits: {
"exampleTrait": "value",
"userType": "premium"
}
);
Checking Download Status
After initialization, you can check the status of the paywall configuration download:
String downloadStatus = await heliumFlutter.getDownloadStatus() ?? 'Unknown';
The download status will be one of the following:
"notDownloadedYet"
: The download has not been initiated or is still in progress."downloadSuccess"
: The download was successful."downloadFailure"
: The download failed.
You can use this to handle different states in your app.
Checking if Paywalls are Loaded
You can also check if paywalls have been loaded successfully:
bool paywallsLoaded = await heliumFlutter.paywallsLoaded() ?? false;
Presenting Paywalls
There are several ways to present Helium paywalls in your Flutter app:
Via Direct Method Call
You can present a paywall programmatically using the presentUpsell
method:
ElevatedButton(
onPressed: () async {
await heliumFlutter.presentUpsell(trigger: 'onboarding');
},
child: Text('Show Premium Features'),
),
The trigger
parameter is a unique identifier for the paywall trigger point in your app. Helium uses this to track and optimize the paywall for each trigger point.
Via Widget Integration
You can also use the UpsellViewForTrigger
widget to embed a paywall directly in your widget tree:
class ViewForTriggerPage extends StatelessWidget {
const ViewForTriggerPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: UpsellViewForTrigger(trigger: 'onboarding'),
);
}
}
Hiding Paywalls
To programmatically hide a paywall:
bool hideResult = await heliumFlutter.hideUpsell() ?? false;
Handling Custom Dismissal Actions
You can implement custom dismissal logic by handling paywall events in your HeliumCallbacks
implementation:
@override
Future<void> onPaywallEvent(Map<String, dynamic> heliumPaywallEvent) async {
final eventType = heliumPaywallEvent['type'];
if (eventType == 'ctaPressed') {
final ctaName = heliumPaywallEvent['ctaName'];
final triggerName = heliumPaywallEvent['triggerName'];
if (ctaName == 'dismiss') {
// Handle custom dismissal logic here
// For example, navigate back or show a different screen
}
}
}
Getting User ID
To retrieve the Helium user ID:
String userId = await heliumFlutter.getHeliumUserId() ?? 'Unknown';
Testing
Documentation for testing will be provided separately. After integration, please message us directly to get set up with a test app + in-app test support.